<commit_before>package main

import  (
    "os"
    "fmt"
    "archive/tar"
    "crypto/sha512"
    "encoding/hex"
    "encoding/json"
    "github.com/hinasssan/msgpack-go"
    "hash"
    "path/filepath"
    "flag"
    "net/http"
    "net/url"
    "io/ioutil"
    "log"
)

type LMVFile struct {
    Size int64  `msgpack:"size"`
    Name string `msgpack:"name"`
    Algorithm string `msgpack:"algorithm"`
    Chunks []LMVChunk `msgpack:"chunks"`
    Tar bool `msgpack:"tar"`
}

type LMVChunk struct {
    Hash string `msgpack:"hash"`
    Size int64 `msgpack:"size"`
    Index int `msgpack:"index"`
}

// CONSTANTS

const CHUNK_SIZE int64 = 1048576

func TarballDirectory(fp string) string {

    f, err := ioutil.TempFile("", "")

    if err != nil {
        log.Fatal(err)
    }

    type WalkedFile struct {
        Path string
        Info os.FileInfo
    }

    if err != nil {
        log.Fatal(err)
    }

    defer f.Close()

    tw := tar.NewWriter(f)

    files := make([]WalkedFile, 0)

    tarball := func(path string, info os.FileInfo, err error) error {

        if info.IsDir() {
            //
        } else {

            files = append(files, WalkedFile{
                Path: path,
                Info: info,
            })

        }
        return nil

    }

    err = filepath.Walk(fp, tarball)
    if err != nil {
        log.Fatal(err)
    }

    for _, fr := range files {

        hdr := &tar.Header{
            Name: fr.Path,
            Size: fr.Info.Size(),
        }

        err := tw.WriteHeader(hdr)

        if err != nil {
            log.Fatal(err)
        }

        file, err := os.Open(fr.Path)

        if err != nil {
            log.Fatal(err)
        }

        defer file.Close()

        stat, err := file.Stat()

        if err != nil {
            log.Fatal(err)
        }

        bs := make([]byte, stat.Size())
        _, err = file.Read(bs)

        if err != nil {
            log.Fatal(err)
        }

        if _, err := tw.Write([]byte(bs)); err != nil {
            log.Fatal(err)
        }

    }

    if err := tw.Close(); err != nil {
        log.Fatal(err)
    }

    return f.Name()

}

func CalculateSHA512(data []byte) string {

    var hasher hash.Hash = sha512.New()

    hasher.Reset()
    hasher.Write(data)
    return hex.EncodeToString(hasher.Sum(nil))

}

func encode(fp string, token bool, register string) {

    lmv_file := new(LMVFile)

    lmv_file.Algorithm = "SHA512"

    lmv_file.Name = filepath.Base(fp)

    stat, err := os.Stat(fp)

    if err != nil {
        log.Fatal(err)
    }

    if stat.IsDir() {
        fp = TarballDirectory(fp)
        lmv_file.Tar = true
    } else {
        lmv_file.Tar = false
    }

    file, err := os.Open(fp)

    if err != nil {
        log.Fatal(err)
    }

    defer file.Close()

    stat, err = file.Stat()

    if err != nil {
        log.Fatal(err)
    }

    bs := make([]byte, stat.Size())
    _, err = file.Read(bs)

    if err != nil {
        log.Fatal(err)
    }

    lmv_file.Size = stat.Size()

    chunks := make([]LMVChunk, 1)

    if stat.Size() <= CHUNK_SIZE {

        chunks[0] = LMVChunk {
            CalculateSHA512(bs),
            stat.Size(),
            0,
        }

    } else {

        chunk_count := stat.Size() / CHUNK_SIZE + 1

        chunks = make([]LMVChunk, chunk_count)

        for i := 0; i < len(chunks) - 1; i++ {

            chunk := bs[int64(i)*CHUNK_SIZE:int64(i+1)*CHUNK_SIZE]

            chunks[i] = LMVChunk{
                CalculateSHA512(chunk),
                CHUNK_SIZE,
                i,
            }

        }

        chunk := bs[int64(cap(chunks)-1)*CHUNK_SIZE:]

        chunks[cap(chunks)-1] = LMVChunk{
            CalculateSHA512(chunk),
            int64(len(chunk)),
            cap(chunks)-1,
        }

    }

    lmv_file.Chunks = chunks

    if token {

        upload_address := register + "/upload"

        packed, err := json.Marshal(lmv_file)

        if err != nil {
            log.Fatal(err)
        }

        fields := make(url.Values)
        fields.Set("file", string(packed))

        resp, err := http.PostForm(upload_address, fields)

        if err != nil {
            log.Fatal(err)
        }

        defer resp.Body.Close()

        body, err := ioutil.ReadAll(resp.Body)

        if err != nil {
            log.Fatal(err)
        }

        fmt.Println("'" + lmv_file.Name + "'" + " --> " + "'" + string(body) + "'")

    } else {

        os.Create(lmv_file.Name + ".lmv")

        b, err := msgpack.Marshal(lmv_file)

        if err != nil {
            log.Fatal(err)
        }

        err = ioutil.WriteFile(lmv_file.Name + ".lmv", b, 0644)

        if err != nil {
            log.Fatal(err)
        }

    }

}

func main() {

    token := flag.Bool("token", false, "Use tokens in place of .lmv files")
    register := flag.String("register", "http://127.0.0.1:8081", "Register for tokens (including protocol)")

    flag.Parse()

    if len(os.Args) < 2 {

        fmt.Println("Use lmv -h for usage")

    } else {

        for i := 0; i < len(os.Args[1:]); i++ {

            if _, err := os.Stat(os.Args[i+1]); err == nil {

                encode(os.Args[i+1], *token, *register)

            }

        }

    }
}
<commit_msg>Set REGISTER as a global variable<commit_after>package main

import  (
    "os"
    "fmt"
    "archive/tar"
    "crypto/sha512"
    "encoding/hex"
    "encoding/json"
    "github.com/hinasssan/msgpack-go"
    "hash"
    "path/filepath"
    "flag"
    "net/http"
    "net/url"
    "io/ioutil"
    "log"
)

type LMVFile struct {
    Size int64  `msgpack:"size"`
    Name string `msgpack:"name"`
    Algorithm string `msgpack:"algorithm"`
    Chunks []LMVChunk `msgpack:"chunks"`
    Tar bool `msgpack:"tar"`
}

type LMVChunk struct {
    Hash string `msgpack:"hash"`
    Size int64 `msgpack:"size"`
    Index int `msgpack:"index"`
}

// CONSTANTS

const CHUNK_SIZE int64 = 1048576
var REGISTER string = ""

func TarballDirectory(fp string) string {

    f, err := ioutil.TempFile("", "")

    if err != nil {
        log.Fatal(err)
    }

    type WalkedFile struct {
        Path string
        Info os.FileInfo
    }

    if err != nil {
        log.Fatal(err)
    }

    defer f.Close()

    tw := tar.NewWriter(f)

    files := make([]WalkedFile, 0)

    tarball := func(path string, info os.FileInfo, err error) error {

        if info.IsDir() {
            //
        } else {

            files = append(files, WalkedFile{
                Path: path,
                Info: info,
            })

        }
        return nil

    }

    err = filepath.Walk(fp, tarball)
    if err != nil {
        log.Fatal(err)
    }

    for _, fr := range files {

        hdr := &tar.Header{
            Name: fr.Path,
            Size: fr.Info.Size(),
        }

        err := tw.WriteHeader(hdr)

        if err != nil {
            log.Fatal(err)
        }

        file, err := os.Open(fr.Path)

        if err != nil {
            log.Fatal(err)
        }

        defer file.Close()

        stat, err := file.Stat()

        if err != nil {
            log.Fatal(err)
        }

        bs := make([]byte, stat.Size())
        _, err = file.Read(bs)

        if err != nil {
            log.Fatal(err)
        }

        if _, err := tw.Write([]byte(bs)); err != nil {
            log.Fatal(err)
        }

    }

    if err := tw.Close(); err != nil {
        log.Fatal(err)
    }

    return f.Name()

}

func CalculateSHA512(data []byte) string {

    var hasher hash.Hash = sha512.New()

    hasher.Reset()
    hasher.Write(data)
    return hex.EncodeToString(hasher.Sum(nil))

}

func encode(fp string, token bool) {

    lmv_file := new(LMVFile)

    lmv_file.Algorithm = "SHA512"

    lmv_file.Name = filepath.Base(fp)

    stat, err := os.Stat(fp)

    if err != nil {
        log.Fatal(err)
    }

    if stat.IsDir() {
        fp = TarballDirectory(fp)
        lmv_file.Tar = true
    } else {
        lmv_file.Tar = false
    }

    file, err := os.Open(fp)

    if err != nil {
        log.Fatal(err)
    }

    defer file.Close()

    stat, err = file.Stat()

    if err != nil {
        log.Fatal(err)
    }

    bs := make([]byte, stat.Size())
    _, err = file.Read(bs)

    if err != nil {
        log.Fatal(err)
    }

    lmv_file.Size = stat.Size()

    chunks := make([]LMVChunk, 1)

    if stat.Size() <= CHUNK_SIZE {

        chunks[0] = LMVChunk {
            CalculateSHA512(bs),
            stat.Size(),
            0,
        }

    } else {

        chunk_count := stat.Size() / CHUNK_SIZE + 1

        chunks = make([]LMVChunk, chunk_count)

        for i := 0; i < len(chunks) - 1; i++ {

            chunk := bs[int64(i)*CHUNK_SIZE:int64(i+1)*CHUNK_SIZE]

            chunks[i] = LMVChunk{
                CalculateSHA512(chunk),
                CHUNK_SIZE,
                i,
            }

        }

        chunk := bs[int64(cap(chunks)-1)*CHUNK_SIZE:]

        chunks[cap(chunks)-1] = LMVChunk{
            CalculateSHA512(chunk),
            int64(len(chunk)),
            cap(chunks)-1,
        }

    }

    lmv_file.Chunks = chunks

    if token {

        upload_address := REGISTER + "/upload"

        packed, err := json.Marshal(lmv_file)

        if err != nil {
            log.Fatal(err)
        }

        fields := make(url.Values)
        fields.Set("file", string(packed))

        resp, err := http.PostForm(upload_address, fields)

        if err != nil {
            log.Fatal(err)
        }

        defer resp.Body.Close()

        body, err := ioutil.ReadAll(resp.Body)

        if err != nil {
            log.Fatal(err)
        }

        fmt.Println("'" + lmv_file.Name + "'" + " --> " + "'" + string(body) + "'")

    } else {

        os.Create(lmv_file.Name + ".lmv")

        b, err := msgpack.Marshal(lmv_file)

        if err != nil {
            log.Fatal(err)
        }

        err = ioutil.WriteFile(lmv_file.Name + ".lmv", b, 0644)

        if err != nil {
            log.Fatal(err)
        }

    }

}

