<commit_before>package cli

import (
	"bytes"
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"strconv"

	"github.com/tus/tusd"
)

type HookType string

const (
	HookPostFinish    HookType = "post-finish"
	HookPostTerminate HookType = "post-terminate"
	HookPostTerminate HookType = "post-terminate"
	HookPreCreate     HookType = "pre-create"
)

type hookDataStore struct {
	tusd.DataStore
}

func (store hookDataStore) NewUpload(info tusd.FileInfo) (id string, err error) {
	if output, err := invokeHookSync(HookPreCreate, info, true); err != nil {
		return "", fmt.Errorf("pre-create hook failed:  %s\n%s", err, string(output))
	}
	return store.DataStore.NewUpload(info)
}

func SetupPreHooks(composer *tusd.StoreComposer) {
	composer.UseCore(hookDataStore{
		DataStore: composer.Core,
	})
}

func SetupPostHooks(handler *tusd.Handler) {
	go func() {
		for {
			select {
			case info := <-handler.CompleteUploads:
				invokeHook(HookPostFinish, info)
			case info := <-handler.TerminatedUploads:
				invokeHook(HookPostTerminate, info)
			case info := <-handler.UploadProgress:
				invokeHook(HookPostReceive, info)
			}
		}
	}()
}

func invokeHook(typ HookType, info tusd.FileInfo) {
	go func() {
		// Error handling is token care of by the function.
		_, _ = invokeHookSync(typ, info, false)
	}()
}

func invokeHookSync(typ HookType, info tusd.FileInfo, captureOutput bool) ([]byte, error) {
	switch typ {
	case HookPostFinish:
		logEv("UploadFinished", "id", info.ID, "size", strconv.FormatInt(info.Size, 10))
	case HookPostTerminate:
		logEv("UploadTerminated", "id", info.ID)
	}

	if !Flags.HooksInstalled {
		return nil, nil
	}

	name := string(typ)
	logEv("HookInvocationStart", "type", name, "id", info.ID)

	cmd := exec.Command(Flags.HooksDir + "/" + name)
	env := os.Environ()
	env = append(env, "TUS_ID="+info.ID)
	env = append(env, "TUS_SIZE="+strconv.FormatInt(info.Size, 10))
	env = append(env, "TUS_OFFSET="+strconv.FormatInt(info.Offset, 10))

	jsonInfo, err := json.Marshal(info)
	if err != nil {
		return nil, err
	}

	reader := bytes.NewReader(jsonInfo)
	cmd.Stdin = reader

	cmd.Env = env
	cmd.Dir = Flags.HooksDir
	cmd.Stderr = os.Stderr

	// If `captureOutput` is true, this function will return the output (both,
	// stderr and stdout), else it will use this process' stdout
	var output []byte
	if !captureOutput {
		cmd.Stdout = os.Stdout
		err = cmd.Run()
	} else {
		output, err = cmd.Output()
	}

	if err != nil {
		logEv("HookInvocationError", "type", string(typ), "id", info.ID, "error", err.Error())
	} else {
		logEv("HookInvocationFinish", "type", string(typ), "id", info.ID)
	}

	// Ignore the error, only, if the hook's file could not be found. This usually
	// means that the user is only using a subset of the available hooks.
	if os.IsNotExist(err) {
		err = nil
	}

	return output, err
}
<commit_msg>Correct constant for post-receive hook name<commit_after>package cli

import (
	"bytes"
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"strconv"

	"github.com/tus/tusd"
)

type HookType string

const (
	HookPostFinish    HookType = "post-finish"
	HookPostTerminate HookType = "post-terminate"
	HookPostReceive   HookType = "post-receive"
	HookPreCreate     HookType = "pre-create"
)

type hookDataStore struct {
	tusd.DataStore
}

func (store hookDataStore) NewUpload(info tusd.FileInfo) (id string, err error) {
	if output, err := invokeHookSync(HookPreCreate, info, true); err != nil {
		return "", fmt.Errorf("pre-create hook failed:  %s\n%s", err, string(output))
	}
	return store.DataStore.NewUpload(info)
}

func SetupPreHooks(composer *tusd.StoreComposer) {
	composer.UseCore(hookDataStore{
		DataStore: composer.Core,
	})
}

func SetupPostHooks(handler *tusd.Handler) {
	go func() {
		for {
			select {
			case info := <-handler.CompleteUploads:
				invokeHook(HookPostFinish, info)
			case info := <-handler.TerminatedUploads:
				invokeHook(HookPostTerminate, info)
			case info := <-handler.UploadProgress:
				invokeHook(HookPostReceive, info)
			}
		}
	}()
}

func invokeHook(typ HookType, info tusd.FileInfo) {
	go func() {
		// Error handling is token care of by the function.
		_, _ = invokeHookSync(typ, info, false)
	}()
}

func invokeHookSync(typ HookType, info tusd.FileInfo, captureOutput bool) ([]byte, error) {
	switch typ {
	case HookPostFinish:
		logEv("UploadFinished", "id", info.ID, "size", strconv.FormatInt(info.Size, 10))
	case HookPostTerminate:
		logEv("UploadTerminated", "id", info.ID)
	}

	if !Flags.HooksInstalled {
		return nil, nil
	}

	name := string(typ)
	logEv("HookInvocationStart", "type", name, "id", info.ID)

	cmd := exec.Command(Flags.HooksDir + "/" + name)
	env := os.Environ()
	env = append(env, "TUS_ID="+info.ID)
	env = append(env, "TUS_SIZE="+strconv.FormatInt(info.Size, 10))
	env = append(env, "TUS_OFFSET="+strconv.FormatInt(info.Offset, 10))

	jsonInfo, err := json.Marshal(info)
	if err != nil {
		return nil, err
	}

	reader := bytes.NewReader(jsonInfo)
	cmd.Stdin = reader

	cmd.Env = env
	cmd.Dir = Flags.HooksDir
	cmd.Stderr = os.Stderr

	// If `captureOutput` is true, this function will return the output (both,
	// stderr and stdout), else it will use this process' stdout
	var output []byte
	if !captureOutput {
		cmd.Stdout = os.Stdout
		err = cmd.Run()
	} else {
		output, err = cmd.Output()
	}

	if err != nil {
		logEv("HookInvocationError", "type", string(typ), "id", info.ID, "error", err.Error())
	} else {
		logEv("HookInvocationFinish", "type", string(typ), "id", info.ID)
	}

	// Ignore the error, only, if the hook's file could not be found. This usually
	// means that the user is only using a subset of the available hooks.
	if os.IsNotExist(err) {
		err = nil
	}

	return output, err
}
<|endoftext|>


<commit_before>/*
 * This file is part of the KubeVirt project
 *
 * 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.
 *
 * Copyright 2017 Red Hat, Inc.
 *
 */

package kubecli

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
	"net/url"

	"github.com/gorilla/websocket"

	k8smetav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"

	"k8s.io/apimachinery/pkg/types"

	"kubevirt.io/kubevirt/pkg/api/v1"
)

const (
	WebsocketMessageBufferSize = 10240
)

func (k *kubevirt) VM(namespace string) VMInterface {
	return &vms{
		restClient: k.restClient,
		clientSet:  k.Clientset,
		namespace:  namespace,
		resource:   "virtualmachines",
		master:     k.master,
		kubeconfig: k.kubeconfig,
	}
}

type vms struct {
	restClient *rest.RESTClient
	clientSet  *kubernetes.Clientset
	namespace  string
	resource   string
	master     string
	kubeconfig string
}

type BinaryReadWriter struct {
	Conn *websocket.Conn
}

func (s *BinaryReadWriter) Write(p []byte) (int, error) {
	wsFrameHeaderSize := 2 + 8 + 4
	// our websocket package has an issue where it truncates messages
	// when the message+header is greater than the buffer size we allocate.
	// because of this, we have to chunk messages
	chunkSize := WebsocketMessageBufferSize - wsFrameHeaderSize
	bytesWritten := 0

	for i := 0; i < len(p); i += chunkSize {
		w, err := s.Conn.NextWriter(websocket.BinaryMessage)
		if err != nil {
			return bytesWritten, s.err(err)
		}
		defer w.Close()

		end := i + chunkSize
		if end > len(p) {
			end = len(p)
		}
		n, err := w.Write(p[i:end])
		if err != nil {
			return bytesWritten, err
		}

		bytesWritten = n + bytesWritten
	}
	return bytesWritten, nil

}

func (s *BinaryReadWriter) Read(p []byte) (int, error) {
	for {
		msgType, r, err := s.Conn.NextReader()
		if err != nil {
			return 0, s.err(err)
		}

		switch msgType {
		case websocket.BinaryMessage:
			n, err := r.Read(p)
			return n, s.err(err)

		case websocket.CloseMessage:
			return 0, io.EOF
		}
	}
}

func (s *BinaryReadWriter) err(err error) error {
	if err == nil {
		return nil
	}
	if e, ok := err.(*websocket.CloseError); ok {
		if e.Code == websocket.CloseNormalClosure {
			return io.EOF
		}
	}
	return err
}

type RoundTripCallback func(conn *websocket.Conn, resp *http.Response, err error) error

type WebsocketRoundTripper struct {
	Dialer *websocket.Dialer
	Do     RoundTripCallback
}

func (d *WebsocketRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
	conn, resp, err := d.Dialer.Dial(r.URL.String(), r.Header)
	if err == nil {
		defer conn.Close()
	}
	return resp, d.Do(conn, resp, err)
}

type wsCallbackObj struct {
	in  io.Reader
	out io.Writer
}

func (obj *wsCallbackObj) WebsocketCallback(ws *websocket.Conn, resp *http.Response, err error) error {

	if err != nil {
		if resp != nil && resp.StatusCode != http.StatusOK {
			buf := new(bytes.Buffer)
			buf.ReadFrom(resp.Body)
			return fmt.Errorf("Can't connect to websocket (%d): %s\n", resp.StatusCode, buf.String())
		}
		return fmt.Errorf("Can't connect to websocket: %s\n", err.Error())
	}

	wsReadWriter := &BinaryReadWriter{Conn: ws}

	copyErr := make(chan error)

	go func() {
		_, err := io.Copy(wsReadWriter, obj.in)
		copyErr <- err
	}()

	go func() {
		_, err := io.Copy(obj.out, wsReadWriter)
		copyErr <- err
	}()

	err = <-copyErr
	return err
}

func roundTripperFromConfig(config *rest.Config, in io.Reader, out io.Writer) (http.RoundTripper, error) {

	// Configure TLS
	tlsConfig, err := rest.TLSConfigFor(config)
	if err != nil {
		return nil, err
	}

	// Configure the websocket dialer
	dialer := &websocket.Dialer{
		Proxy:           http.ProxyFromEnvironment,
		TLSClientConfig: tlsConfig,
		WriteBufferSize: WebsocketMessageBufferSize,
		ReadBufferSize:  WebsocketMessageBufferSize,
	}

	obj := &wsCallbackObj{
		in:  in,
		out: out,
	}
	// Create a roundtripper which will pass in the final underlying websocket connection to a callback
	rt := &WebsocketRoundTripper{
		Do:     obj.WebsocketCallback,
		Dialer: dialer,
	}

	// Make sure we inherit all relevant security headers
	return rest.HTTPWrappersForConfig(config, rt)
}