func main() {

    token := flag.Bool("token", false, "Use tokens in place of .lmv files")
    register := flag.String("register", "http://127.0.0.1:8081", "Register for tokens (including protocol)")

    REGISTER = *register

    flag.Parse()

    if len(os.Args) < 2 {

        fmt.Println("Use lmv -h for usage")

    } else {

        for i := 0; i < len(os.Args[1:]); i++ {

            if _, err := os.Stat(os.Args[i+1]); err == nil {

                encode(os.Args[i+1], *token)

            }

        }

    }
}
<|endoftext|>


<commit_before>package lxd

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

	"github.com/gorilla/websocket"
	"gopkg.in/macaroon-bakery.v2/bakery"
	"gopkg.in/macaroon-bakery.v2/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
	chConnected chan struct{}

	eventListeners     []*EventListener
	eventListenersLock sync.Mutex

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

	bakeryClient         *httpbakery.Client
	bakeryInteractor     []httpbakery.Interactor
	requireAuthenticated bool

	clusterTarget string
	project       string
}

// Disconnect gets rid of any background goroutines
func (r *ProtocolLXD) Disconnect() {
	if r.chConnected != nil {
		close(r.chConnected)
	}
}

// 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"
	info.URL = r.httpHost
	info.SocketPath = r.httpUnixPath
	info.Project = r.project
	if info.Project == "" {
		info.Project = "default"
	}

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

	if r.server != nil && len(r.server.Environment.Addresses) > 0 {
		for _, addr := range r.server.Environment.Addresses {
			if strings.HasPrefix(addr, ":") {
				continue
			}

			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)
}

// RawOperation allows direct querying of a LXD API endpoint returning
// background operations.
func (r *ProtocolLXD) RawOperation(method string, path string, data interface{}, ETag string) (Operation, string, error) {
	return r.queryOperation(method, path, data, ETag)
}

// Internal functions
func lxdParseResponse(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 {
		switch data.(type) {
		case io.Reader:
			// Some data to be sent along with the request
			req, err = http.NewRequest(method, url, data.(io.Reader))
			if err != nil {
				return nil, "", err
			}

			// Set the encoding accordingly
			req.Header.Set("Content-Type", "application/octet-stream")
		default:
			// 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 lxdParseResponse(resp)
}

func (r *ProtocolLXD) setQueryAttributes(uri string) (string, error) {
	// Parse the full URI
	fields, err := neturl.Parse(uri)
	if err != nil {
		return "", err
	}

	// Extract query fields and update for cluster targeting or project
	values := fields.Query()
	if r.clusterTarget != "" {
		if values.Get("target") == "" {
			values.Set("target", r.clusterTarget)
		}
	}

	if r.project != "" {
		if values.Get("project") == "" {
			values.Set("project", r.project)
		}
	}
	fields.RawQuery = values.Encode()

	return fields.String(), nil
}

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)

	// Add project/target
	url, err := r.setQueryAttributes(url)
	if err != nil {
		return nil, "", err
	}

	// Run the actual query
	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: %v", url)

	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 {
		for _, interactor := range r.bakeryInteractor {
			r.bakeryClient.AddInteractor(interactor)
		}
	}
}
<commit_msg>Use result of type assertion to simplify cases<commit_after>package lxd

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

	"github.com/gorilla/websocket"
	"gopkg.in/macaroon-bakery.v2/bakery"
	"gopkg.in/macaroon-bakery.v2/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
	chConnected chan struct{}

	eventListeners     []*EventListener
	eventListenersLock sync.Mutex

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

	bakeryClient         *httpbakery.Client
	bakeryInteractor     []httpbakery.Interactor
	requireAuthenticated bool

	clusterTarget string
	project       string
}

// Disconnect gets rid of any background goroutines
func (r *ProtocolLXD) Disconnect() {
	if r.chConnected != nil {
		close(r.chConnected)
	}
}

// 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"
	info.URL = r.httpHost
	info.SocketPath = r.httpUnixPath
	info.Project = r.project
	if info.Project == "" {
		info.Project = "default"
	}

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

	if r.server != nil && len(r.server.Environment.Addresses) > 0 {
		for _, addr := range r.server.Environment.Addresses {
			if strings.HasPrefix(addr, ":") {
				continue
			}

			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)
}

// RawOperation allows direct querying of a LXD API endpoint returning
// background operations.
func (r *ProtocolLXD) RawOperation(method string, path string, data interface{}, ETag string) (Operation, string, error) {
	return r.queryOperation(method, path, data, ETag)
}

// Internal functions
func lxdParseResponse(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 {
		switch data := data.(type) {
		case io.Reader:
			// Some data to be sent along with the request
			req, err = http.NewRequest(method, url, data)
			if err != nil {
				return nil, "", err
			}

			// Set the encoding accordingly
			req.Header.Set("Content-Type", "application/octet-stream")
		default:
			// 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 lxdParseResponse(resp)
}

func (r *ProtocolLXD) setQueryAttributes(uri string) (string, error) {
	// Parse the full URI
	fields, err := neturl.Parse(uri)
	if err != nil {
		return "", err
	}

	// Extract query fields and update for cluster targeting or project
	values := fields.Query()
	if r.clusterTarget != "" {
		if values.Get("target") == "" {
			values.Set("target", r.clusterTarget)
		}
	}

	if r.project != "" {
		if values.Get("project") == "" {
			values.Set("project", r.project)
		}
	}
	fields.RawQuery = values.Encode()

	return fields.String(), nil
}

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)

	// Add project/target
	url, err := r.setQueryAttributes(url)
	if err != nil {
		return nil, "", err
	}

	// Run the actual query
	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: %v", url)

	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 {
		for _, interactor := range r.bakeryInteractor {
			r.bakeryClient.AddInteractor(interactor)
		}
	}
}
<|endoftext|>