func RequestFromConfig(config *rest.Config, vm string, namespace string, resource string) (*http.Request, error) {

	u, err := url.Parse(config.Host)
	if err != nil {
		return nil, err
	}

	switch u.Scheme {
	case "https":
		u.Scheme = "wss"
	case "http":
		u.Scheme = "ws"
	default:
		return nil, fmt.Errorf("Unsupported Protocol %s", u.Scheme)
	}

	u.Path = fmt.Sprintf("/apis/subresources.kubevirt.io/v1alpha1/namespaces/%s/virtualmachines/%s/%s", namespace, vm, resource)
	req := &http.Request{
		Method: http.MethodGet,
		URL:    u,
	}

	return req, nil
}

func (v *vms) VNC(name string, in io.Reader, out io.Writer) error {
	return v.subresourceHelper(name, "vnc", in, out)
}
func (v *vms) SerialConsole(name string, in io.Reader, out io.Writer) error {
	return v.subresourceHelper(name, "console", in, out)
}

func (v *vms) subresourceHelper(name string, resource string, in io.Reader, out io.Writer) error {

	// creates the connection
	config, err := clientcmd.BuildConfigFromFlags(v.master, v.kubeconfig)
	if err != nil {
		return fmt.Errorf("failed to create restClient for remote execution: %v", err)
	}

	// Create a round tripper with all necessary kubernetes security details
	wrappedRoundTripper, err := roundTripperFromConfig(config, in, out)
	if err != nil {
		return fmt.Errorf("unable to create round tripper for remote execution: %v", err)
	}

	// Create a request out of config and the query parameters
	req, err := RequestFromConfig(config, name, v.namespace, resource)
	if err != nil {
		return fmt.Errorf("unable to create request for remote execution: %v", err)
	}

	// Send the request and let the callback do its work
	response, err := wrappedRoundTripper.RoundTrip(req)

	if err != nil {
		return err
	}

	if response != nil {
		switch response.StatusCode {
		case http.StatusOK:
			return nil
		case http.StatusNotFound:
			return fmt.Errorf("Virtual Machine not found.")
		case http.StatusInternalServerError:
			return fmt.Errorf("Websocket failed due to internal server error.")
		default:
			return fmt.Errorf("Websocket failed with http status: %s", response.Status)
		}
	} else {
		return fmt.Errorf("no response received")
	}
}

func (v *vms) Get(name string, options k8smetav1.GetOptions) (vm *v1.VirtualMachine, err error) {
	vm = &v1.VirtualMachine{}
	err = v.restClient.Get().
		Resource(v.resource).
		Namespace(v.namespace).
		Name(name).
		VersionedParams(&options, scheme.ParameterCodec).
		Do().
		Into(vm)
	vm.SetGroupVersionKind(v1.VirtualMachineGroupVersionKind)
	return
}

func (v *vms) List(options k8smetav1.ListOptions) (vmList *v1.VirtualMachineList, err error) {
	vmList = &v1.VirtualMachineList{}
	err = v.restClient.Get().
		Resource(v.resource).
		Namespace(v.namespace).
		VersionedParams(&options, scheme.ParameterCodec).
		Do().
		Into(vmList)
	for _, vm := range vmList.Items {
		vm.SetGroupVersionKind(v1.VirtualMachineGroupVersionKind)
	}

	return
}

func (v *vms) Create(vm *v1.VirtualMachine) (result *v1.VirtualMachine, err error) {
	result = &v1.VirtualMachine{}
	err = v.restClient.Post().
		Namespace(v.namespace).
		Resource(v.resource).
		Body(vm).
		Do().
		Into(result)
	result.SetGroupVersionKind(v1.VirtualMachineGroupVersionKind)
	return
}

func (v *vms) Update(vm *v1.VirtualMachine) (result *v1.VirtualMachine, err error) {
	result = &v1.VirtualMachine{}
	err = v.restClient.Put().
		Name(vm.ObjectMeta.Name).
		Namespace(v.namespace).
		Resource(v.resource).
		Body(vm).
		Do().
		Into(result)
	result.SetGroupVersionKind(v1.VirtualMachineGroupVersionKind)
	return
}

func (v *vms) Delete(name string, options *k8smetav1.DeleteOptions) error {
	return v.restClient.Delete().
		Namespace(v.namespace).
		Resource(v.resource).
		Name(name).
		Body(options).
		Do().
		Error()
}

func (v *vms) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.VirtualMachine, err error) {
	result = &v1.VirtualMachine{}
	err = v.restClient.Patch(pt).
		Namespace(v.namespace).
		Resource(v.resource).
		SubResource(subresources...).
		Name(name).
		Body(data).
		Do().
		Into(result)
	return
}
<commit_msg>Add comment about where ws frame header values come from<commit_after>/*
 * This file is part of the KubeVirt project
 *
 * 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.
 *
 * Copyright 2017 Red Hat, Inc.
 *
 */

package kubecli

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
	"net/url"

	"github.com/gorilla/websocket"

	k8smetav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"

	"k8s.io/apimachinery/pkg/types"

	"kubevirt.io/kubevirt/pkg/api/v1"
)

const (
	WebsocketMessageBufferSize = 10240
)

func (k *kubevirt) VM(namespace string) VMInterface {
	return &vms{
		restClient: k.restClient,
		clientSet:  k.Clientset,
		namespace:  namespace,
		resource:   "virtualmachines",
		master:     k.master,
		kubeconfig: k.kubeconfig,
	}
}

type vms struct {
	restClient *rest.RESTClient
	clientSet  *kubernetes.Clientset
	namespace  string
	resource   string
	master     string
	kubeconfig string
}

type BinaryReadWriter struct {
	Conn *websocket.Conn
}

func (s *BinaryReadWriter) Write(p []byte) (int, error) {
	wsFrameHeaderSize := 2 + 8 + 4 // Fixed header + length + mask (RFC 6455)
	// our websocket package has an issue where it truncates messages
	// when the message+header is greater than the buffer size we allocate.
	// because of this, we have to chunk messages
	chunkSize := WebsocketMessageBufferSize - wsFrameHeaderSize
	bytesWritten := 0

	for i := 0; i < len(p); i += chunkSize {
		w, err := s.Conn.NextWriter(websocket.BinaryMessage)
		if err != nil {
			return bytesWritten, s.err(err)
		}
		defer w.Close()

		end := i + chunkSize
		if end > len(p) {
			end = len(p)
		}
		n, err := w.Write(p[i:end])
		if err != nil {
			return bytesWritten, err
		}

		bytesWritten = n + bytesWritten
	}
	return bytesWritten, nil

}

func (s *BinaryReadWriter) Read(p []byte) (int, error) {
	for {
		msgType, r, err := s.Conn.NextReader()
		if err != nil {
			return 0, s.err(err)
		}

		switch msgType {
		case websocket.BinaryMessage:
			n, err := r.Read(p)
			return n, s.err(err)

		case websocket.CloseMessage:
			return 0, io.EOF
		}
	}
}

func (s *BinaryReadWriter) err(err error) error {
	if err == nil {
		return nil
	}
	if e, ok := err.(*websocket.CloseError); ok {
		if e.Code == websocket.CloseNormalClosure {
			return io.EOF
		}
	}
	return err
}

type RoundTripCallback func(conn *websocket.Conn, resp *http.Response, err error) error

type WebsocketRoundTripper struct {
	Dialer *websocket.Dialer
	Do     RoundTripCallback
}

func (d *WebsocketRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
	conn, resp, err := d.Dialer.Dial(r.URL.String(), r.Header)
	if err == nil {
		defer conn.Close()
	}
	return resp, d.Do(conn, resp, err)
}

type wsCallbackObj struct {
	in  io.Reader
	out io.Writer
}

func (obj *wsCallbackObj) WebsocketCallback(ws *websocket.Conn, resp *http.Response, err error) error {

	if err != nil {
		if resp != nil && resp.StatusCode != http.StatusOK {
			buf := new(bytes.Buffer)
			buf.ReadFrom(resp.Body)
			return fmt.Errorf("Can't connect to websocket (%d): %s\n", resp.StatusCode, buf.String())
		}
		return fmt.Errorf("Can't connect to websocket: %s\n", err.Error())
	}

	wsReadWriter := &BinaryReadWriter{Conn: ws}

	copyErr := make(chan error)

	go func() {
		_, err := io.Copy(wsReadWriter, obj.in)
		copyErr <- err
	}()

	go func() {
		_, err := io.Copy(obj.out, wsReadWriter)
		copyErr <- err
	}()

	err = <-copyErr
	return err
}

func roundTripperFromConfig(config *rest.Config, in io.Reader, out io.Writer) (http.RoundTripper, error) {

	// Configure TLS
	tlsConfig, err := rest.TLSConfigFor(config)
	if err != nil {
		return nil, err
	}

	// Configure the websocket dialer
	dialer := &websocket.Dialer{
		Proxy:           http.ProxyFromEnvironment,
		TLSClientConfig: tlsConfig,
		WriteBufferSize: WebsocketMessageBufferSize,
		ReadBufferSize:  WebsocketMessageBufferSize,
	}

	obj := &wsCallbackObj{
		in:  in,
		out: out,
	}
	// Create a roundtripper which will pass in the final underlying websocket connection to a callback
	rt := &WebsocketRoundTripper{
		Do:     obj.WebsocketCallback,
		Dialer: dialer,
	}

	// Make sure we inherit all relevant security headers
	return rest.HTTPWrappersForConfig(config, rt)
}

func RequestFromConfig(config *rest.Config, vm string, namespace string, resource string) (*http.Request, error) {

	u, err := url.Parse(config.Host)
	if err != nil {
		return nil, err
	}

	switch u.Scheme {
	case "https":
		u.Scheme = "wss"
	case "http":
		u.Scheme = "ws"
	default:
		return nil, fmt.Errorf("Unsupported Protocol %s", u.Scheme)
	}

	u.Path = fmt.Sprintf("/apis/subresources.kubevirt.io/v1alpha1/namespaces/%s/virtualmachines/%s/%s", namespace, vm, resource)
	req := &http.Request{
		Method: http.MethodGet,
		URL:    u,
	}

	return req, nil
}

func (v *vms) VNC(name string, in io.Reader, out io.Writer) error {
	return v.subresourceHelper(name, "vnc", in, out)
}
func (v *vms) SerialConsole(name string, in io.Reader, out io.Writer) error {
	return v.subresourceHelper(name, "console", in, out)
}

func (v *vms) subresourceHelper(name string, resource string, in io.Reader, out io.Writer) error {

	// creates the connection
	config, err := clientcmd.BuildConfigFromFlags(v.master, v.kubeconfig)
	if err != nil {
		return fmt.Errorf("failed to create restClient for remote execution: %v", err)
	}

	// Create a round tripper with all necessary kubernetes security details
	wrappedRoundTripper, err := roundTripperFromConfig(config, in, out)
	if err != nil {
		return fmt.Errorf("unable to create round tripper for remote execution: %v", err)
	}

	// Create a request out of config and the query parameters
	req, err := RequestFromConfig(config, name, v.namespace, resource)
	if err != nil {
		return fmt.Errorf("unable to create request for remote execution: %v", err)
	}

	// Send the request and let the callback do its work
	response, err := wrappedRoundTripper.RoundTrip(req)

	if err != nil {
		return err
	}

	if response != nil {
		switch response.StatusCode {
		case http.StatusOK:
			return nil
		case http.StatusNotFound:
			return fmt.Errorf("Virtual Machine not found.")
		case http.StatusInternalServerError:
			return fmt.Errorf("Websocket failed due to internal server error.")
		default:
			return fmt.Errorf("Websocket failed with http status: %s", response.Status)
		}
	} else {
		return fmt.Errorf("no response received")
	}
}

func (v *vms) Get(name string, options k8smetav1.GetOptions) (vm *v1.VirtualMachine, err error) {
	vm = &v1.VirtualMachine{}
	err = v.restClient.Get().
		Resource(v.resource).
		Namespace(v.namespace).
		Name(name).
		VersionedParams(&options, scheme.ParameterCodec).
		Do().
		Into(vm)
	vm.SetGroupVersionKind(v1.VirtualMachineGroupVersionKind)
	return
}

func (v *vms) List(options k8smetav1.ListOptions) (vmList *v1.VirtualMachineList, err error) {
	vmList = &v1.VirtualMachineList{}
	err = v.restClient.Get().
		Resource(v.resource).
		Namespace(v.namespace).
		VersionedParams(&options, scheme.ParameterCodec).
		Do().
		Into(vmList)
	for _, vm := range vmList.Items {
		vm.SetGroupVersionKind(v1.VirtualMachineGroupVersionKind)
	}

	return
}

func (v *vms) Create(vm *v1.VirtualMachine) (result *v1.VirtualMachine, err error) {
	result = &v1.VirtualMachine{}
	err = v.restClient.Post().
		Namespace(v.namespace).
		Resource(v.resource).
		Body(vm).
		Do().
		Into(result)
	result.SetGroupVersionKind(v1.VirtualMachineGroupVersionKind)
	return
}

func (v *vms) Update(vm *v1.VirtualMachine) (result *v1.VirtualMachine, err error) {
	result = &v1.VirtualMachine{}
	err = v.restClient.Put().
		Name(vm.ObjectMeta.Name).
		Namespace(v.namespace).
		Resource(v.resource).
		Body(vm).
		Do().
		Into(result)
	result.SetGroupVersionKind(v1.VirtualMachineGroupVersionKind)
	return
}

func (v *vms) Delete(name string, options *k8smetav1.DeleteOptions) error {
	return v.restClient.Delete().
		Namespace(v.namespace).
		Resource(v.resource).
		Name(name).
		Body(options).
		Do().
		Error()
}

func (v *vms) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.VirtualMachine, err error) {
	result = &v1.VirtualMachine{}
	err = v.restClient.Patch(pt).
		Namespace(v.namespace).
		Resource(v.resource).
		SubResource(subresources...).
		Name(name).
		Body(data).
		Do().
		Into(result)
	return
}
<|endoftext|>


<commit_before>// Original license //
// ---------------- //