<commit_before>/*
 *
 * Copyright 2014, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

package grpc

import (
	"errors"
	"log"
	"sync"
	"time"

	"golang.org/x/net/context"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/transport"
)

var (
	// ErrUnspecTarget indicates that the target address is unspecified.
	ErrUnspecTarget = errors.New("grpc: target is unspecified")
	// ErrClientConnClosing indicates that the operation is illegal because
	// the session is closing.
	ErrClientConnClosing = errors.New("grpc: the client connection is closing")
	// ErrClientConnTimeout indicates that the connection could not be
	// established or re-established within the specified timeout.
	ErrClientConnTimeout = errors.New("grpc: timed out trying to connect")
)

// DialOption configures how we set up the connection.
type DialOption func(*transport.DialOptions)

// WithTransportCredentials returns a DialOption which configures a
// connection level security credentials (e.g., TLS/SSL).
func WithTransportCredentials(creds credentials.TransportAuthenticator) DialOption {
	return func(o *transport.DialOptions) {
		o.AuthOptions = append(o.AuthOptions, creds)
	}
}

// WithPerRPCCredentials returns a DialOption which sets
// credentials which will place auth state on each outbound RPC.
func WithPerRPCCredentials(creds credentials.Credentials) DialOption {
	return func(o *transport.DialOptions) {
		o.AuthOptions = append(o.AuthOptions, creds)
	}
}

// WithTimeout returns a DialOption that configures a timeout for dialing a client connection.
func WithTimeout(d time.Duration) DialOption {
	return func(o *transport.DialOptions) {
		o.Timeout = d
	}
}

// Dial creates a client connection the given target.
// TODO(zhaoq): Have an option to make Dial return immediately without waiting
// for connection to complete.
func Dial(target string, opts ...DialOption) (*ClientConn, error) {
	if target == "" {
		return nil, ErrUnspecTarget
	}
	cc := &ClientConn{
		target: target,
	}
	for _, opt := range opts {
		opt(&cc.dopts)
	}
	if err := cc.resetTransport(false); err != nil {
		return nil, err
	}
	cc.shutdownChan = make(chan struct{})
	// Start to monitor the error status of transport.
	go cc.transportMonitor()
	return cc, nil
}

// ClientConn represents a client connection to an RPC service.
type ClientConn struct {
	target       string
	dopts        transport.DialOptions
	shutdownChan chan struct{}

	mu sync.Mutex
	// ready is closed and becomes nil when a new transport is up or failed
	// due to timeout.
	ready chan struct{}
	// Indicates the ClientConn is under destruction.
	closing bool
	// Every time a new transport is created, this is incremented by 1. Used
	// to avoid trying to recreate a transport while the new one is already
	// under construction.
	transportSeq int
	transport    transport.ClientTransport
}

func (cc *ClientConn) resetTransport(closeTransport bool) error {
	var retries int
	start := time.Now()
	for {
		cc.mu.Lock()
		t := cc.transport
		ts := cc.transportSeq
		// Avoid wait() picking up a dying transport unnecessarily.
		cc.transportSeq = 0
		if cc.closing {
			cc.mu.Unlock()
			return ErrClientConnClosing
		}
		cc.mu.Unlock()
		if closeTransport {
			t.Close()
		}
		// Adjust timeout for the current try.
		dopts := cc.dopts
		if dopts.Timeout < 0 {
			cc.Close()
			return ErrClientConnTimeout
		}
		if dopts.Timeout > 0 {
			dopts.Timeout -= time.Since(start)
			if dopts.Timeout <= 0 {
				cc.Close()
				return ErrClientConnTimeout
			}
		}
		newTransport, err := transport.NewClientTransport(cc.target, &dopts)
		if err != nil {
			sleepTime := backoff(retries)
			// Fail early before falling into sleep.
			if cc.dopts.Timeout > 0 && cc.dopts.Timeout < sleepTime + time.Since(start) {
				cc.Close()
				return ErrClientConnTimeout
			}
			closeTransport = false
			time.Sleep(sleepTime)
			retries++
			// TODO(zhaoq): Record the error with glog.V.
			log.Printf("grpc: ClientConn.resetTransport failed to create client transport: %v; Reconnecting to %q", err, cc.target)
			continue
		}
		cc.mu.Lock()
		cc.transport = newTransport
		cc.transportSeq = ts + 1
		if cc.ready != nil {
			close(cc.ready)
			cc.ready = nil
		}
		cc.mu.Unlock()
		return nil
	}
}

// Run in a goroutine to track the error in transport and create the
// new transport if an error happens. It returns when the channel is closing.
func (cc *ClientConn) transportMonitor() {
	for {
		select {
		// shutdownChan is needed to detect the channel teardown when
		// the ClientConn is idle (i.e., no RPC in flight).
		case <-cc.shutdownChan:
			return
		case <-cc.transport.Error():
			if err := cc.resetTransport(true); err != nil {
				// The channel is closing.
				// TODO(zhaoq): Record the error with glog.V.
				log.Printf("grpc: ClientConn.transportMonitor exits due to: %v", err)
				return
			}
			continue
		}
	}
}

// When wait returns, either the new transport is up or ClientConn is
// closing. Used to avoid working on a dying transport. It updates and
// returns the transport and its version when there is no error.
func (cc *ClientConn) wait(ctx context.Context, ts int) (transport.ClientTransport, int, error) {
	for {
		cc.mu.Lock()
		switch {
		case cc.closing:
			cc.mu.Unlock()
			return nil, 0, ErrClientConnClosing
		case ts < cc.transportSeq:
			// Worked on a dying transport. Try the new one immediately.
			defer cc.mu.Unlock()
			return cc.transport, cc.transportSeq, nil
		default:
			ready := cc.ready
			if ready == nil {
				ready = make(chan struct{})
				cc.ready = ready
			}
			cc.mu.Unlock()
			select {
			case <-ctx.Done():
				return nil, 0, transport.ContextErr(ctx.Err())
			// Wait until the new transport is ready or failed.
			case <-ready:
			}
		}
	}
}

// Close starts to tear down the ClientConn. Returns ErrClientConnClosing if
// it has been closed (mostly due to dial time-out).
// TODO(zhaoq): Make this synchronous to avoid unbounded memory consumption in
// some edge cases (e.g., the caller opens and closes many ClientConn's in a
// tight loop.
func (cc *ClientConn) Close() error {
	cc.mu.Lock()
	defer cc.mu.Unlock()
	if cc.closing {
		return ErrClientConnClosing
	}
	cc.closing = true
	if cc.ready != nil {
		close(cc.ready)
		cc.ready = nil
	}
	if cc.transport != nil {
		cc.transport.Close()
	}
	if cc.shutdownChan != nil {
		close(cc.shutdownChan)
	}
	return nil
}
<commit_msg>fix a bug<commit_after>/*
 *
 * Copyright 2014, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

package grpc

import (
	"errors"
	"log"
	"sync"
	"time"

	"golang.org/x/net/context"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/transport"
)

var (
	// ErrUnspecTarget indicates that the target address is unspecified.
	ErrUnspecTarget = errors.New("grpc: target is unspecified")
	// ErrClientConnClosing indicates that the operation is illegal because
	// the session is closing.
	ErrClientConnClosing = errors.New("grpc: the client connection is closing")
	// ErrClientConnTimeout indicates that the connection could not be
	// established or re-established within the specified timeout.
	ErrClientConnTimeout = errors.New("grpc: timed out trying to connect")
)

// DialOption configures how we set up the connection.
type DialOption func(*transport.DialOptions)

// WithTransportCredentials returns a DialOption which configures a
// connection level security credentials (e.g., TLS/SSL).
func WithTransportCredentials(creds credentials.TransportAuthenticator) DialOption {
	return func(o *transport.DialOptions) {
		o.AuthOptions = append(o.AuthOptions, creds)
	}
}

// WithPerRPCCredentials returns a DialOption which sets
// credentials which will place auth state on each outbound RPC.
func WithPerRPCCredentials(creds credentials.Credentials) DialOption {
	return func(o *transport.DialOptions) {
		o.AuthOptions = append(o.AuthOptions, creds)
	}
}

// WithTimeout returns a DialOption that configures a timeout for dialing a client connection.
func WithTimeout(d time.Duration) DialOption {
	return func(o *transport.DialOptions) {
		o.Timeout = d
	}
}

// Dial creates a client connection the given target.
// TODO(zhaoq): Have an option to make Dial return immediately without waiting
// for connection to complete.
func Dial(target string, opts ...DialOption) (*ClientConn, error) {
	if target == "" {
		return nil, ErrUnspecTarget
	}
	cc := &ClientConn{
		target: target,
	}
	for _, opt := range opts {
		opt(&cc.dopts)
	}
	if err := cc.resetTransport(false); err != nil {
		return nil, err
	}
	cc.shutdownChan = make(chan struct{})
	// Start to monitor the error status of transport.
	go cc.transportMonitor()
	return cc, nil
}

// ClientConn represents a client connection to an RPC service.
type ClientConn struct {
	target       string
	dopts        transport.DialOptions
	shutdownChan chan struct{}

	mu sync.Mutex
	// ready is closed and becomes nil when a new transport is up or failed
	// due to timeout.
	ready chan struct{}
	// Indicates the ClientConn is under destruction.
	closing bool
	// Every time a new transport is created, this is incremented by 1. Used
	// to avoid trying to recreate a transport while the new one is already
	// under construction.
	transportSeq int
	transport    transport.ClientTransport
}

func (cc *ClientConn) resetTransport(closeTransport bool) error {
	var retries int
	start := time.Now()
	for {
		cc.mu.Lock()
		t := cc.transport
		ts := cc.transportSeq
		// Avoid wait() picking up a dying transport unnecessarily.
		cc.transportSeq = 0
		if cc.closing {
			cc.mu.Unlock()
			return ErrClientConnClosing
		}
		cc.mu.Unlock()
		if closeTransport {
			t.Close()
		}
		// Adjust timeout for the current try.
		dopts := cc.dopts
		if dopts.Timeout < 0 {
			cc.Close()
			return ErrClientConnTimeout
		}
		if dopts.Timeout > 0 {
			dopts.Timeout -= time.Since(start)
			if dopts.Timeout <= 0 {
				cc.Close()
				return ErrClientConnTimeout
			}
		}
		newTransport, err := transport.NewClientTransport(cc.target, &dopts)
		if err != nil {
			sleepTime := backoff(retries)
			// Fail early before falling into sleep.
			if cc.dopts.Timeout > 0 && cc.dopts.Timeout < sleepTime + time.Since(start) {
				cc.Close()
				return ErrClientConnTimeout
			}
			closeTransport = false
			time.Sleep(sleepTime)
			retries++
			// TODO(zhaoq): Record the error with glog.V.
			log.Printf("grpc: ClientConn.resetTransport failed to create client transport: %v; Reconnecting to %q", err, cc.target)
			continue
		}
		cc.mu.Lock()
		if cc.closing {
			// cc.Close() has been invoked.
			cc.mu.Unlock()
			newTransport.Close()
			return ErrClientConnClosing
		}
		cc.transport = newTransport
		cc.transportSeq = ts + 1
		if cc.ready != nil {
			close(cc.ready)
			cc.ready = nil
		}
		cc.mu.Unlock()
		return nil
	}
}

// Run in a goroutine to track the error in transport and create the
// new transport if an error happens. It returns when the channel is closing.
func (cc *ClientConn) transportMonitor() {
	for {
		select {
		// shutdownChan is needed to detect the channel teardown when
		// the ClientConn is idle (i.e., no RPC in flight).
		case <-cc.shutdownChan:
			return
		case <-cc.transport.Error():
			if err := cc.resetTransport(true); err != nil {
				// The channel is closing.
				// TODO(zhaoq): Record the error with glog.V.
				log.Printf("grpc: ClientConn.transportMonitor exits due to: %v", err)
				return
			}
			continue
		}
	}
}

// When wait returns, either the new transport is up or ClientConn is
// closing. Used to avoid working on a dying transport. It updates and
// returns the transport and its version when there is no error.
func (cc *ClientConn) wait(ctx context.Context, ts int) (transport.ClientTransport, int, error) {
	for {
		cc.mu.Lock()
		switch {
		case cc.closing:
			cc.mu.Unlock()
			return nil, 0, ErrClientConnClosing
		case ts < cc.transportSeq:
			// Worked on a dying transport. Try the new one immediately.
			defer cc.mu.Unlock()
			return cc.transport, cc.transportSeq, nil
		default:
			ready := cc.ready
			if ready == nil {
				ready = make(chan struct{})
				cc.ready = ready
			}
			cc.mu.Unlock()
			select {
			case <-ctx.Done():
				return nil, 0, transport.ContextErr(ctx.Err())
			// Wait until the new transport is ready or failed.
			case <-ready:
			}
		}
	}
}

// Close starts to tear down the ClientConn. Returns ErrClientConnClosing if
// it has been closed (mostly due to dial time-out).
// TODO(zhaoq): Make this synchronous to avoid unbounded memory consumption in
// some edge cases (e.g., the caller opens and closes many ClientConn's in a
// tight loop.
func (cc *ClientConn) Close() error {
	cc.mu.Lock()
	defer cc.mu.Unlock()
	if cc.closing {
		return ErrClientConnClosing
	}
	cc.closing = true
	if cc.ready != nil {
		close(cc.ready)
		cc.ready = nil
	}
	if cc.transport != nil {
		cc.transport.Close()
	}
	if cc.shutdownChan != nil {
		close(cc.shutdownChan)
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright © 2016 Kevin Kirsche <kev.kirsche@gmail.com>
//
// 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 cmd

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"

	"github.com/Sirupsen/logrus"
	"github.com/spf13/cobra"
)

var (
	domains []string

	outputJSON  bool
	ouptutHuman bool

	grepable bool
)

// DetectedURL is a sub-struct of DomainReportResult
type DetectedURL struct {
	Positives int    `json:"positives"`
	ScanDate  string `json:"scan_date"`
	Total     int    `json:"total"`
	URL       string `json:"url"`
}

// UndetectedDownloadSample is a sub-struct of DomainReportResult
type UndetectedDownloadSample struct {
	Date      string `json:"date"`
	Positives int    `json:"positives"`
	Sha256    string `json:"sha256"`
	Total     int    `json:"total"`
}

// UndetectedReferrerSample is a sub-struct of DomainReportResult
type UndetectedReferrerSample struct {
	Positives int    `json:"positives"`
	Sha256    string `json:"sha256"`
	Total     int    `json:"total"`
}

// Resolution is a sub-struct of DomainReportResult
type Resolution struct {
	IPAddress    string `json:"ip_address"`
	LastResolved string `json:"last_resolved"`
}

// WebutationInfo is a sub-struct of DomainReportResult
type WebutationInfo struct {
	AdultContent string `json:"Adult content"`
	SafetyScore  int    `json:"Safety score"`
	Verdict      string `json:"Verdict"`
}

// WebOfTrustInfo is a sub-struct of DomainReportResult
type WebOfTrustInfo struct {
	ChildSafety       string `json:"Child safety"`
	Privacy           string `json:"Privacy"`
	Trustworthiness   string `json:"Trustworthiness"`
	VendorReliability string `json:"Vendor reliability"`
}

// DomainReportResult is the result that is received after requesting a
// domain report from VirusTotal's public API.
type DomainReportResult struct {
	AlexaDomainInfo              string         `json:"Alexa domain info"`
	BitDefenderCategory          string         `json:"BitDefender category"`
	TrendMicroCategory           string         `json:"TrendMicro category"`
	WOTDomainInfo                WebOfTrustInfo `json:"WOT domain info"`
	WebsenseThreatSeekerCategory string         `json:"Websense ThreatSeeker category"`
	WebutationDomainInfo         WebutationInfo `json:"Webutation domain info"`
	Categories                   []string       `json:"categories"`
	JSON                         string
	DetectedURLs                 []DetectedURL              `json:"detected_urls"`
	DomainSiblings               []string                   `json:"domain_siblings"`
	Resolutions                  []Resolution               `json:"resolutions"`
	ResponseCode                 int                        `json:"response_code"`
	Subdomains                   []string                   `json:"subdomains"`
	UndetectedDownloadedSamples  []UndetectedDownloadSample `json:"undetected_downloaded_samples"`
	UndetectedReferrerSamples    []UndetectedReferrerSample `json:"undetected_referrer_samples"`
	VerboseMsg                   string                     `json:"verbose_msg"`
	Whois                        string                     `json:"whois"`
	WhoisTimestamp               float64                    `json:"whois_timestamp"`
}

// domainCmd represents the scanurl command
var domainCmd = &cobra.Command{
	Use:   "domain",
	Short: "Retrieve informaiton about a domain",
	Long: `Retrieve all information a domain in human readable or JSON format.

Example:

virustotal domain -a {{ api_key }} -d {{ domain(s) }}
`,
	Run: func(cmd *cobra.Command, args []string) {
		responses := retrieveDomainInformation()

		for _, resp := range responses {
			if outputJSON {
				fmt.Println(resp.JSON)
			}

			if ouptutHuman || outputJSON == false {
				printString("Alexa Domain Info", resp.AlexaDomainInfo)
				printString("BitDefender Category", resp.BitDefenderCategory)
				printStringSlice("Categories", resp.Categories)
				printDetectedURLs("Detected URLs", resp.DetectedURLs)
				printStringSlice("Domain Siblings", resp.DomainSiblings)
				printResolutions("Resolutions", resp.Resolutions)
				printResponseCode("Response Code", resp.ResponseCode)
				printStringSlice("Subdomains", resp.Subdomains)
				printString("TrendMicro Category", resp.TrendMicroCategory)
				printDownloadSamples("Undetected Download Samples", resp.UndetectedDownloadedSamples)
				printReferrerSamples("Undetected Referrer Samples", resp.UndetectedReferrerSamples)
				printString("Verbose Message", resp.VerboseMsg)
				printWOT("Web of Trust Domain Information", resp.WOTDomainInfo)
				printString("Websense ThreatSeeker Category", resp.WebsenseThreatSeekerCategory)
				printStringSlice("Whois", strings.Split(resp.Whois, "\n"))
			}
		}
	},
}

func printString(title, value string) {
	if value != "" {
		fmt.Println(title + ":")
		fmt.Printf("\t%s\n", value)
	}
}

func printResponseCode(title string, code int) {
	var meaning string
	switch code {
	case 1:
		meaning = "Present and Retrieved"
	case 0:
		meaning = "Not Found"
	case -1:
		meaning = "Unexpected Error"
	case -2:
		meaning = "Queued for Analysis"
	default:
		meaning = "Unknown"
	}
	fmt.Printf("%s:\t%d (%s)\n", title, code, meaning)
}

func printStringSlice(title string, values []string) {
	if len(values) > 0 {
		fmt.Println(title + ":")
		for _, value := range values {
			fmt.Printf("\t%s\n", strings.TrimSpace(value))
		}
	}
}

func printDetectedURLs(title string, urls []DetectedURL) {
	if len(urls) > 0 {
		fmt.Println(title + ":")
		for _, url := range urls {
			fmt.Printf("\tPositives:\t%d\n", url.Positives)
			fmt.Printf("\tScan Date:\t%s\n", url.ScanDate)
			fmt.Printf("\tTotal:\t\t%d\n", url.Total)
			fmt.Printf("\tURL:\t\t%s\n", url.URL)
		}
	}
}

func printWOT(title string, wot WebOfTrustInfo) {
	if (WebOfTrustInfo{}) != wot {
		fmt.Println(title + ":")
		fmt.Printf("\tChild Safety:\t\t%s\n", wot.ChildSafety)
		fmt.Printf("\tPrivacy:\t\t%s\n", wot.Privacy)
		fmt.Printf("\tTrustworthiness:\t%s\n", wot.Trustworthiness)
		fmt.Printf("\tVendor Reliability:\t%s\n", wot.VendorReliability)
	}
}

func printResolutions(title string, resolutions []Resolution) {
	if resolutions != nil {
		fmt.Println(title + ":")
		for _, resolution := range resolutions {
			fmt.Printf("\tIP Address:\t\t%s\n", resolution.IPAddress)
			fmt.Printf("\tLast Resolved:\t\t%s\n", resolution.LastResolved)
		}
	}
}

func printDownloadSamples(title string, downloadSamples []UndetectedDownloadSample) {
	if downloadSamples != nil {
		fmt.Println(title + ":")
		for _, sample := range downloadSamples {
			fmt.Printf("\tDate:\t%s\n", sample.Date)
			fmt.Printf("\tPositives:\t%d\n", sample.Positives)
			fmt.Printf("\tSHA-256:\t%s\n", sample.Sha256)
			fmt.Printf("\tTotal:\t%d\n", sample.Total)
		}
	}
}

func printReferrerSamples(title string, referrerSamples []UndetectedReferrerSample) {
	if referrerSamples != nil {
		fmt.Println(title + ":")
		for _, sample := range referrerSamples {
			fmt.Printf("\tPositives:\t%d\n", sample.Positives)
			fmt.Printf("\tSHA-256:\t%s\n", sample.Sha256)
			fmt.Printf("\tTotal:\t%d\n", sample.Total)
		}
	}
}

func retrieveDomainInformation() (responses []*DomainReportResult) {
	vtURL := "https://www.virustotal.com/vtapi/v2/domain/report"

	client := &http.Client{}

	for _, domain := range domains {
		req, err := http.NewRequest("GET", vtURL, nil)
		if err != nil {
			logrus.WithError(err).Errorln("Failed to generate new request.")
			continue
		}

		q := req.URL.Query()
		q.Add("apikey", apiKey)
		q.Add("domain", strings.TrimSpace(domain))
		req.URL.RawQuery = q.Encode()

		resp, err := client.Do(req)
		if err != nil {
			logrus.WithError(err).Fatal("Received error while retrieving report.")
			continue
		}
		defer resp.Body.Close()

		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			logrus.WithError(err).Fatal("Received error while reading response body.")
			continue
		}

		var domResp DomainReportResult
		err = json.Unmarshal(body, &domResp)
		if err != nil {
			logrus.WithError(err).Errorln("Failed to parse VirusTotal response")
			continue
		}

		domResp.JSON = string(body)

		responses = append(responses, &domResp)
	}

	return responses
}

func init() {
	RootCmd.AddCommand(domainCmd)

	domainCmd.PersistentFlags().StringSliceVarP(&domains, "domain", "d", []string{}, "Provide the domain which you would like information about.")
	domainCmd.Flags().BoolVarP(&outputJSON, "output-json", "j", false, "Output JSON instead of human readable.")
	domainCmd.Flags().BoolVarP(&ouptutHuman, "output-human", "u", false, "Output in human readable format.")
}
<commit_msg>Add Environment variable version of API key<commit_after>// Copyright © 2016 Kevin Kirsche <kev.kirsche@gmail.com>
//
// 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 cmd

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"strings"

	"github.com/Sirupsen/logrus"
	"github.com/spf13/cobra"
)

var (
	domains []string

	outputJSON  bool
	ouptutHuman bool

	grepable bool
)

// DetectedURL is a sub-struct of DomainReportResult
type DetectedURL struct {
	Positives int    `json:"positives"`
	ScanDate  string `json:"scan_date"`
	Total     int    `json:"total"`
	URL       string `json:"url"`
}

// UndetectedDownloadSample is a sub-struct of DomainReportResult
type UndetectedDownloadSample struct {
	Date      string `json:"date"`
	Positives int    `json:"positives"`
	Sha256    string `json:"sha256"`
	Total     int    `json:"total"`
}

// UndetectedReferrerSample is a sub-struct of DomainReportResult
type UndetectedReferrerSample struct {
	Positives int    `json:"positives"`
	Sha256    string `json:"sha256"`
	Total     int    `json:"total"`
}

// Resolution is a sub-struct of DomainReportResult
type Resolution struct {
	IPAddress    string `json:"ip_address"`
	LastResolved string `json:"last_resolved"`
}

// WebutationInfo is a sub-struct of DomainReportResult
type WebutationInfo struct {
	AdultContent string `json:"Adult content"`
	SafetyScore  int    `json:"Safety score"`
	Verdict      string `json:"Verdict"`
}

// WebOfTrustInfo is a sub-struct of DomainReportResult
type WebOfTrustInfo struct {
	ChildSafety       string `json:"Child safety"`
	Privacy           string `json:"Privacy"`
	Trustworthiness   string `json:"Trustworthiness"`
	VendorReliability string `json:"Vendor reliability"`
}

// DomainReportResult is the result that is received after requesting a
// domain report from VirusTotal's public API.
type DomainReportResult struct {
	AlexaDomainInfo              string         `json:"Alexa domain info"`
	BitDefenderCategory          string         `json:"BitDefender category"`
	TrendMicroCategory           string         `json:"TrendMicro category"`
	WOTDomainInfo                WebOfTrustInfo `json:"WOT domain info"`
	WebsenseThreatSeekerCategory string         `json:"Websense ThreatSeeker category"`
	WebutationDomainInfo         WebutationInfo `json:"Webutation domain info"`
	Categories                   []string       `json:"categories"`
	JSON                         string
	DetectedURLs                 []DetectedURL              `json:"detected_urls"`
	DomainSiblings               []string                   `json:"domain_siblings"`
	Resolutions                  []Resolution               `json:"resolutions"`
	ResponseCode                 int                        `json:"response_code"`
	Subdomains                   []string                   `json:"subdomains"`
	UndetectedDownloadedSamples  []UndetectedDownloadSample `json:"undetected_downloaded_samples"`
	UndetectedReferrerSamples    []UndetectedReferrerSample `json:"undetected_referrer_samples"`
	VerboseMsg                   string                     `json:"verbose_msg"`
	Whois                        string                     `json:"whois"`
	WhoisTimestamp               float64                    `json:"whois_timestamp"`
}

// domainCmd represents the scanurl command
var domainCmd = &cobra.Command{
	Use:   "domain",
	Short: "Retrieve informaiton about a domain",
	Long: `Retrieve all information a domain in human readable or JSON format.

Example:

virustotal domain -a {{ api_key }} -d {{ domain(s) }}
`,
	Run: func(cmd *cobra.Command, args []string) {
		responses := retrieveDomainInformation()

		for _, resp := range responses {
			if outputJSON {
				fmt.Println(resp.JSON)
			}

			if ouptutHuman || outputJSON == false {
				printString("Alexa Domain Info", resp.AlexaDomainInfo)
				printString("BitDefender Category", resp.BitDefenderCategory)
				printStringSlice("Categories", resp.Categories)
				printDetectedURLs("Detected URLs", resp.DetectedURLs)
				printStringSlice("Domain Siblings", resp.DomainSiblings)
				printResolutions("Resolutions", resp.Resolutions)
				printResponseCode("Response Code", resp.ResponseCode)
				printStringSlice("Subdomains", resp.Subdomains)
				printString("TrendMicro Category", resp.TrendMicroCategory)
				printDownloadSamples("Undetected Download Samples", resp.UndetectedDownloadedSamples)
				printReferrerSamples("Undetected Referrer Samples", resp.UndetectedReferrerSamples)
				printString("Verbose Message", resp.VerboseMsg)
				printWOT("Web of Trust Domain Information", resp.WOTDomainInfo)
				printString("Websense ThreatSeeker Category", resp.WebsenseThreatSeekerCategory)
				printStringSlice("Whois", strings.Split(resp.Whois, "\n"))
			}
		}
	},
}

func printString(title, value string) {
	if value != "" {
		fmt.Println(title + ":")
		fmt.Printf("\t%s\n", value)
	}
}

func printResponseCode(title string, code int) {
	var meaning string
	switch code {
	case 1:
		meaning = "Present and Retrieved"
	case 0:
		meaning = "Not Found"
	case -1:
		meaning = "Unexpected Error"
	case -2:
		meaning = "Queued for Analysis"
	default:
		meaning = "Unknown"
	}
	fmt.Printf("%s:\t%d (%s)\n", title, code, meaning)
}

func printStringSlice(title string, values []string) {
	if len(values) > 0 {
		fmt.Println(title + ":")
		for _, value := range values {
			fmt.Printf("\t%s\n", strings.TrimSpace(value))
		}
	}
}

func printDetectedURLs(title string, urls []DetectedURL) {
	if len(urls) > 0 {
		fmt.Println(title + ":")
		for _, url := range urls {
			fmt.Printf("\tPositives:\t%d\n", url.Positives)
			fmt.Printf("\tScan Date:\t%s\n", url.ScanDate)
			fmt.Printf("\tTotal:\t\t%d\n", url.Total)
			fmt.Printf("\tURL:\t\t%s\n", url.URL)
		}
	}
}

func printWOT(title string, wot WebOfTrustInfo) {
	if (WebOfTrustInfo{}) != wot {
		fmt.Println(title + ":")
		fmt.Printf("\tChild Safety:\t\t%s\n", wot.ChildSafety)
		fmt.Printf("\tPrivacy:\t\t%s\n", wot.Privacy)
		fmt.Printf("\tTrustworthiness:\t%s\n", wot.Trustworthiness)
		fmt.Printf("\tVendor Reliability:\t%s\n", wot.VendorReliability)
	}
}

func printResolutions(title string, resolutions []Resolution) {
	if resolutions != nil {
		fmt.Println(title + ":")
		for _, resolution := range resolutions {
			fmt.Printf("\tIP Address:\t\t%s\n", resolution.IPAddress)
			fmt.Printf("\tLast Resolved:\t\t%s\n", resolution.LastResolved)
		}
	}
}

func printDownloadSamples(title string, downloadSamples []UndetectedDownloadSample) {
	if downloadSamples != nil {
		fmt.Println(title + ":")
		for _, sample := range downloadSamples {
			fmt.Printf("\tDate:\t%s\n", sample.Date)
			fmt.Printf("\tPositives:\t%d\n", sample.Positives)
			fmt.Printf("\tSHA-256:\t%s\n", sample.Sha256)
			fmt.Printf("\tTotal:\t%d\n", sample.Total)
		}
	}
}

func printReferrerSamples(title string, referrerSamples []UndetectedReferrerSample) {
	if referrerSamples != nil {
		fmt.Println(title + ":")
		for _, sample := range referrerSamples {
			fmt.Printf("\tPositives:\t%d\n", sample.Positives)
			fmt.Printf("\tSHA-256:\t%s\n", sample.Sha256)
			fmt.Printf("\tTotal:\t%d\n", sample.Total)
		}
	}
}

func retrieveDomainInformation() (responses []*DomainReportResult) {
	vtURL := "https://www.virustotal.com/vtapi/v2/domain/report"

	client := &http.Client{}

	for _, domain := range domains {
		req, err := http.NewRequest("GET", vtURL, nil)
		if err != nil {
			logrus.WithError(err).Errorln("Failed to generate new request.")
			continue
		}

		q := req.URL.Query()
		q.Add("apikey", apiKey)
		if apiKey == "" {
			apiKey = os.Getenv("virustotal_api_key")
			if apiKey == "" {
				apiKey = os.Getenv("VIRUSTOTAL_API_KEY")
			}
			if apiKey == "" {
				apiKey = os.Getenv("Virustotal_Api_Key")
			}
		}
		q.Add("domain", strings.TrimSpace(domain))
		req.URL.RawQuery = q.Encode()

		resp, err := client.Do(req)
		if err != nil {
			logrus.WithError(err).Fatal("Received error while retrieving report.")
			continue
		}
		defer resp.Body.Close()

		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			logrus.WithError(err).Fatal("Received error while reading response body.")
			continue
		}

		var domResp DomainReportResult
		err = json.Unmarshal(body, &domResp)
		if err != nil {
			logrus.WithError(err).Errorln("Failed to parse VirusTotal response")
			continue
		}

		domResp.JSON = string(body)

		responses = append(responses, &domResp)
	}

	return responses
}

func init() {
	RootCmd.AddCommand(domainCmd)

	domainCmd.PersistentFlags().StringSliceVarP(&domains, "domain", "d", []string{}, "Provide the domain which you would like information about.")
	domainCmd.Flags().BoolVarP(&outputJSON, "output-json", "j", false, "Output JSON instead of human readable.")
	domainCmd.Flags().BoolVarP(&ouptutHuman, "output-human", "u", false, "Output in human readable format.")
}
<|endoftext|>


<commit_before>package cmd

import (
	"encoding/base64"
	"fmt"
	"io/ioutil"
	"os"
	"strings"

	"github.com/AlecAivazis/survey/v2"
	"github.com/google/uuid"
	"github.com/ory/viper"
	"github.com/spf13/cobra"

	fn "knative.dev/kn-plugin-func"
	"knative.dev/kn-plugin-func/utils"
)

func NewInvokeCmd(newClient ClientFactory) *cobra.Command {
	cmd := &cobra.Command{
		Use:   "invoke",
		Short: "Invoke a Function",
		Long: `
NAME
	{{.Name}} invoke - Invoke a Function.

SYNOPSIS
	{{.Name}} invoke [-t|--target] [-f|--format]
	             [--id] [--source] [--type] [--data] [--file] [--content-type]
	             [-s|--save] [-p|--path] [-c|--confirm] [-v|--verbose]

DESCRIPTION
	Invokes the Function by sending a test request to the currently running
	Function instance, either locally or remote.  If the Function is running
	both locally and remote, the local instance will be invoked.  This behavior
	can be manually overridden using the --target flag.

	Functions are invoked with a test data structure consisting of five values:
		id:            A unique identifier for the request.
		source:        A sender name for the request (sender).
		type:          A type for the request.
		data:          Data (content) for this request.
		content-type:  The MIME type of the value contained in 'data'.

	The values of these parameters can be individually altered from their defaults
	using their associated flags. Data can also be provided from a file using the
	--file flag.

	Invocation Target
	  The Function instance to invoke can be specified using the --target flag
	  which accepts the values "local", "remote", or <URL>.  By default the
	  local Function instance is chosen if running (see {{.Name}} run).
	  To explicitly target the remote (deployed) Function:
	    {{.Name}} invoke --target=remote
	  To target an arbitrary endpoint, provide a URL:
	    {{.Name}} invoke --target=https://myfunction.example.com

	Invocation Data
	  Providing a filename in the --file flag will base64 encode its contents
	  as the "data" parameter sent to the Function.  The value of --content-type
	  should be set to the type from the source file.  For example, the following
	  would send a JPEG base64 encoded in the "data" POST parameter:
	    {{.Name}} invoke --file=example.jpeg --content-type=image/jpeg

	Message Format
	  By default Functions are sent messages which match the invocation format
	  of the template they were created using; for example "http" or "cloudevent".
	  To override this behavior, use the --format (-f) flag.
	    {{.Name}} invoke -f=cloudevent -t=http://my-sink.my-cluster

EXAMPLES

	o Invoke the default (local or remote) running Function with default values
	  $ {{.Name}} invoke

	o Run the Function locally and then invoke it with a test request:
	  (run in two terminals or by running the first in the background)
	  $ {{.Name}} run
	  $ {{.Name}} invoke

	o Deploy and then invoke the remote Function:
	  $ {{.Name}} deploy
	  $ {{.Name}} invoke

	o Invoke a remote (deployed) Function when it is already running locally:
	  (overrides the default behavior of preferring locally running instances)
	  $ {{.Name}} invoke --target=remote

	o Specify the data to send to the Function as a flag
	  $ {{.Name}} invoke --data="Hello World!"

	o Send a JPEG to the Function
	  $ {{.Name}} invoke --file=example.jpeg --content-type=image/jpeg

	o Invoke an arbitrary endpoint (HTTP POST)
		$ {{.Name}} invoke --target="https://my-http-handler.example.com"

	o Invoke an arbitrary endpoint (CloudEvent)
		$ {{.Name}} invoke -f=cloudevent -t="https://my-event-broker.example.com"

`,
		SuggestFor: []string{"emit", "emti", "send", "emit", "exec", "nivoke", "onvoke", "unvoke", "knvoke", "imvoke", "ihvoke", "ibvoke"},
		PreRunE:    bindEnv("path", "format", "target", "id", "source", "type", "data", "content-type", "file", "confirm"),
	}

	// Flags
	cmd.Flags().StringP("path", "p", cwd(), "Path to the Function which should have its instance invoked. (Env: $FUNC_PATH)")
	cmd.Flags().StringP("format", "f", "", "Format of message to send, 'http' or 'cloudevent'.  Default is to choose automatically. (Env: $FUNC_FORMAT)")
	cmd.Flags().StringP("target", "t", "", "Function instance to invoke.  Can be 'local', 'remote' or a URL.  Defaults to auto-discovery if not provided. (Env: $FUNC_TARGET)")
	cmd.Flags().StringP("id", "", uuid.NewString(), "ID for the request data. (Env: $FUNC_ID)")
	cmd.Flags().StringP("source", "", fn.DefaultInvokeSource, "Source value for the request data. (Env: $FUNC_SOURCE)")
	cmd.Flags().StringP("type", "", fn.DefaultInvokeType, "Type value for the request data. (Env: $FUNC_TYPE)")
	cmd.Flags().StringP("content-type", "", fn.DefaultInvokeContentType, "Content Type of the data. (Env: $FUNC_CONTENT_TYPE)")
	cmd.Flags().StringP("data", "", fn.DefaultInvokeData, "Data to send in the request. (Env: $FUNC_DATA)")
	cmd.Flags().StringP("file", "", "", "Path to a file to use as data. Overrides --data flag and should be sent with a correct --content-type. (Env: $FUNC_FILE)")
	cmd.Flags().BoolP("confirm", "c", false, "Prompt to confirm all options interactively. (Env: $FUNC_CONFIRM)")

	cmd.SetHelpFunc(defaultTemplatedHelp)

	cmd.RunE = func(cmd *cobra.Command, args []string) error {
		return runInvoke(cmd, args, newClient)
	}

	return cmd
}

// Run
func runInvoke(cmd *cobra.Command, args []string, newClient ClientFactory) (err error) {
	// Gather flag values for the invocation
	cfg, err := newInvokeConfig(newClient)
	if err != nil {
		return
	}

	// Client instance from env vars, flags, args and user prompts (if --confirm)
	client, done := newClient(ClientConfig{Namespace: cfg.Namespace, Verbose: cfg.Verbose})
	defer done()

	// Message to send the running Function built from parameters gathered
	// from the user (or defaults)
	m := fn.InvokeMessage{
		ID:          cfg.ID,
		Source:      cfg.Source,
		Type:        cfg.Type,
		ContentType: cfg.ContentType,
		Data:        cfg.Data,
		Format:      cfg.Format,
	}

	// If --file was specified, use its content for message data
	if cfg.File != "" {
		content, err := os.ReadFile(cfg.File)
		if err != nil {
			return err
		}
		m.Data = base64.StdEncoding.EncodeToString(content)
	}

	// Invoke
	metadata, body, err := client.Invoke(cmd.Context(), cfg.Path, cfg.Target, m)
	if err != nil {
		return err
	}

	// Always print a "Received response" message because a simple echo to
	// stdout could be confusing on a first-time run, viewing a proper echo.
	fmt.Println("Received response")

	// When Verbose
	// - Print an explicit "Received response" indicator
	// - Print metadata (headers for HTTP requests, CloudEvents already include
	//   metadata in their data value.
	if cfg.Verbose {
		if len(metadata) > 0 {
			fmt.Println("Metadata:")
		}
		for k, vv := range metadata {
			values := strings.Join(vv, ";")
			fmt.Fprintf(cmd.OutOrStdout(), "  %v: %v\n", k, values)
		}
		if len(metadata) > 0 {
			fmt.Println("Content:")
		}
	}

	// Always print the response's default stringification
	// Note body already includes a linebreak.
	fmt.Fprint(cmd.OutOrStdout(), body)
	return
}

type invokeConfig struct {
	Path        string
	Target      string
	Format      string
	ID          string
	Source      string
	Type        string
	Data        string
	ContentType string
	File        string
	Namespace   string
	Confirm     bool
	Verbose     bool
}

func newInvokeConfig(newClient ClientFactory) (cfg invokeConfig, err error) {
	cfg = invokeConfig{
		Path:        viper.GetString("path"),
		Target:      viper.GetString("target"),
		ID:          viper.GetString("id"),
		Source:      viper.GetString("source"),
		Type:        viper.GetString("type"),
		Data:        viper.GetString("data"),
		ContentType: viper.GetString("content-type"),
		File:        viper.GetString("file"),
		Confirm:     viper.GetBool("confirm"),
		Verbose:     viper.GetBool("verbose"),
		Namespace:   viper.GetString("namespace"),
	}

	// If file was passed, read it in as data
	if cfg.File != "" {
		b, err := ioutil.ReadFile(cfg.File)
		if err != nil {
			return cfg, err
		}
		cfg.Data = string(b)
	}

	// if not in confirm/prompting mode, the cfg structure is complete.
	if !cfg.Confirm {
		return
	}

	// Client instance for use during prompting.
	client, done := newClient(ClientConfig{Namespace: cfg.Namespace, Verbose: cfg.Verbose})
	defer done()

	// If in interactive terminal mode, prompt to modify defaults.
	if interactiveTerminal() {
		return cfg.prompt(client)
	}

	// Confirming, but noninteractive, is essentially a selective verbose mode
	// which prints out the effective values of config as a confirmation.
	fmt.Printf("Path: %v\n", cfg.Path)
	fmt.Printf("Target: %v\n", cfg.Target)
	fmt.Printf("ID: %v\n", cfg.ID)
	fmt.Printf("Source: %v\n", cfg.Source)
	fmt.Printf("Type: %v\n", cfg.Type)
	fmt.Printf("Data: %v\n", cfg.Data)
	fmt.Printf("Content Type: %v\n", cfg.ContentType)
	fmt.Printf("File: %v\n", cfg.File)
	return
}

func (c invokeConfig) prompt(client *fn.Client) (invokeConfig, error) {
	var qs []*survey.Question

	// First get path to effective Function
	qs = []*survey.Question{
		{
			Name: "Path",
			Prompt: &survey.Input{
				Message: "Function Path:",
				Default: c.Path,
			},
			Validate: func(val interface{}) error {
				if val.(string) != "" {
					derivedName, _ := deriveNameAndAbsolutePathFromPath(val.(string))
					return utils.ValidateFunctionName(derivedName)
				}
				return nil
			},
			Transform: func(ans interface{}) interface{} {
				if ans.(string) != "" {
					_, absolutePath := deriveNameAndAbsolutePathFromPath(ans.(string))
					return absolutePath
				}
				return ""
			},
		},
	}
	if err := survey.Ask(qs, &c); err != nil {
		return c, err
	}
	formatOptions := []string{"", "http", "cloudevent"}
	qs = []*survey.Question{
		{
			Name: "Target",
			Prompt: &survey.Input{
				Message: "(Optional) Target ('local', 'remote' or URL).  If not provided, local will be preferred over remote.",
				Default: "",
			},
		},
		{
			Name: "Format",
			Prompt: &survey.Select{
				Message: "(Optional) Format Override",
				Options: formatOptions,
				Default: surveySelectDefault(c.Format, formatOptions),
			},
		},
	}
	if err := survey.Ask(qs, &c); err != nil {
		return c, err
	}

	// Prompt for the next set of values, with defaults set first by the Function
	// as it exists on disk, followed by environment variables, and finally flags.
	// user interactive prompts therefore are the last applied, and thus highest
	// precidence values.
	qs = []*survey.Question{
		{
			Name: "ID",
			Prompt: &survey.Input{
				Message: "Data ID",
				Default: c.ID,
			},
		}, {
			Name: "Source",
			Prompt: &survey.Input{
				Message: "Data Source",
				Default: c.Source,
			},
		}, {
			Name: "Type",
			Prompt: &survey.Input{
				Message: "Data Type",
				Default: c.Type,
			},
		}, {
			Name: "File",
			Prompt: &survey.Input{
				Message: "(Optional) Load Data Content from File",
				Default: c.File,
			},
		},
	}
	if err := survey.Ask(qs, &c); err != nil {
		return c, err
	}

	// If the user did not specify a file for data content, prompt for it
	if c.File == "" {
		qs = []*survey.Question{
			{
				Name: "Data",
				Prompt: &survey.Input{
					Message: "Data Content",
					Default: c.Data,
				},
			},
		}
		if err := survey.Ask(qs, &c); err != nil {
			return c, err
		}
	}

	// Finally, allow mutation of the data content type.
	contentTypeMessage := "Content type of data"
	if c.File != "" {
		contentTypeMessage = "Content type of file"
	}
	qs = []*survey.Question{
		{
			Name: "ContentType",
			Prompt: &survey.Input{
				Message: contentTypeMessage,
				Default: c.ContentType,
			},
		}}
	if err := survey.Ask(qs, &c); err != nil {
		return c, err
	}

	return c, nil
}
<commit_msg>fix: map invoke format flag (#1041)<commit_after>package cmd

import (
	"encoding/base64"
	"fmt"
	"io/ioutil"
	"os"
	"strings"

	"github.com/AlecAivazis/survey/v2"
	"github.com/google/uuid"
	"github.com/ory/viper"
	"github.com/spf13/cobra"

	fn "knative.dev/kn-plugin-func"
	"knative.dev/kn-plugin-func/utils"
)

func NewInvokeCmd(newClient ClientFactory) *cobra.Command {
	cmd := &cobra.Command{
		Use:   "invoke",
		Short: "Invoke a Function",
		Long: `
NAME
	{{.Name}} invoke - Invoke a Function.

SYNOPSIS
	{{.Name}} invoke [-t|--target] [-f|--format]
	             [--id] [--source] [--type] [--data] [--file] [--content-type]
	             [-s|--save] [-p|--path] [-c|--confirm] [-v|--verbose]

DESCRIPTION
	Invokes the Function by sending a test request to the currently running
	Function instance, either locally or remote.  If the Function is running
	both locally and remote, the local instance will be invoked.  This behavior
	can be manually overridden using the --target flag.

	Functions are invoked with a test data structure consisting of five values:
		id:            A unique identifier for the request.
		source:        A sender name for the request (sender).
		type:          A type for the request.
		data:          Data (content) for this request.
		content-type:  The MIME type of the value contained in 'data'.

	The values of these parameters can be individually altered from their defaults
	using their associated flags. Data can also be provided from a file using the
	--file flag.

	Invocation Target
	  The Function instance to invoke can be specified using the --target flag
	  which accepts the values "local", "remote", or <URL>.  By default the
	  local Function instance is chosen if running (see {{.Name}} run).
	  To explicitly target the remote (deployed) Function:
	    {{.Name}} invoke --target=remote
	  To target an arbitrary endpoint, provide a URL:
	    {{.Name}} invoke --target=https://myfunction.example.com

	Invocation Data
	  Providing a filename in the --file flag will base64 encode its contents
	  as the "data" parameter sent to the Function.  The value of --content-type
	  should be set to the type from the source file.  For example, the following
	  would send a JPEG base64 encoded in the "data" POST parameter:
	    {{.Name}} invoke --file=example.jpeg --content-type=image/jpeg

	Message Format
	  By default Functions are sent messages which match the invocation format
	  of the template they were created using; for example "http" or "cloudevent".
	  To override this behavior, use the --format (-f) flag.
	    {{.Name}} invoke -f=cloudevent -t=http://my-sink.my-cluster

EXAMPLES

	o Invoke the default (local or remote) running Function with default values
	  $ {{.Name}} invoke

	o Run the Function locally and then invoke it with a test request:
	  (run in two terminals or by running the first in the background)
	  $ {{.Name}} run
	  $ {{.Name}} invoke

	o Deploy and then invoke the remote Function:
	  $ {{.Name}} deploy
	  $ {{.Name}} invoke

	o Invoke a remote (deployed) Function when it is already running locally:
	  (overrides the default behavior of preferring locally running instances)
	  $ {{.Name}} invoke --target=remote

	o Specify the data to send to the Function as a flag
	  $ {{.Name}} invoke --data="Hello World!"

	o Send a JPEG to the Function
	  $ {{.Name}} invoke --file=example.jpeg --content-type=image/jpeg

	o Invoke an arbitrary endpoint (HTTP POST)
		$ {{.Name}} invoke --target="https://my-http-handler.example.com"

	o Invoke an arbitrary endpoint (CloudEvent)
		$ {{.Name}} invoke -f=cloudevent -t="https://my-event-broker.example.com"

`,
		SuggestFor: []string{"emit", "emti", "send", "emit", "exec", "nivoke", "onvoke", "unvoke", "knvoke", "imvoke", "ihvoke", "ibvoke"},
		PreRunE:    bindEnv("path", "format", "target", "id", "source", "type", "data", "content-type", "file", "confirm"),
	}

	// Flags
	cmd.Flags().StringP("path", "p", cwd(), "Path to the Function which should have its instance invoked. (Env: $FUNC_PATH)")
	cmd.Flags().StringP("format", "f", "", "Format of message to send, 'http' or 'cloudevent'.  Default is to choose automatically. (Env: $FUNC_FORMAT)")
	cmd.Flags().StringP("target", "t", "", "Function instance to invoke.  Can be 'local', 'remote' or a URL.  Defaults to auto-discovery if not provided. (Env: $FUNC_TARGET)")
	cmd.Flags().StringP("id", "", uuid.NewString(), "ID for the request data. (Env: $FUNC_ID)")
	cmd.Flags().StringP("source", "", fn.DefaultInvokeSource, "Source value for the request data. (Env: $FUNC_SOURCE)")
	cmd.Flags().StringP("type", "", fn.DefaultInvokeType, "Type value for the request data. (Env: $FUNC_TYPE)")
	cmd.Flags().StringP("content-type", "", fn.DefaultInvokeContentType, "Content Type of the data. (Env: $FUNC_CONTENT_TYPE)")
	cmd.Flags().StringP("data", "", fn.DefaultInvokeData, "Data to send in the request. (Env: $FUNC_DATA)")
	cmd.Flags().StringP("file", "", "", "Path to a file to use as data. Overrides --data flag and should be sent with a correct --content-type. (Env: $FUNC_FILE)")
	cmd.Flags().BoolP("confirm", "c", false, "Prompt to confirm all options interactively. (Env: $FUNC_CONFIRM)")

	cmd.SetHelpFunc(defaultTemplatedHelp)

	cmd.RunE = func(cmd *cobra.Command, args []string) error {
		return runInvoke(cmd, args, newClient)
	}

	return cmd
}

// Run
func runInvoke(cmd *cobra.Command, args []string, newClient ClientFactory) (err error) {
	// Gather flag values for the invocation
	cfg, err := newInvokeConfig(newClient)
	if err != nil {
		return
	}

	// Client instance from env vars, flags, args and user prompts (if --confirm)
	client, done := newClient(ClientConfig{Namespace: cfg.Namespace, Verbose: cfg.Verbose})
	defer done()

	// Message to send the running Function built from parameters gathered
	// from the user (or defaults)
	m := fn.InvokeMessage{
		ID:          cfg.ID,
		Source:      cfg.Source,
		Type:        cfg.Type,
		ContentType: cfg.ContentType,
		Data:        cfg.Data,
		Format:      cfg.Format,
	}

	// If --file was specified, use its content for message data
	if cfg.File != "" {
		content, err := os.ReadFile(cfg.File)
		if err != nil {
			return err
		}
		m.Data = base64.StdEncoding.EncodeToString(content)
	}

	// Invoke
	metadata, body, err := client.Invoke(cmd.Context(), cfg.Path, cfg.Target, m)
	if err != nil {
		return err
	}

	// Always print a "Received response" message because a simple echo to
	// stdout could be confusing on a first-time run, viewing a proper echo.
	fmt.Println("Received response")

	// When Verbose
	// - Print an explicit "Received response" indicator
	// - Print metadata (headers for HTTP requests, CloudEvents already include
	//   metadata in their data value.
	if cfg.Verbose {
		if len(metadata) > 0 {
			fmt.Println("Metadata:")
		}
		for k, vv := range metadata {
			values := strings.Join(vv, ";")
			fmt.Fprintf(cmd.OutOrStdout(), "  %v: %v\n", k, values)
		}
		if len(metadata) > 0 {
			fmt.Println("Content:")
		}
	}

	// Always print the response's default stringification
	// Note body already includes a linebreak.
	fmt.Fprint(cmd.OutOrStdout(), body)
	return
}

type invokeConfig struct {
	Path        string
	Target      string
	Format      string
	ID          string
	Source      string
	Type        string
	Data        string
	ContentType string
	File        string
	Namespace   string
	Confirm     bool
	Verbose     bool
}

func newInvokeConfig(newClient ClientFactory) (cfg invokeConfig, err error) {
	cfg = invokeConfig{
		Path:        viper.GetString("path"),
		Target:      viper.GetString("target"),
		Format:      viper.GetString("format"),
		ID:          viper.GetString("id"),
		Source:      viper.GetString("source"),
		Type:        viper.GetString("type"),
		Data:        viper.GetString("data"),
		ContentType: viper.GetString("content-type"),
		File:        viper.GetString("file"),
		Confirm:     viper.GetBool("confirm"),
		Verbose:     viper.GetBool("verbose"),
		Namespace:   viper.GetString("namespace"),
	}

	// If file was passed, read it in as data
	if cfg.File != "" {
		b, err := ioutil.ReadFile(cfg.File)
		if err != nil {
			return cfg, err
		}
		cfg.Data = string(b)
	}

	// if not in confirm/prompting mode, the cfg structure is complete.
	if !cfg.Confirm {
		return
	}

	// Client instance for use during prompting.
	client, done := newClient(ClientConfig{Namespace: cfg.Namespace, Verbose: cfg.Verbose})
	defer done()

	// If in interactive terminal mode, prompt to modify defaults.
	if interactiveTerminal() {
		return cfg.prompt(client)
	}

	// Confirming, but noninteractive, is essentially a selective verbose mode
	// which prints out the effective values of config as a confirmation.
	fmt.Printf("Path: %v\n", cfg.Path)
	fmt.Printf("Target: %v\n", cfg.Target)
	fmt.Printf("ID: %v\n", cfg.ID)
	fmt.Printf("Source: %v\n", cfg.Source)
	fmt.Printf("Type: %v\n", cfg.Type)
	fmt.Printf("Data: %v\n", cfg.Data)
	fmt.Printf("Content Type: %v\n", cfg.ContentType)
	fmt.Printf("File: %v\n", cfg.File)
	return
}

func (c invokeConfig) prompt(client *fn.Client) (invokeConfig, error) {
	var qs []*survey.Question

	// First get path to effective Function
	qs = []*survey.Question{
		{
			Name: "Path",
			Prompt: &survey.Input{
				Message: "Function Path:",
				Default: c.Path,
			},
			Validate: func(val interface{}) error {
				if val.(string) != "" {
					derivedName, _ := deriveNameAndAbsolutePathFromPath(val.(string))
					return utils.ValidateFunctionName(derivedName)
				}
				return nil
			},
			Transform: func(ans interface{}) interface{} {
				if ans.(string) != "" {
					_, absolutePath := deriveNameAndAbsolutePathFromPath(ans.(string))
					return absolutePath
				}
				return ""
			},
		},
	}
	if err := survey.Ask(qs, &c); err != nil {
		return c, err
	}
	formatOptions := []string{"", "http", "cloudevent"}
	qs = []*survey.Question{
		{
			Name: "Target",
			Prompt: &survey.Input{
				Message: "(Optional) Target ('local', 'remote' or URL).  If not provided, local will be preferred over remote.",
				Default: "",
			},
		},
		{
			Name: "Format",
			Prompt: &survey.Select{
				Message: "(Optional) Format Override",
				Options: formatOptions,
				Default: surveySelectDefault(c.Format, formatOptions),
			},
		},
	}
	if err := survey.Ask(qs, &c); err != nil {
		return c, err
	}

	// Prompt for the next set of values, with defaults set first by the Function
	// as it exists on disk, followed by environment variables, and finally flags.
	// user interactive prompts therefore are the last applied, and thus highest
	// precidence values.
	qs = []*survey.Question{
		{
			Name: "ID",
			Prompt: &survey.Input{
				Message: "Data ID",
				Default: c.ID,
			},
		}, {
			Name: "Source",
			Prompt: &survey.Input{
				Message: "Data Source",
				Default: c.Source,
			},
		}, {
			Name: "Type",
			Prompt: &survey.Input{
				Message: "Data Type",
				Default: c.Type,
			},
		}, {
			Name: "File",
			Prompt: &survey.Input{
				Message: "(Optional) Load Data Content from File",
				Default: c.File,
			},
		},
	}
	if err := survey.Ask(qs, &c); err != nil {
		return c, err
	}

	// If the user did not specify a file for data content, prompt for it
	if c.File == "" {
		qs = []*survey.Question{
			{
				Name: "Data",
				Prompt: &survey.Input{
					Message: "Data Content",
					Default: c.Data,
				},
			},
		}
		if err := survey.Ask(qs, &c); err != nil {
			return c, err
		}
	}

	// Finally, allow mutation of the data content type.
	contentTypeMessage := "Content type of data"
	if c.File != "" {
		contentTypeMessage = "Content type of file"
	}
	qs = []*survey.Question{
		{
			Name: "ContentType",
			Prompt: &survey.Input{
				Message: contentTypeMessage,
				Default: c.ContentType,
			},
		}}
	if err := survey.Ask(qs, &c); err != nil {
		return c, err
	}

	return c, nil
}
<|endoftext|>


<commit_before>package cmd

import (
	"flag"
	"fmt"
	"os"

	"github.com/driusan/dgit/git"
)

func Status(c *git.Client, args []string) error {
	flags := flag.NewFlagSet("status", flag.ExitOnError)
	flags.SetOutput(flag.CommandLine.Output())
	flags.Usage = func() {
		flag.Usage()
		fmt.Fprintf(flag.CommandLine.Output(), "\n\nOptions:\n")
		flags.PrintDefaults()
	}

	opts := git.StatusOptions{}

	flags.BoolVar(&opts.Short, "short", false, "Give the output in short format")
	flags.BoolVar(&opts.Short, "s", false, "Alias of --short")

	flags.BoolVar(&opts.Branch, "branch", false, "Short branch and tracking info, even in short mode")
	flags.BoolVar(&opts.Branch, "b", false, "Alias of --branch")

	flags.BoolVar(&opts.ShowStash, "show-stash", false, "Show the number of entries currently stashed")

	porcelain := flags.Int("porcelain", 0, "Give the output in a porcelain format")

	flags.BoolVar(&opts.Long, "long", true, "Give the output in long format")

	flags.BoolVar(&opts.Verbose, "verbose", false, "In addition to the modifies files, show a diff")
	flags.BoolVar(&opts.Verbose, "v", false, "Alias of --verbose")

	uno := flags.Bool("uno", false, "Do not show untracked files")
	unormal := flags.Bool("unormal", false, "Show untracked files and directories (default)")
	uall := flags.Bool("uall", false, "Show untracked files and files inside of directories")

	// FIXME: This should handle both --ignore-submodules and --ignore-submodules=<when>
	ignoresubmodules := flags.String("ignore-submodules", "", "When to ignore submodules")

	flags.BoolVar(&opts.Ignored, "ignored", false, "Show ignored files as well")

	flags.BoolVar(&opts.NullTerminate, "z", false, "Terminate entries with NULL, not LF. Implies --porcelain=v1 if not specified")

	// FIXME: This should handle both --column and --column=<options>
	column := flags.String("column", "default", "Show status in columns (not implemented)")

	nocolumn := flags.Bool("no-column", false, "Equivalent to --column=never")

	flags.Parse(args)

	switch *porcelain {
	case 0, 1, 2:
		opts.Porcelain = uint8(*porcelain)
	default:
		fmt.Fprintf(flag.CommandLine.Output(), "Invalid value for --porcelain, must be 0, 1 or 2\n")
		flags.Usage()
		os.Exit(2)
	}

	if *uno {
		opts.UntrackedMode = git.StatusUntrackedNo
	} else if *unormal {
		opts.UntrackedMode = git.StatusUntrackedNormal
	} else if *uall {
		opts.UntrackedMode = git.StatusUntrackedAll
	} else {
		opts.UntrackedMode = git.StatusUntrackedNormal
	}

	switch *ignoresubmodules {
	case "", "all":
		opts.IgnoreSubmodules = git.StatusIgnoreSubmodulesAll
	case "none":
		opts.IgnoreSubmodules = git.StatusIgnoreSubmodulesNone
	case "untracked":
		opts.IgnoreSubmodules = git.StatusIgnoreSubmodulesUntracked
	case "dirty":
		opts.IgnoreSubmodules = git.StatusIgnoreSubmodulesAll
	default:
		fmt.Fprintf(flag.CommandLine.Output(), "Invalid option for --ignore-submodules, must be all, none, untracked or dirty.\n")
		flags.Usage()
		os.Exit(2)
	}

	if *column != "" {
		opts.Column = git.StatusColumnOptions(*column)
	}
	if *nocolumn {
		opts.Column = "never"
	}

	status, err := git.Status(c, opts, nil)
	if err != nil {
		return err
	}
	fmt.Print(status)
	return nil
}
<commit_msg>Fix the flags with optional values in the status command (#198)<commit_after>package cmd

import (
	"flag"
	"fmt"
	"os"

	"github.com/driusan/dgit/git"
)

func Status(c *git.Client, args []string) error {
	flags := flag.NewFlagSet("status", flag.ExitOnError)
	flags.SetOutput(flag.CommandLine.Output())
	flags.Usage = func() {
		flag.Usage()
		fmt.Fprintf(flag.CommandLine.Output(), "\n\nOptions:\n")
		flags.PrintDefaults()
	}

	opts := git.StatusOptions{}

	flags.BoolVar(&opts.Short, "short", false, "Give the output in short format")
	flags.BoolVar(&opts.Short, "s", false, "Alias of --short")

	flags.BoolVar(&opts.Branch, "branch", false, "Short branch and tracking info, even in short mode")
	flags.BoolVar(&opts.Branch, "b", false, "Alias of --branch")

	flags.BoolVar(&opts.ShowStash, "show-stash", false, "Show the number of entries currently stashed")

	porcelain := flags.Int("porcelain", 0, "Give the output in a porcelain format")

	flags.BoolVar(&opts.Long, "long", true, "Give the output in long format")

	flags.BoolVar(&opts.Verbose, "verbose", false, "In addition to the modifies files, show a diff")
	flags.BoolVar(&opts.Verbose, "v", false, "Alias of --verbose")

	uno := flags.Bool("uno", false, "Do not show untracked files")
	unormal := flags.Bool("unormal", false, "Show untracked files and directories (default)")
	uall := flags.Bool("uall", false, "Show untracked files and files inside of directories")

	ignoresubmodules := flags.String("ignore-submodules", "", "When to ignore submodules")

	flags.BoolVar(&opts.Ignored, "ignored", false, "Show ignored files as well")

	flags.BoolVar(&opts.NullTerminate, "z", false, "Terminate entries with NULL, not LF. Implies --porcelain=v1 if not specified")

	column := flags.String("column", "default", "Show status in columns (not implemented)")

	nocolumn := flags.Bool("no-column", false, "Equivalent to --column=never")

	adjustedArgs := []string{}
	for _, a := range args {
		if a == "--porcelain" {
			a = "--porcelain=1"
		}
		if a == "--ignore-submodules" {
			a = "--ignore-submodules=all"
		}
		if a == "--column" {
			a = "--column=always"
		}
		adjustedArgs = append(adjustedArgs, a)
	}

	flags.Parse(adjustedArgs)

	switch *porcelain {
	case 0, 1, 2:
		opts.Porcelain = uint8(*porcelain)
	default:
		fmt.Fprintf(flag.CommandLine.Output(), "Invalid value for --porcelain, must be 0, 1 or 2\n")
		flags.Usage()
		os.Exit(2)
	}

	if *uno {
		opts.UntrackedMode = git.StatusUntrackedNo
	} else if *unormal {
		opts.UntrackedMode = git.StatusUntrackedNormal
	} else if *uall {
		opts.UntrackedMode = git.StatusUntrackedAll
	} else {
		opts.UntrackedMode = git.StatusUntrackedNormal
	}

	switch *ignoresubmodules {
	case "", "all":
		opts.IgnoreSubmodules = git.StatusIgnoreSubmodulesAll
	case "none":
		opts.IgnoreSubmodules = git.StatusIgnoreSubmodulesNone
	case "untracked":
		opts.IgnoreSubmodules = git.StatusIgnoreSubmodulesUntracked
	case "dirty":
		opts.IgnoreSubmodules = git.StatusIgnoreSubmodulesAll
	default:
		fmt.Fprintf(flag.CommandLine.Output(), "Invalid option for --ignore-submodules, must be all, none, untracked or dirty.\n")
		flags.Usage()
		os.Exit(2)
	}

	if *column != "" {
		opts.Column = git.StatusColumnOptions(*column)
	}
	if *nocolumn {
		opts.Column = "never"
	}

	status, err := git.Status(c, opts, nil)
	if err != nil {
		return err
	}
	fmt.Print(status)
	return nil
}
<|endoftext|>