/*
Copyright 2011 Google Inc.

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

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

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

// All other modifications and improvements //
// ---------------------------------------- //

/*
 * Mini Object Storage, (C) 2015 Minio, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package s3

import (
	"time"

	"encoding/xml"
)

// Date format
const (
	xmlTimeFormat = "2006-01-02T15:04:05.000Z"
)

type xmlTime struct {
	time.Time
}

func (c *xmlTime) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
	var v string
	d.DecodeElement(&v, &start)
	parse, _ := time.Parse(xmlTimeFormat, v)
	*c = xmlTime{parse}
	return nil
}

func (c *xmlTime) UnmarshalXMLAttr(attr xml.Attr) error {
	t, _ := time.Parse(xmlTimeFormat, attr.Value)
	*c = xmlTime{t}
	return nil
}

func (c *xmlTime) format() string {
	return c.Time.Format(xmlTimeFormat)
}
<commit_msg>Change name from xmlFormat to actual understandable name ``iso8601Format``<commit_after>// Original license //
// ---------------- //

/*
Copyright 2011 Google Inc.

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

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

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

// All other modifications and improvements //
// ---------------------------------------- //

/*
 * Mini Object Storage, (C) 2015 Minio, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package s3

import (
	"time"

	"encoding/xml"
)

// Date format
const (
	iso8601Format = "2006-01-02T15:04:05.000Z"
)

type xmlTime struct {
	time.Time
}

func (c *xmlTime) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
	var v string
	d.DecodeElement(&v, &start)
	parse, _ := time.Parse(iso8601Format, v)
	*c = xmlTime{parse}
	return nil
}

func (c *xmlTime) UnmarshalXMLAttr(attr xml.Attr) error {
	t, _ := time.Parse(iso8601Format, attr.Value)
	*c = xmlTime{t}
	return nil
}

func (c *xmlTime) format() string {
	return c.Time.Format(iso8601Format)
}
<|endoftext|>


<commit_before>package units

import (
	"fmt"
	"regexp"
	"strconv"
	"strings"
)

// See: http://en.wikipedia.org/wiki/Binary_prefix
const (
	// Decimal

	KB = 1000
	MB = 1000 * KB
	GB = 1000 * MB
	TB = 1000 * GB
	PB = 1000 * TB

	// Binary

	KiB = 1024
	MiB = 1024 * KiB
	GiB = 1024 * MiB
	TiB = 1024 * GiB
	PiB = 1024 * TiB
)

type unitMap map[string]int64

var (
	decimalMap = unitMap{"k": KB, "m": MB, "g": GB, "t": TB, "p": PB}
	binaryMap  = unitMap{"k": KiB, "m": MiB, "g": GiB, "t": TiB, "p": PiB}
	sizeRegex  = regexp.MustCompile(`^(\d+)([kKmMgGtTpP])?[bB]?$`)
)

var decimapAbbrs = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
var binaryAbbrs = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"}

// HumanSize returns a human-readable approximation of a size
// using SI standard (eg. "44kB", "17MB")
func HumanSize(size float64) string {
	return intToString(float64(size), 1000.0, decimapAbbrs)
}

func BytesSize(size float64) string {
	return intToString(size, 1024.0, binaryAbbrs)
}

func intToString(size, unit float64, _map []string) string {
	i := 0
	for size >= unit {
		size = size / unit
		i++
	}
	return fmt.Sprintf("%.4g %s", size, _map[i])
}

// FromHumanSize returns an integer from a human-readable specification of a
// size using SI standard (eg. "44kB", "17MB")
func FromHumanSize(size string) (int64, error) {
	return parseSize(size, decimalMap)
}

// RAMInBytes parses a human-readable string representing an amount of RAM
// in bytes, kibibytes, mebibytes, gibibytes, or tebibytes and
// returns the number of bytes, or -1 if the string is unparseable.
// Units are case-insensitive, and the 'b' suffix is optional.
func RAMInBytes(size string) (int64, error) {
	return parseSize(size, binaryMap)
}

// Parses the human-readable size string into the amount it represents
func parseSize(sizeStr string, uMap unitMap) (int64, error) {
	matches := sizeRegex.FindStringSubmatch(sizeStr)
	if len(matches) != 3 {
		return -1, fmt.Errorf("invalid size: '%s'", sizeStr)
	}

	size, err := strconv.ParseInt(matches[1], 10, 0)
	if err != nil {
		return -1, err
	}

	unitPrefix := strings.ToLower(matches[2])
	if mul, ok := uMap[unitPrefix]; ok {
		size *= mul
	}

	return size, nil
}
<commit_msg>use CustomSize replace intToString<commit_after>package units

import (
	"fmt"
	"regexp"
	"strconv"
	"strings"
)

// See: http://en.wikipedia.org/wiki/Binary_prefix
const (
	// Decimal

	KB = 1000
	MB = 1000 * KB
	GB = 1000 * MB
	TB = 1000 * GB
	PB = 1000 * TB

	// Binary

	KiB = 1024
	MiB = 1024 * KiB
	GiB = 1024 * MiB
	TiB = 1024 * GiB
	PiB = 1024 * TiB
)

type unitMap map[string]int64

var (
	decimalMap = unitMap{"k": KB, "m": MB, "g": GB, "t": TB, "p": PB}
	binaryMap  = unitMap{"k": KiB, "m": MiB, "g": GiB, "t": TiB, "p": PiB}
	sizeRegex  = regexp.MustCompile(`^(\d+)([kKmMgGtTpP])?[bB]?$`)
)

var decimapAbbrs = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
var binaryAbbrs = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"}

// CustomSize returns a human-readable approximation of a size
// using custom format
func CustomSize(format string, size float64, base float64, _map []string) string {
	i := 0
	for size >= base {
		size = size / base
		i++
	}
	return fmt.Sprintf(format, size, _map[i])
}

// HumanSize returns a human-readable approximation of a size
// using SI standard (eg. "44kB", "17MB")
func HumanSize(size float64) string {
	return CustomSize("%.4g %s", float64(size), 1000.0, decimapAbbrs)
}

func BytesSize(size float64) string {
	return CustomSize("%.4g %s", size, 1024.0, binaryAbbrs)
}

// FromHumanSize returns an integer from a human-readable specification of a
// size using SI standard (eg. "44kB", "17MB")
func FromHumanSize(size string) (int64, error) {
	return parseSize(size, decimalMap)
}

// RAMInBytes parses a human-readable string representing an amount of RAM
// in bytes, kibibytes, mebibytes, gibibytes, or tebibytes and
// returns the number of bytes, or -1 if the string is unparseable.
// Units are case-insensitive, and the 'b' suffix is optional.
func RAMInBytes(size string) (int64, error) {
	return parseSize(size, binaryMap)
}

// Parses the human-readable size string into the amount it represents
func parseSize(sizeStr string, uMap unitMap) (int64, error) {
	matches := sizeRegex.FindStringSubmatch(sizeStr)
	if len(matches) != 3 {
		return -1, fmt.Errorf("invalid size: '%s'", sizeStr)
	}

	size, err := strconv.ParseInt(matches[1], 10, 0)
	if err != nil {
		return -1, err
	}

	unitPrefix := strings.ToLower(matches[2])
	if mul, ok := uMap[unitPrefix]; ok {
		size *= mul
	}

	return size, nil
}
<|endoftext|>


<commit_before>package cli

import (
	"errors"
	"fmt"
	"os"
	"strings"

	cmds "github.com/jbenet/go-ipfs/commands"
)

// Parse parses the input commandline string (cmd, flags, and args).
// returns the corresponding command Request object.
func Parse(input []string, roots ...*cmds.Command) (cmds.Request, *cmds.Command, error) {
	var req cmds.Request
	var root *cmds.Command

	// use the root that matches the longest path (most accurately matches request)
	maxLength := 0
	for _, r := range roots {
		path, input, cmd := parsePath(input, r)
		opts, stringArgs, err := parseOptions(input)
		if err != nil {
			return nil, nil, err
		}

		length := len(path)
		if length > maxLength {
			maxLength = length

			args, err := parseArgs(stringArgs, cmd)
			if err != nil {
				return nil, nil, err
			}

			req = cmds.NewRequest(path, opts, args, cmd)
			root = r
		}
	}

	if maxLength == 0 {
		return nil, nil, errors.New("Not a valid subcommand")
	}

	return req, root, nil
}

// parsePath gets the command path from the command line input
func parsePath(input []string, root *cmds.Command) ([]string, []string, *cmds.Command) {
	cmd := root
	i := 0

	for _, blob := range input {
		if strings.HasPrefix(blob, "-") {
			break
		}

		sub := cmd.Subcommand(blob)
		if sub == nil {
			break
		}
		cmd = sub

		i++
	}

	return input[:i], input[i:], cmd
}

// parseOptions parses the raw string values of the given options
// returns the parsed options as strings, along with the CLI args
func parseOptions(input []string) (map[string]interface{}, []string, error) {
	opts := make(map[string]interface{})
	args := []string{}

	for i := 0; i < len(input); i++ {
		blob := input[i]

		if strings.HasPrefix(blob, "-") {
			name := blob[1:]
			value := ""

			// support single and double dash
			if strings.HasPrefix(name, "-") {
				name = name[1:]
			}

			if strings.Contains(name, "=") {
				split := strings.SplitN(name, "=", 2)
				name = split[0]
				value = split[1]
			}

			if _, ok := opts[name]; ok {
				return nil, nil, fmt.Errorf("Duplicate values for option '%s'", name)
			}

			opts[name] = value

		} else {
			args = append(args, blob)
		}
	}

	return opts, args, nil
}

// Note that the argument handling here is dumb, it does not do any error-checking.
// (Arguments are further processed when the request is passed to the command to run)
func parseArgs(stringArgs []string, cmd *cmds.Command) ([]interface{}, error) {
	args := make([]interface{}, len(cmd.Arguments))

	for i, arg := range cmd.Arguments {
		// TODO: handle variadic args
		if i >= len(stringArgs) {
			break
		}

		if arg.Type == cmds.ArgString {
			args[i] = stringArgs[i]

		} else {
			in, err := os.Open(stringArgs[i])
			if err != nil {
				return nil, err
			}
			args[i] = in
		}
	}

	return args, nil
}
<commit_msg>commands/cli: Made parser handle variadic arguments<commit_after>package cli

import (
	"errors"
	"fmt"
	"os"
	"strings"

	cmds "github.com/jbenet/go-ipfs/commands"
)

// Parse parses the input commandline string (cmd, flags, and args).
// returns the corresponding command Request object.
func Parse(input []string, roots ...*cmds.Command) (cmds.Request, *cmds.Command, error) {
	var req cmds.Request
	var root *cmds.Command

	// use the root that matches the longest path (most accurately matches request)
	maxLength := 0
	for _, r := range roots {
		path, input, cmd := parsePath(input, r)
		opts, stringArgs, err := parseOptions(input)
		if err != nil {
			return nil, nil, err
		}

		length := len(path)
		if length > maxLength {
			maxLength = length

			args, err := parseArgs(stringArgs, cmd)
			if err != nil {
				return nil, nil, err
			}

			req = cmds.NewRequest(path, opts, args, cmd)
			root = r
		}
	}

	if maxLength == 0 {
		return nil, nil, errors.New("Not a valid subcommand")
	}

	return req, root, nil
}

// parsePath gets the command path from the command line input
func parsePath(input []string, root *cmds.Command) ([]string, []string, *cmds.Command) {
	cmd := root
	i := 0

	for _, blob := range input {
		if strings.HasPrefix(blob, "-") {
			break
		}

		sub := cmd.Subcommand(blob)
		if sub == nil {
			break
		}
		cmd = sub

		i++
	}

	return input[:i], input[i:], cmd
}

// parseOptions parses the raw string values of the given options
// returns the parsed options as strings, along with the CLI args
func parseOptions(input []string) (map[string]interface{}, []string, error) {
	opts := make(map[string]interface{})
	args := []string{}

	for i := 0; i < len(input); i++ {
		blob := input[i]

		if strings.HasPrefix(blob, "-") {
			name := blob[1:]
			value := ""

			// support single and double dash
			if strings.HasPrefix(name, "-") {
				name = name[1:]
			}

			if strings.Contains(name, "=") {
				split := strings.SplitN(name, "=", 2)
				name = split[0]
				value = split[1]
			}

			if _, ok := opts[name]; ok {
				return nil, nil, fmt.Errorf("Duplicate values for option '%s'", name)
			}

			opts[name] = value

		} else {
			args = append(args, blob)
		}
	}

	return opts, args, nil
}

// Note that the argument handling here is dumb, it does not do any error-checking.
// (Arguments are further processed when the request is passed to the command to run)
func parseArgs(stringArgs []string, cmd *cmds.Command) ([]interface{}, error) {
	var argDef cmds.Argument
	args := make([]interface{}, len(stringArgs))

	for i, arg := range stringArgs {
		if i < len(cmd.Arguments) {
			argDef = cmd.Arguments[i]
		}

		if argDef.Type == cmds.ArgString {
			args[i] = arg

		} else {
			in, err := os.Open(arg)
			if err != nil {
				return nil, err
			}
			args[i] = in
		}
	}

	return args, nil
}
<|endoftext|>


<commit_before>// +build linux darwin freebsd

package commands

import (
	"bytes"
	"crypto/sha256"
	"fmt"
	"io/ioutil"
	"net/http"
	"os/exec"
	"strings"
)

func init() {
	// Nothing happens here.
}

// LengthCheck makes sure a string has at least minLength lines.
func LengthCheck(data string, minLength int) bool {
	length := LineCount(data)
	Log(fmt.Sprintf("length='%d' minLength='%d'", length, minLength), "debug")
	if length >= minLength {
		return true
	}
	return false
}

// ReadURL grabs a URL and returns the string from the body.
func ReadURL(url string) string {
	resp, err := http.Get(url)
	if err != nil {
		Log(fmt.Sprintf("function='ReadURL' panic='true' url='%s'", url), "info")
		fmt.Printf("Panic: Could not open URL: '%s'\n", url)
		StatsdPanic(url, "read_url")
	}
	defer resp.Body.Close()
	body, _ := ioutil.ReadAll(resp.Body)
	return string(body)
}

// LineCount splits a string by linebreak and returns the number of lines.
func LineCount(data string) int {
	var length int
	if strings.ContainsAny(data, "\n") {
		length = strings.Count(data, "\n")
	} else {
		length = 1
	}
	return length
}

// ComputeChecksum takes a string and computes a SHA256 checksum.
func ComputeChecksum(data string) string {
	dataBytes := []byte(data)
	computedChecksum := sha256.Sum256(dataBytes)
	finalChecksum := fmt.Sprintf("%x", computedChecksum)
	Log(fmt.Sprintf("computedChecksum='%s'", finalChecksum), "debug")
	if finalChecksum == "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" {
		Log("WARNING: That checksum means the data/key is blank. WARNING", "info")
	}
	return finalChecksum
}

// ChecksumCompare takes a string, generates a SHA256 checksum and compares
// against the passed checksum to see if they match.
func ChecksumCompare(data string, checksum string) bool {
	computedChecksum := ComputeChecksum(data)
	Log(fmt.Sprintf("checksum='%s' computedChecksum='%s'", checksum, computedChecksum), "debug")
	if strings.TrimSpace(computedChecksum) == strings.TrimSpace(checksum) {
		return true
	}
	return false
}

// UnixDiff runs diff to generate text for the Datadog events.
func UnixDiff(old, new string) string {
	diff, _ := exec.Command("diff", "-u", old, new).Output()
	text := string(diff)
	finalText := removeLines(text, 3)
	return finalText
}

// removeLines trims the top n number of lines from a string.
func removeLines(text string, number int) string {
	lines := strings.Split(text, "\n")
	var cleaned []string
	cleaned = append(cleaned, lines[number:]...)
	finalText := strings.Join(cleaned, "\n")
	return finalText
}

// RunCommand runs a cli command with arguments.
func RunCommand(command string) bool {
	parts := strings.Fields(command)
	cli := parts[0]
	args := parts[1:len(parts)]
	cmd := exec.Command(cli, args...)
	var out bytes.Buffer
	cmd.Stdout = &out
	err := cmd.Run()
	if err != nil {
		Log(fmt.Sprintf("exec='error' message='%v'", err), "info")
		return false
	}
	return true
}

// GenerateLockReason creates a reason with filename, username and date.
func GenerateLockReason() string {
	reason := fmt.Sprintf("No reason given for '%s' by '%s' at '%s'.", FiletoLock, GetCurrentUsername(), ReturnCurrentUTC())
	return reason
}

// LockFile sets a key in Consul so that a particular file won't be updated. See commands/lock.go
func LockFile(key string) bool {
	c, err := Connect(ConsulServer, Token)
	if err != nil {
		LogFatal("Could not connect to Consul.", key, "consul_connect")
	}
	saved := Set(c, key, LockReason)
	if saved {
		StatsdLock(key)
		return true
	}
	return false
}

// UnlockFile removes a key in Consul so that a particular file can be updated. See commands/unlock.go
func UnlockFile(key string) bool {
	c, err := Connect(ConsulServer, Token)
	if err != nil {
		LogFatal("copy: Could not connect to Consul.", key, "consul_connect")
	}
	value := Del(c, key)
	StatsdUnlock(key)
	return value
}
<commit_msg>If there's an error with UNIX diff - return an error message.<commit_after>// +build linux darwin freebsd

package commands

import (
	"bytes"
	"crypto/sha256"
	"fmt"
	"io/ioutil"
	"net/http"
	"os/exec"
	"strings"
)

func init() {
	// Nothing happens here.
}

// LengthCheck makes sure a string has at least minLength lines.
func LengthCheck(data string, minLength int) bool {
	length := LineCount(data)
	Log(fmt.Sprintf("length='%d' minLength='%d'", length, minLength), "debug")
	if length >= minLength {
		return true
	}
	return false
}

// ReadURL grabs a URL and returns the string from the body.
func ReadURL(url string) string {
	resp, err := http.Get(url)
	if err != nil {
		Log(fmt.Sprintf("function='ReadURL' panic='true' url='%s'", url), "info")
		fmt.Printf("Panic: Could not open URL: '%s'\n", url)
		StatsdPanic(url, "read_url")
	}
	defer resp.Body.Close()
	body, _ := ioutil.ReadAll(resp.Body)
	return string(body)
}

// LineCount splits a string by linebreak and returns the number of lines.
func LineCount(data string) int {
	var length int
	if strings.ContainsAny(data, "\n") {
		length = strings.Count(data, "\n")
	} else {
		length = 1
	}
	return length
}

// ComputeChecksum takes a string and computes a SHA256 checksum.
func ComputeChecksum(data string) string {
	dataBytes := []byte(data)
	computedChecksum := sha256.Sum256(dataBytes)
	finalChecksum := fmt.Sprintf("%x", computedChecksum)
	Log(fmt.Sprintf("computedChecksum='%s'", finalChecksum), "debug")
	if finalChecksum == "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" {
		Log("WARNING: That checksum means the data/key is blank. WARNING", "info")
	}
	return finalChecksum
}

// ChecksumCompare takes a string, generates a SHA256 checksum and compares
// against the passed checksum to see if they match.
func ChecksumCompare(data string, checksum string) bool {
	computedChecksum := ComputeChecksum(data)
	Log(fmt.Sprintf("checksum='%s' computedChecksum='%s'", checksum, computedChecksum), "debug")
	if strings.TrimSpace(computedChecksum) == strings.TrimSpace(checksum) {
		return true
	}
	return false
}

// UnixDiff runs diff to generate text for the Datadog events.
func UnixDiff(old, new string) string {
	diff, err := exec.Command("diff", "-u", old, new).Output()
	if err != nil {
		return "There was an error generating the diff."
	}
	text := string(diff)
	finalText := removeLines(text, 3)
	return finalText
}

// removeLines trims the top n number of lines from a string.
func removeLines(text string, number int) string {
	lines := strings.Split(text, "\n")
	var cleaned []string
	cleaned = append(cleaned, lines[number:]...)
	finalText := strings.Join(cleaned, "\n")
	return finalText
}

// RunCommand runs a cli command with arguments.
func RunCommand(command string) bool {
	parts := strings.Fields(command)
	cli := parts[0]
	args := parts[1:len(parts)]
	cmd := exec.Command(cli, args...)
	var out bytes.Buffer
	cmd.Stdout = &out
	err := cmd.Run()
	if err != nil {
		Log(fmt.Sprintf("exec='error' message='%v'", err), "info")
		return false
	}
	return true
}

// GenerateLockReason creates a reason with filename, username and date.
func GenerateLockReason() string {
	reason := fmt.Sprintf("No reason given for '%s' by '%s' at '%s'.", FiletoLock, GetCurrentUsername(), ReturnCurrentUTC())
	return reason
}

// LockFile sets a key in Consul so that a particular file won't be updated. See commands/lock.go
func LockFile(key string) bool {
	c, err := Connect(ConsulServer, Token)
	if err != nil {
		LogFatal("Could not connect to Consul.", key, "consul_connect")
	}
	saved := Set(c, key, LockReason)
	if saved {
		StatsdLock(key)
		return true
	}
	return false
}

// UnlockFile removes a key in Consul so that a particular file can be updated. See commands/unlock.go
func UnlockFile(key string) bool {
	c, err := Connect(ConsulServer, Token)
	if err != nil {
		LogFatal("copy: Could not connect to Consul.", key, "consul_connect")
	}
	value := Del(c, key)
	StatsdUnlock(key)
	return value
}
<|endoftext|>


<commit_before>package plugin

import (
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/mackerelio/mkr/logger"
	"github.com/mholt/archiver"
	"github.com/pkg/errors"
	"gopkg.in/urfave/cli.v1"
)

// The reason why an immediate function, not `init()` is used here is that
// the `defaultPluginInstallLocation` is used in following `commandPluginInstall`
// assignment. Top level variable assignment is executed before `init()`.
var defaultPluginInstallLocation = func() string {
	if runtime.GOOS != "windows" {
		return "/opt/mackerel-agent/plugins"
	}
	path, err := os.Executable()
	logger.DieIf(err)
	return filepath.Join(filepath.Dir(path), "plugins")
}()

var commandPluginInstall = cli.Command{
	Name:      "install",
	Usage:     "Install a plugin from github or plugin registry",
	ArgsUsage: "[--prefix <prefix>] [--overwrite] <install_target>",
	Action:    doPluginInstall,
	Flags: []cli.Flag{
		cli.StringFlag{
			Name:  "prefix",
			Usage: fmt.Sprintf("Plugin install location. The default is %s", defaultPluginInstallLocation),
		},
		cli.BoolFlag{
			Name:  "overwrite",
			Usage: "Overwrite a plugin command in a plugin directory, even if same name command exists",
		},
	},
	Description: `
    Install a mackerel plugin and a check plugin from github or plugin registry.
    To install by mkr, a plugin has to be released to Github Releases in specification format.

    <install_target> is:
    - <owner>/<repo>[@<release_tag>]
          Install from specified github owner, repository, and Github Releases tag.
          If you omit <release_tag>, the installer install from latest Github Release.
          Example: mkr plugin install mackerelio/mackerel-plugin-sample@v0.0.1
    - <plugin_name>[@<release_tag]
          Install from plugin registry.
          You can find available plugins in https://github.com/mackerelio/plugin-registry
          Example: mkr plugin install mackerel-plugin-sample

    The installer uses Github API to find the latest release.  Please set a github token to
    GITHUB_TOKEN environment variable, or to github.token in .gitconfig.
    Otherwise, installation sometimes fails because of Github API Rate Limit.

    If you want to use the plugin installer by a server provisioning tool,
    we recommend you to specify <release_tag> explicitly.
    If you specify <release_tag>, the installer doesn't use Github API,
    so Github API Rate Limit error doesn't occur.
`,
}

var isWin = runtime.GOOS == "windows"

// main function for mkr plugin install
func doPluginInstall(c *cli.Context) error {
	argInstallTarget := c.Args().First()
	if argInstallTarget == "" {
		return fmt.Errorf("Specify install target")
	}

	it, err := newInstallTargetFromString(argInstallTarget)
	if err != nil {
		return errors.Wrap(err, "Failed to install plugin while parsing install target")
	}

	pluginDir, err := setupPluginDir(c.String("prefix"))
	if err != nil {
		return errors.Wrap(err, "Failed to install plugin while setup plugin directory")
	}

	// Create a work directory for downloading and extracting an artifact
	workdir, err := ioutil.TempDir(filepath.Join(pluginDir, "work"), "mkr-plugin-installer-")
	if err != nil {
		return errors.Wrap(err, "Failed to install plugin while creating a work directory")
	}
	defer os.RemoveAll(workdir)

	// Download an artifact and install by it
	downloadURL, err := it.makeDownloadURL()
	if err != nil {
		return errors.Wrap(err, "Failed to install plugin while making a download URL")
	}
	artifactFile, err := downloadPluginArtifact(downloadURL, workdir)
	if err != nil {
		return errors.Wrap(err, "Failed to install plugin while downloading an artifact")
	}
	err = installByArtifact(artifactFile, filepath.Join(pluginDir, "bin"), workdir, c.Bool("overwrite"))
	if err != nil {
		return errors.Wrap(err, "Failed to install plugin while extracting and placing")
	}

	logger.Log("", fmt.Sprintf("Successfully installed %s", argInstallTarget))
	return nil
}

// Create a directory for plugin install
func setupPluginDir(pluginDir string) (string, error) {
	if pluginDir == "" {
		pluginDir = defaultPluginInstallLocation
	}
	err := os.MkdirAll(filepath.Join(pluginDir, "bin"), 0755)
	if err != nil {
		return "", err
	}
	err = os.MkdirAll(filepath.Join(pluginDir, "work"), 0755)
	if err != nil {
		return "", err
	}
	return pluginDir, nil
}

// Download plugin artifact from `u`(URL) to `workdir`,
// and returns downloaded filepath
func downloadPluginArtifact(u, workdir string) (fpath string, err error) {
	logger.Log("", fmt.Sprintf("Downloading %s", u))

	// Create request to download
	resp, err := (&client{}).get(u)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	// fpath is filepath where artifact will be saved
	fpath = filepath.Join(workdir, path.Base(u))

	// download artifact
	file, err := os.OpenFile(fpath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
	if err != nil {
		return "", err
	}
	defer file.Close()

	_, err = io.Copy(file, resp.Body)
	if err != nil {
		return "", err
	}

	return fpath, nil
}

// Extract artifact and install plugin
func installByArtifact(artifactFile, bindir, workdir string, overwrite bool) error {
	// unzip artifact to work directory
	fn := archiver.Zip.Open
	if strings.HasSuffix(artifactFile, ".tar.gz") || strings.HasSuffix(artifactFile, ".tgz") {
		fn = archiver.TarGz.Open
	}
	err := fn(artifactFile, workdir)
	if err != nil {
		return err
	}

	// Look for plugin files recursively, and place those to binPath
	return filepath.Walk(workdir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if info.IsDir() {
			return nil
		}

		// a plugin file should be executable, and have specified name.
		name := info.Name()
		isExecutable := isWin || (info.Mode()&0111) != 0
		if isExecutable && looksLikePlugin(name) {
			return placePlugin(path, filepath.Join(bindir, name), overwrite)
		}
		// `path` is a file but not plugin.
		return nil
	})
}

func looksLikePlugin(name string) bool {
	return strings.HasPrefix(name, "check-") || strings.HasPrefix(name, "mackerel-plugin-")
}

func placePlugin(src, dest string, overwrite bool) error {
	_, err := os.Stat(dest)
	if err == nil && !overwrite {
		logger.Log("", fmt.Sprintf("%s already exists. Skip installing for now", dest))
		return nil
	}
	logger.Log("", fmt.Sprintf("Installing %s", dest))
	return os.Rename(src, dest)
}
<commit_msg>Add link to plugin documents<commit_after>package plugin

import (
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/mackerelio/mkr/logger"
	"github.com/mholt/archiver"
	"github.com/pkg/errors"
	"gopkg.in/urfave/cli.v1"
)

// The reason why an immediate function, not `init()` is used here is that
// the `defaultPluginInstallLocation` is used in following `commandPluginInstall`
// assignment. Top level variable assignment is executed before `init()`.
var defaultPluginInstallLocation = func() string {
	if runtime.GOOS != "windows" {
		return "/opt/mackerel-agent/plugins"
	}
	path, err := os.Executable()
	logger.DieIf(err)
	return filepath.Join(filepath.Dir(path), "plugins")
}()

var commandPluginInstall = cli.Command{
	Name:      "install",
	Usage:     "Install a plugin from github or plugin registry",
	ArgsUsage: "[--prefix <prefix>] [--overwrite] <install_target>",
	Action:    doPluginInstall,
	Flags: []cli.Flag{
		cli.StringFlag{
			Name:  "prefix",
			Usage: fmt.Sprintf("Plugin install location. The default is %s", defaultPluginInstallLocation),
		},
		cli.BoolFlag{
			Name:  "overwrite",
			Usage: "Overwrite a plugin command in a plugin directory, even if same name command exists",
		},
	},
	Description: `
    Install a mackerel plugin and a check plugin from github or plugin registry.
    To install by mkr, a plugin has to be released to Github Releases in specification format.

    <install_target> is:
    - <owner>/<repo>[@<release_tag>]
          Install from specified github owner, repository, and Github Releases tag.
          If you omit <release_tag>, the installer install from latest Github Release.
          Example: mkr plugin install mackerelio/mackerel-plugin-sample@v0.0.1
    - <plugin_name>[@<release_tag]
          Install from plugin registry.
          You can find available plugins in https://github.com/mackerelio/plugin-registry
          Example: mkr plugin install mackerel-plugin-sample

    The installer uses Github API to find the latest release.  Please set a github token to
    GITHUB_TOKEN environment variable, or to github.token in .gitconfig.
    Otherwise, installation sometimes fails because of Github API Rate Limit.

    If you want to use the plugin installer by a server provisioning tool,
    we recommend you to specify <release_tag> explicitly.
    If you specify <release_tag>, the installer doesn't use Github API,
    so Github API Rate Limit error doesn't occur.

    Please refer to following documents for detail.
    - Using mkr plugin install
      https://mackerel.io/docs/entry/advanced/install-plugin-by-mkr
    - Creating plugins supported with mkr plugin install
      https://mackerel.io/docs/entry/advanced/make-plugin-corresponding-to-installer
`,
}

var isWin = runtime.GOOS == "windows"

// main function for mkr plugin install
func doPluginInstall(c *cli.Context) error {
	argInstallTarget := c.Args().First()
	if argInstallTarget == "" {
		return fmt.Errorf("Specify install target")
	}

	it, err := newInstallTargetFromString(argInstallTarget)
	if err != nil {
		return errors.Wrap(err, "Failed to install plugin while parsing install target")
	}

	pluginDir, err := setupPluginDir(c.String("prefix"))
	if err != nil {
		return errors.Wrap(err, "Failed to install plugin while setup plugin directory")
	}

	// Create a work directory for downloading and extracting an artifact
	workdir, err := ioutil.TempDir(filepath.Join(pluginDir, "work"), "mkr-plugin-installer-")
	if err != nil {
		return errors.Wrap(err, "Failed to install plugin while creating a work directory")
	}
	defer os.RemoveAll(workdir)

	// Download an artifact and install by it
	downloadURL, err := it.makeDownloadURL()
	if err != nil {
		return errors.Wrap(err, "Failed to install plugin while making a download URL")
	}
	artifactFile, err := downloadPluginArtifact(downloadURL, workdir)
	if err != nil {
		return errors.Wrap(err, "Failed to install plugin while downloading an artifact")
	}
	err = installByArtifact(artifactFile, filepath.Join(pluginDir, "bin"), workdir, c.Bool("overwrite"))
	if err != nil {
		return errors.Wrap(err, "Failed to install plugin while extracting and placing")
	}

	logger.Log("", fmt.Sprintf("Successfully installed %s", argInstallTarget))
	return nil
}

// Create a directory for plugin install
func setupPluginDir(pluginDir string) (string, error) {
	if pluginDir == "" {
		pluginDir = defaultPluginInstallLocation
	}
	err := os.MkdirAll(filepath.Join(pluginDir, "bin"), 0755)
	if err != nil {
		return "", err
	}
	err = os.MkdirAll(filepath.Join(pluginDir, "work"), 0755)
	if err != nil {
		return "", err
	}
	return pluginDir, nil
}

// Download plugin artifact from `u`(URL) to `workdir`,
// and returns downloaded filepath
func downloadPluginArtifact(u, workdir string) (fpath string, err error) {
	logger.Log("", fmt.Sprintf("Downloading %s", u))

	// Create request to download
	resp, err := (&client{}).get(u)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	// fpath is filepath where artifact will be saved
	fpath = filepath.Join(workdir, path.Base(u))

	// download artifact
	file, err := os.OpenFile(fpath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
	if err != nil {
		return "", err
	}
	defer file.Close()

	_, err = io.Copy(file, resp.Body)
	if err != nil {
		return "", err
	}

	return fpath, nil
}

// Extract artifact and install plugin
func installByArtifact(artifactFile, bindir, workdir string, overwrite bool) error {
	// unzip artifact to work directory
	fn := archiver.Zip.Open
	if strings.HasSuffix(artifactFile, ".tar.gz") || strings.HasSuffix(artifactFile, ".tgz") {
		fn = archiver.TarGz.Open
	}
	err := fn(artifactFile, workdir)
	if err != nil {
		return err
	}

	// Look for plugin files recursively, and place those to binPath
	return filepath.Walk(workdir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if info.IsDir() {
			return nil
		}

		// a plugin file should be executable, and have specified name.
		name := info.Name()
		isExecutable := isWin || (info.Mode()&0111) != 0
		if isExecutable && looksLikePlugin(name) {
			return placePlugin(path, filepath.Join(bindir, name), overwrite)
		}
		// `path` is a file but not plugin.
		return nil
	})
}

func looksLikePlugin(name string) bool {
	return strings.HasPrefix(name, "check-") || strings.HasPrefix(name, "mackerel-plugin-")
}

func placePlugin(src, dest string, overwrite bool) error {
	_, err := os.Stat(dest)
	if err == nil && !overwrite {
		logger.Log("", fmt.Sprintf("%s already exists. Skip installing for now", dest))
		return nil
	}
	logger.Log("", fmt.Sprintf("Installing %s", dest))
	return os.Rename(src, dest)
}
<|endoftext|>


<commit_before>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

// Package cron provides a wrapper of robfig/cron, which manages schedule cron jobs for horologium
package cron

import (
	"fmt"
	"strings"
	"sync"

	"github.com/robfig/cron"
	"github.com/sirupsen/logrus"

	"k8s.io/test-infra/prow/config"
)

// jobStatus is a cache layer for tracking existing cron jobs
type jobStatus struct {
	// entryID is a unique-identifier for each cron entry generated from cronAgent
	entryID cron.EntryID
	// triggered marks if a job has been triggered for the next cron.QueuedJobs() call
	triggered bool
	// cronStr is a cache for job's cron status
	// cron entry will be regenerated if cron string changes from the periodic job
	cronStr string
}

// Cron is a wrapper for cron.Cron
type Cron struct {
	cronAgent *cron.Cron
	jobs      map[string]*jobStatus
	lock      sync.Mutex
}

// New makes a new Cron object
func New() *Cron {
	return &Cron{
		cronAgent: cron.New(),
		jobs:      map[string]*jobStatus{},
	}
}

// Start kicks off current cronAgent scheduler
func (c *Cron) Start() {
	c.cronAgent.Start()
}

// Stop pauses current cronAgent scheduler
func (c *Cron) Stop() {
	c.cronAgent.Stop()
}

// QueuedJobs returns a list of jobs that need to be triggered
// and reset trigger in jobStatus
func (c *Cron) QueuedJobs() []string {
	c.lock.Lock()
	defer c.lock.Unlock()

	res := []string{}
	for k, v := range c.jobs {
		if v.triggered {
			res = append(res, k)
		}
		c.jobs[k].triggered = false
	}
	return res
}

// SyncConfig syncs current cronAgent with current prow config
// which add/delete jobs accordingly.
func (c *Cron) SyncConfig(cfg *config.Config) error {
	c.lock.Lock()
	defer c.lock.Unlock()

	for _, p := range cfg.Periodics {
		if err := c.addPeriodic(p); err != nil {
			return err
		}
	}

	exist := map[string]bool{}
	for _, p := range cfg.AllPeriodics() {
		exist[p.Name] = true
	}

	for k := range c.jobs {
		if _, ok := exist[k]; !ok {
			defer c.removeJob(k)
		}
	}

	return nil
}

// HasJob returns if a job has been scheduled in cronAgent or not
func (c *Cron) HasJob(name string) bool {
	c.lock.Lock()
	defer c.lock.Unlock()

	_, ok := c.jobs[name]
	return ok
}

func (c *Cron) addPeriodic(p config.Periodic) error {
	if p.Cron == "" {
		return nil
	}

	if job, ok := c.jobs[p.Name]; ok {
		if job.cronStr == p.Cron {
			return nil
		}
		// job updated, remove old entry
		if err := c.removeJob(p.Name); err != nil {
			return err
		}
	}

	if err := c.addJob(p.Name, p.Cron); err != nil {
		return err
	}

	for _, ras := range p.RunAfterSuccess {
		if err := c.addPeriodic(ras); err != nil {
			return err
		}
	}

	return nil
}

// addJob adds a cron entry for a job to cronAgent
func (c *Cron) addJob(name, cron string) error {
	id, err := c.cronAgent.AddFunc(cron, func() {
		c.lock.Lock()
		defer c.lock.Unlock()

		c.jobs[name].triggered = true
		logrus.Infof("triggering cron job %s", name)
	})

	if err != nil {
		return fmt.Errorf("cronAgent fails to add job %s with cron %s: %v", name, cron, err)
	}

	c.jobs[name] = &jobStatus{
		entryID: id,
		// try to kick of a periodic trigger right away
		triggered: strings.HasPrefix(cron, "@every"),
	}

	logrus.Infof("Added new cron job %s with trigger %s", name, cron)
	return nil
}

// removeJob removes the job from cronAgent
func (c *Cron) removeJob(name string) error {
	job, ok := c.jobs[name]
	if !ok {
		return fmt.Errorf("job %s has not been added to cronAgent yet", name)
	}
	c.cronAgent.Remove(job.entryID)
	delete(c.jobs, name)
	logrus.Infof("Removed previous cron job %s", name)
	return nil
}
<commit_msg>Unify logging<commit_after>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

// Package cron provides a wrapper of robfig/cron, which manages schedule cron jobs for horologium
package cron

import (
	"fmt"
	"strings"
	"sync"

	"github.com/robfig/cron"
	"github.com/sirupsen/logrus"

	"k8s.io/test-infra/prow/config"
)

// jobStatus is a cache layer for tracking existing cron jobs
type jobStatus struct {
	// entryID is a unique-identifier for each cron entry generated from cronAgent
	entryID cron.EntryID
	// triggered marks if a job has been triggered for the next cron.QueuedJobs() call
	triggered bool
	// cronStr is a cache for job's cron status
	// cron entry will be regenerated if cron string changes from the periodic job
	cronStr string
}

// Cron is a wrapper for cron.Cron
type Cron struct {
	cronAgent *cron.Cron
	jobs      map[string]*jobStatus
	logger    *logrus.Entry
	lock      sync.Mutex
}

// New makes a new Cron object
func New() *Cron {
	return &Cron{
		cronAgent: cron.New(),
		jobs:      map[string]*jobStatus{},
		logger:    logrus.WithField("client", "cron"),
	}
}

// Start kicks off current cronAgent scheduler
func (c *Cron) Start() {
	c.cronAgent.Start()
}

// Stop pauses current cronAgent scheduler
func (c *Cron) Stop() {
	c.cronAgent.Stop()
}

// QueuedJobs returns a list of jobs that need to be triggered
// and reset trigger in jobStatus
func (c *Cron) QueuedJobs() []string {
	c.lock.Lock()
	defer c.lock.Unlock()

	res := []string{}
	for k, v := range c.jobs {
		if v.triggered {
			res = append(res, k)
		}
		c.jobs[k].triggered = false
	}
	return res
}

// SyncConfig syncs current cronAgent with current prow config
// which add/delete jobs accordingly.
func (c *Cron) SyncConfig(cfg *config.Config) error {
	c.lock.Lock()
	defer c.lock.Unlock()

	for _, p := range cfg.Periodics {
		if err := c.addPeriodic(p); err != nil {
			return err
		}
	}

	exist := map[string]bool{}
	for _, p := range cfg.AllPeriodics() {
		exist[p.Name] = true
	}

	for k := range c.jobs {
		if _, ok := exist[k]; !ok {
			defer c.removeJob(k)
		}
	}

	return nil
}

// HasJob returns if a job has been scheduled in cronAgent or not
func (c *Cron) HasJob(name string) bool {
	c.lock.Lock()
	defer c.lock.Unlock()

	_, ok := c.jobs[name]
	return ok
}

func (c *Cron) addPeriodic(p config.Periodic) error {
	if p.Cron == "" {
		return nil
	}

	if job, ok := c.jobs[p.Name]; ok {
		if job.cronStr == p.Cron {
			return nil
		}
		// job updated, remove old entry
		if err := c.removeJob(p.Name); err != nil {
			return err
		}
	}

	if err := c.addJob(p.Name, p.Cron); err != nil {
		return err
	}

	for _, ras := range p.RunAfterSuccess {
		if err := c.addPeriodic(ras); err != nil {
			return err
		}
	}

	return nil
}

// addJob adds a cron entry for a job to cronAgent
func (c *Cron) addJob(name, cron string) error {
	id, err := c.cronAgent.AddFunc(cron, func() {
		c.lock.Lock()
		defer c.lock.Unlock()

		c.jobs[name].triggered = true
		c.logger.Infof("Triggering cron job %s.", name)
	})

	if err != nil {
		return fmt.Errorf("cronAgent fails to add job %s with cron %s: %v", name, cron, err)
	}

	c.jobs[name] = &jobStatus{
		entryID: id,
		// try to kick of a periodic trigger right away
		triggered: strings.HasPrefix(cron, "@every"),
	}

	c.logger.Infof("Added new cron job %s with trigger %s.", name, cron)
	return nil
}

// removeJob removes the job from cronAgent
func (c *Cron) removeJob(name string) error {
	job, ok := c.jobs[name]
	if !ok {
		return fmt.Errorf("job %s has not been added to cronAgent yet", name)
	}
	c.cronAgent.Remove(job.entryID)
	delete(c.jobs, name)
	c.logger.Infof("Removed previous cron job %s.", name)
	return nil
}
<|endoftext|>


<commit_before>package models

import (
	"database/sql"
	"errors"
	"fmt"
	sq "github.com/Masterminds/squirrel"
	log "github.com/Sirupsen/logrus"
	"github.com/guregu/null"
	"github.com/lib/pq"
	"net/http"
	"strings"
	"time"
)

// Returned by a function returning only one listing (usually by ID)
type Listing struct {
	KeyID                int            `json:"keyId"`
	CreationDate         null.Time      `json:"creationDate"`
	LastModificationDate null.Time      `json:"lastModificationDate"`
	Title                string         `json:"title"`
	Description          null.String    `json:"description"`
	UserID               int            `json:"userId"`
	Username             null.String    `json:"username"`
	Price                null.Int       `json:"price"`
	Status               null.String    `json:"status"`
	ExpirationDate       null.Time      `json:"expirationDate"`
	Thumbnail            null.String    `json:"thumbnail"`
	Photos               pq.StringArray `json:"photos"`
	IsStarred            bool           `json:"isStarred"`
}

type listingQuery struct {
	Query            string
	OnlyStarred      bool
	OnlyMine         bool
	TruncationLength int    // number of characters to truncate listing descriptions to
	Limit            uint64 // maximum number of listings to return
	Offset           uint64 // offset in search results to send
	UserID           int
	MinPrice         int
	MaxPrice         int
	MinExpDate       time.Time
	MaxExpDate       time.Time
	MinCreateDate    time.Time
	MaxCreateDate    time.Time
}

func NewListingQuery() *listingQuery {
	q := new(listingQuery)
	q.TruncationLength = defaultTruncationLength
	q.Limit = defaultNumResults
	q.MinPrice = -1
	q.MaxPrice = -1
	return q
}

type IsStarred struct {
	IsStarred bool `json:"isStarred"`
}

// Returns the SQL query that returns true if a particular listing is starred
// by the user with key_id id. This method exists because dealing with nested
// SQL queries in squirrel is an ugly pain in the ass.
func isStarredBy(id int) string {
	// "But Perry!" you say,
	// "concatenating strings and putting it directly in an SQL query is bad!"
	// you say.
	// "You're exactly correct, of course. Unfortunately we need a nested sql
	// query here and I couldn't find any documentation for nested queries
	// using squirrel. (Or any other documentation on squirrel other than the
	// godocs). On the bright side, we're not actually opening ourselves to an
	// injection attack since id is guaranteed to be an int."
	// "But isn't this still annoying and ugly?"
	// "Yeah."
	return fmt.Sprint("exists( SELECT 1 FROM starred_listings",
		" WHERE starred_listings.listing_id=listings.key_id",
		" AND starred_listings.user_id=", id,
		" AND starred_listings.is_active)")
}

// Returns the most recent count listings, based on original date created.
// If queryStr is nonempty, filters that every returned item must have every word in either title or description
// On error, returns an error and the HTTP code associated with that error.
func ReadListings(db *sql.DB, query *listingQuery) ([]*Listing, error, int) {
	// Create listings statement
	stmt := psql.
		Select(
			"listings.key_id",
			"listings.creation_date",
			"listings.last_modification_date",
			"title",
			fmt.Sprintf("left(description, %d)", query.TruncationLength),
			"user_id",
			"users.net_id",
			"price",
			"status",
			"expiration_date",
			"thumbnail_url",
			isStarredBy(query.UserID),
			"photos",
		).
		From("listings").
		Where("listings.is_active=true").
		LeftJoin("users ON listings.user_id = users.key_id")

	for _, word := range strings.Fields(query.Query) {
		stmt = stmt.Where("(lower(listings.title) LIKE lower(?) OR lower(listings.description) LIKE lower(?))", fmt.Sprint("%", word, "%"), fmt.Sprint("%", word, "%"))
	}

	if query.UserID == 0 && (query.OnlyStarred || query.OnlyMine) {
		return nil, errors.New("Unauthenticated user attempted to view profile data"), http.StatusUnauthorized
	}

	if query.MinPrice >= 0 {
		stmt = stmt.Where("listings.price >= ?", query.MinPrice)
	}

	if query.MaxPrice >= 0 {
		stmt = stmt.Where("listings.price <= ?", query.MaxPrice)
	}

	if !query.MinExpDate.IsZero() {
		stmt = stmt.Where("listings.expiration_date >= ? OR listings.expiration_date IS NULL", query.MinExpDate)
	}

	if !query.MaxExpDate.IsZero() {
		stmt = stmt.Where("listings.expiration_date <= ?", query.MaxExpDate)
	}

	if !query.MinCreateDate.IsZero() {
		stmt = stmt.Where("listings.creation_date >= ? OR listings.creation_date IS NULL", query.MinCreateDate)
	}

	if !query.MaxCreateDate.IsZero() {
		stmt = stmt.Where("listings.creation_date <= ?", query.MaxCreateDate)
	}

	if query.OnlyStarred {
		stmt = stmt.Where(isStarredBy(query.UserID))
	}

	if query.OnlyMine {
		stmt = stmt.Where(sq.Eq{"user_id": query.UserID})
	}

	stmt = stmt.OrderBy("listings.creation_date DESC")
	if query.Limit > defaultNumResults {
		stmt = stmt.Limit(query.Limit)
	} else {
		stmt = stmt.Limit(defaultNumResults)
	}
	stmt = stmt.Offset(query.Offset)

	queryStr, _, _ := stmt.ToSql()
	log.WithField("query", queryStr).Debug("query!")

	// Query db
	rows, err := stmt.RunWith(db).Query()
	if err != nil {
		return nil, err, http.StatusInternalServerError
	}
	defer rows.Close()

	// Populate listing structs
	listings := make([]*Listing, 0)
	for rows.Next() {
		l := new(Listing)
		err := rows.Scan(
			&l.KeyID,
			&l.CreationDate,
			&l.LastModificationDate,
			&l.Title,
			&l.Description,
			&l.UserID,
			&l.Username,
			&l.Price,
			&l.Status,
			&l.ExpirationDate,
			&l.Thumbnail,
			&l.IsStarred,
			&l.Photos,
		)
		if err != nil {
			return nil, err, http.StatusInternalServerError
		}
		listings = append(listings, l)
	}

	if err = rows.Err(); err != nil {
		return nil, err, http.StatusInternalServerError
	}

	return listings, nil, http.StatusOK
}

// Returns the most recent count listings, based on original date created. On error
// returns an error and the HTTP code associated with that error.
func ReadListing(db *sql.DB, id string) (Listing, error, int) {
	var listing Listing

	// Create listing query
	query := psql.
		Select(
			"listings.key_id",
			"listings.creation_date",
			"listings.last_modification_date",
			"title",
			"description",
			"user_id",
			"users.net_id",
			"price",
			"status",
			"expiration_date",
			"thumbnail_url",
			"photos",
		).
		From("listings").
		Where("listings.is_active=true").
		LeftJoin("users ON listings.user_id = users.key_id").
		Where(sq.Eq{"listings.key_id": id})

	// Query db for listing
	rows, err := query.RunWith(db).Query()
	if err != nil {
		return listing, err, http.StatusInternalServerError
	}
	defer rows.Close()

	// Populate listing struct
	rows.Next()
	err = rows.Scan(
		&listing.KeyID,
		&listing.CreationDate,
		&listing.LastModificationDate,
		&listing.Title,
		&listing.Description,
		&listing.UserID,
		&listing.Username,
		&listing.Price,
		&listing.Status,
		&listing.ExpirationDate,
		&listing.Thumbnail,
		&listing.Photos,
	)
	if err == sql.ErrNoRows {
		return listing, err, http.StatusNotFound
	} else if err != nil {
		return listing, err, http.StatusInternalServerError
	}

	return listing, nil, http.StatusOK
}

// Inserts the given listing (belonging to userId) into the database. Returns
// listing with its new KeyID added.
func CreateListing(db *sql.DB, listing Listing, userId int) (Listing, error, int) {
	listing.UserID = userId

	// Insert listing
	stmt := psql.Insert("listings").
		Columns("title", "description", "user_id", "price", "status",
			"expiration_date", "thumbnail_url", "photos").
		Values(listing.Title, listing.Description, userId, listing.Price,
			listing.Status, listing.ExpirationDate, listing.Thumbnail, listing.Photos).
		Suffix("RETURNING key_id, creation_date")

	// Add listing to database, retrieve the one we just added (now with a key_id)
	rows, err := stmt.RunWith(db).Query()
	if err != nil {
		return listing, err, http.StatusInternalServerError
	}
	defer rows.Close()

	// Populate listing struct
	rows.Next()
	err = rows.Scan(&listing.KeyID, &listing.CreationDate)
	if err != nil {
		return listing, err, http.StatusInternalServerError
	}

	return listing, nil, http.StatusCreated
}

// Overwrites the listing in the database with the given id with the given listing
// (belonging to userId). Returns the updated listing.
func UpdateListing(db *sql.DB, id string, listing Listing, userId int) (error, int) {
	listing.UserID = userId

	// Update listing
	stmt := psql.Update("listings").
		SetMap(map[string]interface{}{
			"title":           listing.Title,
			"description":     listing.Description,
			"price":           listing.Price,
			"status":          listing.Status,
			"expiration_date": listing.ExpirationDate,
			"thumbnail_url":   listing.Thumbnail,
			"photos":          listing.Photos}).
		Where(sq.Eq{"listings.key_id": id,
			"listings.user_id": userId})

	// Update listing
	result, err := stmt.RunWith(db).Exec()
	return getUpdateResultCode(result, err)
}

// Deletes the listing in the database with the given id with the given listing
// (belonging to userId).
func DeleteListing(db *sql.DB, id string, userId int) (error, int) {
	// Update listing
	stmt := psql.Delete("listings").
		Where(sq.Eq{"listings.key_id": id,
			"listings.user_id": userId})

	// Query db for listing
	result, err := stmt.RunWith(db).Exec()
	return getUpdateResultCode(result, err)
}

// SetStar adds or removes a star, depending on whether add is set to true.
func SetStar(db *sql.DB, add bool, listingId string, userId int) (error, int) {
	if add {
		return addStar(db, listingId, userId)
	} else {
		return removeStar(db, listingId, userId)
	}
}

// addStar adds a star to the table for the given listingId and userId.
func addStar(db *sql.DB, listingId string, userId int) (error, int) {
	insertStarStmt := psql.Insert("starred_listings").
		Columns("user_id", "listing_id").
		Values(userId, listingId)

	// Query db for listing
	result, err := insertStarStmt.RunWith(db).Exec()
	return getUpdateResultCode(result, err)
}

// removeStar remvoes a star from the given listingId for a given userId.
func removeStar(db *sql.DB, listingId string, userId int) (error, int) {
	stmt := psql.Update("starred_listings").
		SetMap(map[string]interface{}{
			"is_active": false,
		}).
		Where(sq.Eq{"listing_id": listingId, "user_id": userId})

	// Query db for listing
	result, err := stmt.RunWith(db).Exec()
	return getUpdateResultCode(result, err)
}
<commit_msg>Delete instead of updating on stars<commit_after>package models

import (
	"database/sql"
	"errors"
	"fmt"
	sq "github.com/Masterminds/squirrel"
	log "github.com/Sirupsen/logrus"
	"github.com/guregu/null"
	"github.com/lib/pq"
	"net/http"
	"strings"
	"time"
)

// Returned by a function returning only one listing (usually by ID)
type Listing struct {
	KeyID                int            `json:"keyId"`
	CreationDate         null.Time      `json:"creationDate"`
	LastModificationDate null.Time      `json:"lastModificationDate"`
	Title                string         `json:"title"`
	Description          null.String    `json:"description"`
	UserID               int            `json:"userId"`
	Username             null.String    `json:"username"`
	Price                null.Int       `json:"price"`
	Status               null.String    `json:"status"`
	ExpirationDate       null.Time      `json:"expirationDate"`
	Thumbnail            null.String    `json:"thumbnail"`
	Photos               pq.StringArray `json:"photos"`
	IsStarred            bool           `json:"isStarred"`
}

type listingQuery struct {
	Query            string
	OnlyStarred      bool
	OnlyMine         bool
	TruncationLength int    // number of characters to truncate listing descriptions to
	Limit            uint64 // maximum number of listings to return
	Offset           uint64 // offset in search results to send
	UserID           int
	MinPrice         int
	MaxPrice         int
	MinExpDate       time.Time
	MaxExpDate       time.Time
	MinCreateDate    time.Time
	MaxCreateDate    time.Time
}

func NewListingQuery() *listingQuery {
	q := new(listingQuery)
	q.TruncationLength = defaultTruncationLength
	q.Limit = defaultNumResults
	q.MinPrice = -1
	q.MaxPrice = -1
	return q
}

type IsStarred struct {
	IsStarred bool `json:"isStarred"`
}

// Returns the SQL query that returns true if a particular listing is starred
// by the user with key_id id. This method exists because dealing with nested
// SQL queries in squirrel is an ugly pain in the ass.
func isStarredBy(id int) string {
	// "But Perry!" you say,
	// "concatenating strings and putting it directly in an SQL query is bad!"
	// you say.
	// "You're exactly correct, of course. Unfortunately we need a nested sql
	// query here and I couldn't find any documentation for nested queries
	// using squirrel. (Or any other documentation on squirrel other than the
	// godocs). On the bright side, we're not actually opening ourselves to an
	// injection attack since id is guaranteed to be an int."
	// "But isn't this still annoying and ugly?"
	// "Yeah."
	return fmt.Sprint("exists( SELECT 1 FROM starred_listings",
		" WHERE starred_listings.listing_id=listings.key_id",
		" AND starred_listings.user_id=", id,
		" AND starred_listings.is_active)")
}

// Returns the most recent count listings, based on original date created.
// If queryStr is nonempty, filters that every returned item must have every word in either title or description
// On error, returns an error and the HTTP code associated with that error.
func ReadListings(db *sql.DB, query *listingQuery) ([]*Listing, error, int) {
	// Create listings statement
	stmt := psql.
		Select(
			"listings.key_id",
			"listings.creation_date",
			"listings.last_modification_date",
			"title",
			fmt.Sprintf("left(description, %d)", query.TruncationLength),
			"user_id",
			"users.net_id",
			"price",
			"status",
			"expiration_date",
			"thumbnail_url",
			isStarredBy(query.UserID),
			"photos",
		).
		From("listings").
		Where("listings.is_active=true").
		LeftJoin("users ON listings.user_id = users.key_id")

	for _, word := range strings.Fields(query.Query) {
		stmt = stmt.Where("(lower(listings.title) LIKE lower(?) OR lower(listings.description) LIKE lower(?))", fmt.Sprint("%", word, "%"), fmt.Sprint("%", word, "%"))
	}

	if query.UserID == 0 && (query.OnlyStarred || query.OnlyMine) {
		return nil, errors.New("Unauthenticated user attempted to view profile data"), http.StatusUnauthorized
	}

	if query.MinPrice >= 0 {
		stmt = stmt.Where("listings.price >= ?", query.MinPrice)
	}

	if query.MaxPrice >= 0 {
		stmt = stmt.Where("listings.price <= ?", query.MaxPrice)
	}

	if !query.MinExpDate.IsZero() {
		stmt = stmt.Where("listings.expiration_date >= ? OR listings.expiration_date IS NULL", query.MinExpDate)
	}

	if !query.MaxExpDate.IsZero() {
		stmt = stmt.Where("listings.expiration_date <= ?", query.MaxExpDate)
	}

	if !query.MinCreateDate.IsZero() {
		stmt = stmt.Where("listings.creation_date >= ? OR listings.creation_date IS NULL", query.MinCreateDate)
	}

	if !query.MaxCreateDate.IsZero() {
		stmt = stmt.Where("listings.creation_date <= ?", query.MaxCreateDate)
	}

	if query.OnlyStarred {
		stmt = stmt.Where(isStarredBy(query.UserID))
	}

	if query.OnlyMine {
		stmt = stmt.Where(sq.Eq{"user_id": query.UserID})
	}

	stmt = stmt.OrderBy("listings.creation_date DESC")
	if query.Limit > defaultNumResults {
		stmt = stmt.Limit(query.Limit)
	} else {
		stmt = stmt.Limit(defaultNumResults)
	}
	stmt = stmt.Offset(query.Offset)

	queryStr, _, _ := stmt.ToSql()
	log.WithField("query", queryStr).Debug("query!")

	// Query db
	rows, err := stmt.RunWith(db).Query()
	if err != nil {
		return nil, err, http.StatusInternalServerError
	}
	defer rows.Close()

	// Populate listing structs
	listings := make([]*Listing, 0)
	for rows.Next() {
		l := new(Listing)
		err := rows.Scan(
			&l.KeyID,
			&l.CreationDate,
			&l.LastModificationDate,
			&l.Title,
			&l.Description,
			&l.UserID,
			&l.Username,
			&l.Price,
			&l.Status,
			&l.ExpirationDate,
			&l.Thumbnail,
			&l.IsStarred,
			&l.Photos,
		)
		if err != nil {
			return nil, err, http.StatusInternalServerError
		}
		listings = append(listings, l)
	}

	if err = rows.Err(); err != nil {
		return nil, err, http.StatusInternalServerError
	}

	return listings, nil, http.StatusOK
}

// Returns the most recent count listings, based on original date created. On error
// returns an error and the HTTP code associated with that error.
func ReadListing(db *sql.DB, id string) (Listing, error, int) {
	var listing Listing

	// Create listing query
	query := psql.
		Select(
			"listings.key_id",
			"listings.creation_date",
			"listings.last_modification_date",
			"title",
			"description",
			"user_id",
			"users.net_id",
			"price",
			"status",
			"expiration_date",
			"thumbnail_url",
			"photos",
		).
		From("listings").
		Where("listings.is_active=true").
		LeftJoin("users ON listings.user_id = users.key_id").
		Where(sq.Eq{"listings.key_id": id})

	// Query db for listing
	rows, err := query.RunWith(db).Query()
	if err != nil {
		return listing, err, http.StatusInternalServerError
	}
	defer rows.Close()

	// Populate listing struct
	rows.Next()
	err = rows.Scan(
		&listing.KeyID,
		&listing.CreationDate,
		&listing.LastModificationDate,
		&listing.Title,
		&listing.Description,
		&listing.UserID,
		&listing.Username,
		&listing.Price,
		&listing.Status,
		&listing.ExpirationDate,
		&listing.Thumbnail,
		&listing.Photos,
	)
	if err == sql.ErrNoRows {
		return listing, err, http.StatusNotFound
	} else if err != nil {
		return listing, err, http.StatusInternalServerError
	}

	return listing, nil, http.StatusOK
}

// Inserts the given listing (belonging to userId) into the database. Returns
// listing with its new KeyID added.
func CreateListing(db *sql.DB, listing Listing, userId int) (Listing, error, int) {
	listing.UserID = userId

	// Insert listing
	stmt := psql.Insert("listings").
		Columns("title", "description", "user_id", "price", "status",
			"expiration_date", "thumbnail_url", "photos").
		Values(listing.Title, listing.Description, userId, listing.Price,
			listing.Status, listing.ExpirationDate, listing.Thumbnail, listing.Photos).
		Suffix("RETURNING key_id, creation_date")

	// Add listing to database, retrieve the one we just added (now with a key_id)
	rows, err := stmt.RunWith(db).Query()
	if err != nil {
		return listing, err, http.StatusInternalServerError
	}
	defer rows.Close()

	// Populate listing struct
	rows.Next()
	err = rows.Scan(&listing.KeyID, &listing.CreationDate)
	if err != nil {
		return listing, err, http.StatusInternalServerError
	}

	return listing, nil, http.StatusCreated
}

// Overwrites the listing in the database with the given id with the given listing
// (belonging to userId). Returns the updated listing.
func UpdateListing(db *sql.DB, id string, listing Listing, userId int) (error, int) {
	listing.UserID = userId

	// Update listing
	stmt := psql.Update("listings").
		SetMap(map[string]interface{}{
			"title":           listing.Title,
			"description":     listing.Description,
			"price":           listing.Price,
			"status":          listing.Status,
			"expiration_date": listing.ExpirationDate,
			"thumbnail_url":   listing.Thumbnail,
			"photos":          listing.Photos}).
		Where(sq.Eq{"listings.key_id": id,
			"listings.user_id": userId})

	// Update listing
	result, err := stmt.RunWith(db).Exec()
	return getUpdateResultCode(result, err)
}

// Deletes the listing in the database with the given id with the given listing
// (belonging to userId).
func DeleteListing(db *sql.DB, id string, userId int) (error, int) {
	// Update listing
	stmt := psql.Delete("listings").
		Where(sq.Eq{"listings.key_id": id,
			"listings.user_id": userId})

	// Query db for listing
	result, err := stmt.RunWith(db).Exec()
	return getUpdateResultCode(result, err)
}

// SetStar adds or removes a star, depending on whether add is set to true.
func SetStar(db *sql.DB, add bool, listingId string, userId int) (error, int) {
	if add {
		return addStar(db, listingId, userId)
	} else {
		return removeStar(db, listingId, userId)
	}
}

// addStar adds a star to the table for the given listingId and userId.
func addStar(db *sql.DB, listingId string, userId int) (error, int) {
	insertStarStmt := psql.Insert("starred_listings").
		Columns("user_id", "listing_id").
		Values(userId, listingId)

	// Query db for listing
	result, err := insertStarStmt.RunWith(db).Exec()
	return getUpdateResultCode(result, err)
}

// removeStar remvoes a star from the given listingId for a given userId.
func removeStar(db *sql.DB, listingId string, userId int) (error, int) {
	stmt := psql.Delete("starred_listings").
		Where(sq.Eq{"listing_id": listingId, "user_id": userId})

	// Query db for listing
	result, err := stmt.RunWith(db).Exec()
	return getUpdateResultCode(result, err)
}
<|endoftext|>