<commit_before>//
// Last.Backend LLC CONFIDENTIAL
// __________________
//
// [2014] - [2018] Last.Backend LLC
// All Rights Reserved.
//
// NOTICE:  All information contained herein is, and remains
// the property of Last.Backend LLC and its suppliers,
// if any.  The intellectual and technical concepts contained
// herein are proprietary to Last.Backend LLC
// and its suppliers and may be covered by Russian Federation and Foreign Patents,
// patents in process, and are protected by trade secretCmd or copyright law.
// Dissemination of this information or reproduction of this material
// is strictly forbidden unless prior written permission is obtained
// from Last.Backend LLC.
//

package cmd

import (
	"fmt"
	"io/ioutil"
	"os"

	"github.com/lastbackend/lastbackend/pkg/api/types/v1/request"
	"github.com/lastbackend/lastbackend/pkg/cli/envs"
	"github.com/lastbackend/lastbackend/pkg/cli/view"
	"github.com/spf13/cobra"
	"gopkg.in/yaml.v2"
	"github.com/lastbackend/lastbackend/pkg/api/types/v1/views"
	"github.com/lastbackend/lastbackend/pkg/api/types/v1"
)

const applyExample = `
  # Apply manifest from file or by URL
  lb apply -f"
`

func init() {
	applyCmd.Flags().StringArrayP("file", "f", make([]string, 0), "create secret from files")
	namespaceCmd.AddCommand(applyCmd)
}

var applyCmd = &cobra.Command{
	Use:   "apply [NAME]",
	Short: "Apply file manifest to cluster",
	Args:    cobra.ExactArgs(1),
	Run: func(cmd *cobra.Command, args []string) {

		namespace := args[0]

		files, err := cmd.Flags().GetStringArray("file")
		if err != nil {
			fmt.Println(err.Error())
			return
		}

		if len(files) == 0 {
			cmd.Help()
		}

		cli := envs.Get().GetClient()

		for _, f := range files {
			c, err := ioutil.ReadFile(f)
			if err != nil {
				_ = fmt.Errorf("failed read data from file: %s", f)
				os.Exit(1)
			}
			fmt.Println(string(c))
			var m = new(request.Runtime)
			yaml.Unmarshal(c, m)

			fmt.Println("Kind:>", m.Kind)

			if m.Kind == "Service" {

				fmt.Println("Service manage")

				spec := v1.Request().Service().Manifest()
				spec.FromYaml(c)

				var rsvc *views.Service

				if m.Meta.Name != nil {
					rsvc, err = cli.V1().Namespace(namespace).Service(*m.Meta.Name).Get(envs.Background())
					if err != nil {
						fmt.Errorf(err.Error())
						//return
					}
				}

				if rsvc == nil {
					fmt.Println("create new service")
					rsvc, err = cli.V1().Namespace(namespace).Service().Create(envs.Background(), spec)
					if err != nil {
						fmt.Println(2)
						fmt.Println(err)
						return
					}
					fmt.Println(fmt.Sprintf("Service `%s` is created", m.Meta.Name))
				} else {
					fmt.Println("update service")
					rsvc, err = cli.V1().Namespace(namespace).Service(rsvc.Meta.Name).Update(envs.Background(), spec)
					if err != nil {
						fmt.Println(3)
						fmt.Println(err)
						return
					}

					fmt.Println(fmt.Sprintf("Service `%s` is updated", m.Meta.Name))
				}

				if rsvc != nil {
					fmt.Println(4)
					service := view.FromApiServiceView(rsvc)
					service.Print()
				} else {
					fmt.Println(5)
					fmt.Println("ooops")
				}

			}

			return

		}
	},
}
<commit_msg>update cli<commit_after>//
// Last.Backend LLC CONFIDENTIAL
// __________________
//
// [2014] - [2018] Last.Backend LLC
// All Rights Reserved.
//
// NOTICE:  All information contained herein is, and remains
// the property of Last.Backend LLC and its suppliers,
// if any.  The intellectual and technical concepts contained
// herein are proprietary to Last.Backend LLC
// and its suppliers and may be covered by Russian Federation and Foreign Patents,
// patents in process, and are protected by trade secretCmd or copyright law.
// Dissemination of this information or reproduction of this material
// is strictly forbidden unless prior written permission is obtained
// from Last.Backend LLC.
//

package cmd

import (
	"fmt"
	"io/ioutil"
	"os"

	"github.com/lastbackend/lastbackend/pkg/api/types/v1/request"
	"github.com/lastbackend/lastbackend/pkg/cli/envs"
	"github.com/lastbackend/lastbackend/pkg/cli/view"
	"github.com/spf13/cobra"
	"gopkg.in/yaml.v2"
	"github.com/lastbackend/lastbackend/pkg/api/types/v1/views"
	"github.com/lastbackend/lastbackend/pkg/api/types/v1"
)

const applyExample = `
  # Apply manifest from file or by URL
  lb apply -f"
`

func init() {
	applyCmd.Flags().StringArrayP("file", "f", make([]string, 0), "create secret from files")
	namespaceCmd.AddCommand(applyCmd)
}

var applyCmd = &cobra.Command{
	Use:   "apply [NAME]",
	Short: "Apply file manifest to cluster",
	Args:    cobra.ExactArgs(1),
	Run: func(cmd *cobra.Command, args []string) {

		namespace := args[0]

		files, err := cmd.Flags().GetStringArray("file")
		if err != nil {
			fmt.Println(err.Error())
			return
		}

		if len(files) == 0 {
			cmd.Help()
		}

		cli := envs.Get().GetClient()

		for _, f := range files {
			c, err := ioutil.ReadFile(f)
			if err != nil {
				_ = fmt.Errorf("failed read data from file: %s", f)
				os.Exit(1)
			}

			var m = new(request.Runtime)
			yaml.Unmarshal(c, m)

			if m.Kind == "Service" {

				spec := v1.Request().Service().Manifest()
				spec.FromYaml(c)

				var rsvc *views.Service

				if m.Meta.Name != nil {
					rsvc, _ = cli.V1().Namespace(namespace).Service(*m.Meta.Name).Get(envs.Background())
				}

				if rsvc == nil {
					fmt.Println("create new service")
					rsvc, err = cli.V1().Namespace(namespace).Service().Create(envs.Background(), spec)
					if err != nil {
						fmt.Println(err)
						return
					}
				} else {
					rsvc, err = cli.V1().Namespace(namespace).Service(rsvc.Meta.Name).Update(envs.Background(), spec)
					if err != nil {
						fmt.Println(3)
						fmt.Println(err)
						return
					}
				}

				if rsvc != nil {
					service := view.FromApiServiceView(rsvc)
					service.Print()
				} else {
					fmt.Println("ooops")
				}

			}

			return

		}
	},
}
<|endoftext|>


<commit_before>package jsonmessage

import (
	"encoding/json"
	"fmt"
	"io"
	"os"
	"strings"
	"time"

	"github.com/Nvveen/Gotty"

	"github.com/docker/docker/pkg/jsonlog"
	"github.com/docker/docker/pkg/term"
	"github.com/docker/go-units"
)

// JSONError wraps a concrete Code and Message, `Code` is
// is an integer error code, `Message` is the error message.
type JSONError struct {
	Code    int    `json:"code,omitempty"`
	Message string `json:"message,omitempty"`
}

func (e *JSONError) Error() string {
	return e.Message
}

// JSONProgress describes a Progress. terminalFd is the fd of the current terminal,
// Start is the initial value for the operation. Current is the current status and
// value of the progress made towards Total. Total is the end value describing when
// we made 100% progress for an operation.
type JSONProgress struct {
	terminalFd uintptr
	Current    int64 `json:"current,omitempty"`
	Total      int64 `json:"total,omitempty"`
	Start      int64 `json:"start,omitempty"`
}

func (p *JSONProgress) String() string {
	var (
		width       = 200
		pbBox       string
		numbersBox  string
		timeLeftBox string
	)

	ws, err := term.GetWinsize(p.terminalFd)
	if err == nil {
		width = int(ws.Width)
	}

	if p.Current <= 0 && p.Total <= 0 {
		return ""
	}
	current := units.HumanSize(float64(p.Current))
	if p.Total <= 0 {
		return fmt.Sprintf("%8v", current)
	}
	total := units.HumanSize(float64(p.Total))
	percentage := int(float64(p.Current)/float64(p.Total)*100) / 2
	if percentage > 50 {
		percentage = 50
	}
	if width > 110 {
		// this number can't be negative gh#7136
		numSpaces := 0
		if 50-percentage > 0 {
			numSpaces = 50 - percentage
		}
		pbBox = fmt.Sprintf("[%s>%s] ", strings.Repeat("=", percentage), strings.Repeat(" ", numSpaces))
	}

	numbersBox = fmt.Sprintf("%8v/%v", current, total)

	if p.Current > p.Total {
		// remove total display if the reported current is wonky.
		numbersBox = fmt.Sprintf("%8v", current)
	}

	if p.Current > 0 && p.Start > 0 && percentage < 50 {
		fromStart := time.Now().UTC().Sub(time.Unix(p.Start, 0))
		perEntry := fromStart / time.Duration(p.Current)
		left := time.Duration(p.Total-p.Current) * perEntry
		left = (left / time.Second) * time.Second

		if width > 50 {
			timeLeftBox = " " + left.String()
		}
	}
	return pbBox + numbersBox + timeLeftBox
}

// JSONMessage defines a message struct. It describes
// the created time, where it from, status, ID of the
// message. It's used for docker events.
type JSONMessage struct {
	Stream          string        `json:"stream,omitempty"`
	Status          string        `json:"status,omitempty"`
	Progress        *JSONProgress `json:"progressDetail,omitempty"`
	ProgressMessage string        `json:"progress,omitempty"` //deprecated
	ID              string        `json:"id,omitempty"`
	From            string        `json:"from,omitempty"`
	Time            int64         `json:"time,omitempty"`
	TimeNano        int64         `json:"timeNano,omitempty"`
	Error           *JSONError    `json:"errorDetail,omitempty"`
	ErrorMessage    string        `json:"error,omitempty"` //deprecated
	// Aux contains out-of-band data, such as digests for push signing.
	Aux *json.RawMessage `json:"aux,omitempty"`
}

/* Satisfied by gotty.TermInfo as well as noTermInfo from below */
type termInfo interface {
	Parse(attr string, params ...interface{}) (string, error)
}

type noTermInfo struct{} // canary used when no terminfo.

func (ti *noTermInfo) Parse(attr string, params ...interface{}) (string, error) {
	return "", fmt.Errorf("noTermInfo")
}

func clearLine(out io.Writer, ti termInfo) {
	// el2 (clear whole line) is not exposed by terminfo.

	// First clear line from beginning to cursor
	if attr, err := ti.Parse("el1"); err == nil {
		fmt.Fprintf(out, "%s", attr)
	} else {
		fmt.Fprintf(out, "%c[1K", 27)
	}
	// Then clear line from cursor to end
	if attr, err := ti.Parse("el"); err == nil {
		fmt.Fprintf(out, "%s", attr)
	} else {
		fmt.Fprintf(out, "%c[K", 27)
	}
}

func cursorUp(out io.Writer, ti termInfo, l int) {
	if l == 0 { // Should never be the case, but be tolerant
		return
	}
	if attr, err := ti.Parse("cuu", l); err == nil {
		fmt.Fprintf(out, "%s", attr)
	} else {
		fmt.Fprintf(out, "%c[%dA", 27, l)
	}
}

func cursorDown(out io.Writer, ti termInfo, l int) {
	if l == 0 { // Should never be the case, but be tolerant
		return
	}
	if attr, err := ti.Parse("cud", l); err == nil {
		fmt.Fprintf(out, "%s", attr)
	} else {
		fmt.Fprintf(out, "%c[%dB", 27, l)
	}
}

// Display displays the JSONMessage to `out`. `termInfo` is non-nil if `out`
// is a terminal. If this is the case, it will erase the entire current line
// when displaying the progressbar.
func (jm *JSONMessage) Display(out io.Writer, termInfo termInfo) error {
	if jm.Error != nil {
		if jm.Error.Code == 401 {
			return fmt.Errorf("Authentication is required.")
		}
		return jm.Error
	}
	var endl string
	if termInfo != nil && jm.Stream == "" && jm.Progress != nil {
		clearLine(out, termInfo)
		endl = "\r"
		fmt.Fprintf(out, endl)
	} else if jm.Progress != nil && jm.Progress.String() != "" { //disable progressbar in non-terminal
		return nil
	}
	if jm.TimeNano != 0 {
		fmt.Fprintf(out, "%s ", time.Unix(0, jm.TimeNano).Format(jsonlog.RFC3339NanoFixed))
	} else if jm.Time != 0 {
		fmt.Fprintf(out, "%s ", time.Unix(jm.Time, 0).Format(jsonlog.RFC3339NanoFixed))
	}
	if jm.ID != "" {
		fmt.Fprintf(out, "%s: ", jm.ID)
	}
	if jm.From != "" {
		fmt.Fprintf(out, "(from %s) ", jm.From)
	}
	if jm.Progress != nil && termInfo != nil {
		fmt.Fprintf(out, "%s %s%s", jm.Status, jm.Progress.String(), endl)
	} else if jm.ProgressMessage != "" { //deprecated
		fmt.Fprintf(out, "%s %s%s", jm.Status, jm.ProgressMessage, endl)
	} else if jm.Stream != "" {
		fmt.Fprintf(out, "%s%s", jm.Stream, endl)
	} else {
		fmt.Fprintf(out, "%s%s\n", jm.Status, endl)
	}
	return nil
}

// DisplayJSONMessagesStream displays a json message stream from `in` to `out`, `isTerminal`
// describes if `out` is a terminal. If this is the case, it will print `\n` at the end of
// each line and move the cursor while displaying.
func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr, isTerminal bool, auxCallback func(*json.RawMessage)) error {
	var (
		dec = json.NewDecoder(in)
		ids = make(map[string]int)
	)

	var termInfo termInfo

	if isTerminal {
		term := os.Getenv("TERM")
		if term == "" {
			term = "vt102"
		}

		var err error
		if termInfo, err = gotty.OpenTermInfo(term); err != nil {
			termInfo = &noTermInfo{}
		}
	}

	for {
		diff := 0
		var jm JSONMessage
		if err := dec.Decode(&jm); err != nil {
			if err == io.EOF {
				break
			}
			return err
		}

		if jm.Aux != nil {
			if auxCallback != nil {
				auxCallback(jm.Aux)
			}
			continue
		}

		if jm.Progress != nil {
			jm.Progress.terminalFd = terminalFd
		}
		if jm.ID != "" && (jm.Progress != nil || jm.ProgressMessage != "") {
			line, ok := ids[jm.ID]
			if !ok {
				// NOTE: This approach of using len(id) to
				// figure out the number of lines of history
				// only works as long as we clear the history
				// when we output something that's not
				// accounted for in the map, such as a line
				// with no ID.
				line = len(ids)
				ids[jm.ID] = line
				if termInfo != nil {
					fmt.Fprintf(out, "\n")
				}
			}
			diff = len(ids) - line
			if termInfo != nil {
				cursorUp(out, termInfo, diff)
			}
		} else {
			// When outputting something that isn't progress
			// output, clear the history of previous lines. We
			// don't want progress entries from some previous
			// operation to be updated (for example, pull -a
			// with multiple tags).
			ids = make(map[string]int)
		}
		err := jm.Display(out, termInfo)
		if jm.ID != "" && termInfo != nil {
			cursorDown(out, termInfo, diff)
		}
		if err != nil {
			return err
		}
	}
	return nil
}

type stream interface {
	io.Writer
	FD() uintptr
	IsTerminal() bool
}

// DisplayJSONMessagesToStream prints json messages to the output stream
func DisplayJSONMessagesToStream(in io.Reader, stream stream, auxCallback func(*json.RawMessage)) error {
	return DisplayJSONMessagesStream(in, stream, stream.FD(), stream.IsTerminal(), auxCallback)
}
<commit_msg>pkg/jsonmessage: Encode ANSI ESC directly as \x1b rather than via fmt as %c<commit_after>package jsonmessage

import (
	"encoding/json"
	"fmt"
	"io"
	"os"
	"strings"
	"time"

	"github.com/Nvveen/Gotty"

	"github.com/docker/docker/pkg/jsonlog"
	"github.com/docker/docker/pkg/term"
	"github.com/docker/go-units"
)

// JSONError wraps a concrete Code and Message, `Code` is
// is an integer error code, `Message` is the error message.
type JSONError struct {
	Code    int    `json:"code,omitempty"`
	Message string `json:"message,omitempty"`
}

func (e *JSONError) Error() string {
	return e.Message
}

// JSONProgress describes a Progress. terminalFd is the fd of the current terminal,
// Start is the initial value for the operation. Current is the current status and
// value of the progress made towards Total. Total is the end value describing when
// we made 100% progress for an operation.
type JSONProgress struct {
	terminalFd uintptr
	Current    int64 `json:"current,omitempty"`
	Total      int64 `json:"total,omitempty"`
	Start      int64 `json:"start,omitempty"`
}

func (p *JSONProgress) String() string {
	var (
		width       = 200
		pbBox       string
		numbersBox  string
		timeLeftBox string
	)

	ws, err := term.GetWinsize(p.terminalFd)
	if err == nil {
		width = int(ws.Width)
	}

	if p.Current <= 0 && p.Total <= 0 {
		return ""
	}
	current := units.HumanSize(float64(p.Current))
	if p.Total <= 0 {
		return fmt.Sprintf("%8v", current)
	}
	total := units.HumanSize(float64(p.Total))
	percentage := int(float64(p.Current)/float64(p.Total)*100) / 2
	if percentage > 50 {
		percentage = 50
	}
	if width > 110 {
		// this number can't be negative gh#7136
		numSpaces := 0
		if 50-percentage > 0 {
			numSpaces = 50 - percentage
		}
		pbBox = fmt.Sprintf("[%s>%s] ", strings.Repeat("=", percentage), strings.Repeat(" ", numSpaces))
	}

	numbersBox = fmt.Sprintf("%8v/%v", current, total)

	if p.Current > p.Total {
		// remove total display if the reported current is wonky.
		numbersBox = fmt.Sprintf("%8v", current)
	}

	if p.Current > 0 && p.Start > 0 && percentage < 50 {
		fromStart := time.Now().UTC().Sub(time.Unix(p.Start, 0))
		perEntry := fromStart / time.Duration(p.Current)
		left := time.Duration(p.Total-p.Current) * perEntry
		left = (left / time.Second) * time.Second

		if width > 50 {
			timeLeftBox = " " + left.String()
		}
	}
	return pbBox + numbersBox + timeLeftBox
}

// JSONMessage defines a message struct. It describes
// the created time, where it from, status, ID of the
// message. It's used for docker events.
type JSONMessage struct {
	Stream          string        `json:"stream,omitempty"`
	Status          string        `json:"status,omitempty"`
	Progress        *JSONProgress `json:"progressDetail,omitempty"`
	ProgressMessage string        `json:"progress,omitempty"` //deprecated
	ID              string        `json:"id,omitempty"`
	From            string        `json:"from,omitempty"`
	Time            int64         `json:"time,omitempty"`
	TimeNano        int64         `json:"timeNano,omitempty"`
	Error           *JSONError    `json:"errorDetail,omitempty"`
	ErrorMessage    string        `json:"error,omitempty"` //deprecated
	// Aux contains out-of-band data, such as digests for push signing.
	Aux *json.RawMessage `json:"aux,omitempty"`
}

/* Satisfied by gotty.TermInfo as well as noTermInfo from below */
type termInfo interface {
	Parse(attr string, params ...interface{}) (string, error)
}

type noTermInfo struct{} // canary used when no terminfo.

func (ti *noTermInfo) Parse(attr string, params ...interface{}) (string, error) {
	return "", fmt.Errorf("noTermInfo")
}

func clearLine(out io.Writer, ti termInfo) {
	// el2 (clear whole line) is not exposed by terminfo.

	// First clear line from beginning to cursor
	if attr, err := ti.Parse("el1"); err == nil {
		fmt.Fprintf(out, "%s", attr)
	} else {
		fmt.Fprintf(out, "\x1b[1K")
	}
	// Then clear line from cursor to end
	if attr, err := ti.Parse("el"); err == nil {
		fmt.Fprintf(out, "%s", attr)
	} else {
		fmt.Fprintf(out, "\x1b[K")
	}
}

func cursorUp(out io.Writer, ti termInfo, l int) {
	if l == 0 { // Should never be the case, but be tolerant
		return
	}
	if attr, err := ti.Parse("cuu", l); err == nil {
		fmt.Fprintf(out, "%s", attr)
	} else {
		fmt.Fprintf(out, "\x1b[%dA", l)
	}
}

func cursorDown(out io.Writer, ti termInfo, l int) {
	if l == 0 { // Should never be the case, but be tolerant
		return
	}
	if attr, err := ti.Parse("cud", l); err == nil {
		fmt.Fprintf(out, "%s", attr)
	} else {
		fmt.Fprintf(out, "\x1b[%dB", l)
	}
}

// Display displays the JSONMessage to `out`. `termInfo` is non-nil if `out`
// is a terminal. If this is the case, it will erase the entire current line
// when displaying the progressbar.
func (jm *JSONMessage) Display(out io.Writer, termInfo termInfo) error {
	if jm.Error != nil {
		if jm.Error.Code == 401 {
			return fmt.Errorf("Authentication is required.")
		}
		return jm.Error
	}
	var endl string
	if termInfo != nil && jm.Stream == "" && jm.Progress != nil {
		clearLine(out, termInfo)
		endl = "\r"
		fmt.Fprintf(out, endl)
	} else if jm.Progress != nil && jm.Progress.String() != "" { //disable progressbar in non-terminal
		return nil
	}
	if jm.TimeNano != 0 {
		fmt.Fprintf(out, "%s ", time.Unix(0, jm.TimeNano).Format(jsonlog.RFC3339NanoFixed))
	} else if jm.Time != 0 {
		fmt.Fprintf(out, "%s ", time.Unix(jm.Time, 0).Format(jsonlog.RFC3339NanoFixed))
	}
	if jm.ID != "" {
		fmt.Fprintf(out, "%s: ", jm.ID)
	}
	if jm.From != "" {
		fmt.Fprintf(out, "(from %s) ", jm.From)
	}
	if jm.Progress != nil && termInfo != nil {
		fmt.Fprintf(out, "%s %s%s", jm.Status, jm.Progress.String(), endl)
	} else if jm.ProgressMessage != "" { //deprecated
		fmt.Fprintf(out, "%s %s%s", jm.Status, jm.ProgressMessage, endl)
	} else if jm.Stream != "" {
		fmt.Fprintf(out, "%s%s", jm.Stream, endl)
	} else {
		fmt.Fprintf(out, "%s%s\n", jm.Status, endl)
	}
	return nil
}

// DisplayJSONMessagesStream displays a json message stream from `in` to `out`, `isTerminal`
// describes if `out` is a terminal. If this is the case, it will print `\n` at the end of
// each line and move the cursor while displaying.
func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr, isTerminal bool, auxCallback func(*json.RawMessage)) error {
	var (
		dec = json.NewDecoder(in)
		ids = make(map[string]int)
	)

	var termInfo termInfo

	if isTerminal {
		term := os.Getenv("TERM")
		if term == "" {
			term = "vt102"
		}

		var err error
		if termInfo, err = gotty.OpenTermInfo(term); err != nil {
			termInfo = &noTermInfo{}
		}
	}

	for {
		diff := 0
		var jm JSONMessage
		if err := dec.Decode(&jm); err != nil {
			if err == io.EOF {
				break
			}
			return err
		}

		if jm.Aux != nil {
			if auxCallback != nil {
				auxCallback(jm.Aux)
			}
			continue
		}

		if jm.Progress != nil {
			jm.Progress.terminalFd = terminalFd
		}
		if jm.ID != "" && (jm.Progress != nil || jm.ProgressMessage != "") {
			line, ok := ids[jm.ID]
			if !ok {
				// NOTE: This approach of using len(id) to
				// figure out the number of lines of history
				// only works as long as we clear the history
				// when we output something that's not
				// accounted for in the map, such as a line
				// with no ID.
				line = len(ids)
				ids[jm.ID] = line
				if termInfo != nil {
					fmt.Fprintf(out, "\n")
				}
			}
			diff = len(ids) - line
			if termInfo != nil {
				cursorUp(out, termInfo, diff)
			}
		} else {
			// When outputting something that isn't progress
			// output, clear the history of previous lines. We
			// don't want progress entries from some previous
			// operation to be updated (for example, pull -a
			// with multiple tags).
			ids = make(map[string]int)
		}
		err := jm.Display(out, termInfo)
		if jm.ID != "" && termInfo != nil {
			cursorDown(out, termInfo, diff)
		}
		if err != nil {
			return err
		}
	}
	return nil
}

type stream interface {
	io.Writer
	FD() uintptr
	IsTerminal() bool
}

// DisplayJSONMessagesToStream prints json messages to the output stream
func DisplayJSONMessagesToStream(in io.Reader, stream stream, auxCallback func(*json.RawMessage)) error {
	return DisplayJSONMessagesStream(in, stream, stream.FD(), stream.IsTerminal(), auxCallback)
}
<|endoftext|>


<commit_before>/* Copyright (C) 2019 Monomax Software Pty Ltd
 *
 * This file is part of Dnote.
 *
 * Dnote is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Dnote is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Dnote.  If not, see <https://www.gnu.org/licenses/>.
 */

package handlers

import (
	"encoding/json"
	"net/http"
	"time"

	"github.com/dnote/dnote/pkg/server/database"
	"github.com/dnote/dnote/pkg/server/operations"
	"github.com/jinzhu/gorm"
	"github.com/pkg/errors"
	"golang.org/x/crypto/bcrypt"
)

// ErrLoginFailure is an error for failed login
var ErrLoginFailure = errors.New("Wrong email and password combination")

// SessionResponse is a response containing a session information
type SessionResponse struct {
	Key       string `json:"key"`
	ExpiresAt int64  `json:"expires_at"`
}

func setSessionCookie(w http.ResponseWriter, key string, expires time.Time) {
	cookie := http.Cookie{
		Name:     "id",
		Value:    key,
		Expires:  expires,
		Path:     "/",
		HttpOnly: true,
	}
	http.SetCookie(w, &cookie)
}

func unsetSessionCookie(w http.ResponseWriter) {
	expire := time.Now().Add(time.Hour * -24 * 30)
	cookie := http.Cookie{
		Name:     "id",
		Value:    "",
		Expires:  expire,
		Path:     "/",
		HttpOnly: true,
	}

	w.Header().Set("Cache-Control", "no-cache")
	http.SetCookie(w, &cookie)
}

func touchLastLoginAt(db *gorm.DB, user database.User) error {
	t := time.Now()
	if err := db.Model(&user).Update(database.User{LastLoginAt: &t}).Error; err != nil {
		return errors.Wrap(err, "updating last_login_at")
	}

	return nil
}

type signinPayload struct {
	Email    string `json:"email"`
	Password string `json:"password"`
}

func (a *App) signin(w http.ResponseWriter, r *http.Request) {
	var params signinPayload
	err := json.NewDecoder(r.Body).Decode(&params)
	if err != nil {
		HandleError(w, "decoding payload", err, http.StatusInternalServerError)
		return
	}
	if params.Email == "" || params.Password == "" {
		http.Error(w, ErrLoginFailure.Error(), http.StatusUnauthorized)
		return
	}

	var account database.Account
	conn := a.DB.Where("email = ?", params.Email).First(&account)
	if conn.RecordNotFound() {
		http.Error(w, ErrLoginFailure.Error(), http.StatusUnauthorized)
		return
	} else if conn.Error != nil {
		HandleError(w, "getting user", err, http.StatusInternalServerError)
		return
	}

	password := []byte(params.Password)
	err = bcrypt.CompareHashAndPassword([]byte(account.Password.String), password)
	if err != nil {
		http.Error(w, ErrLoginFailure.Error(), http.StatusUnauthorized)
		return
	}

	var user database.User
	err = a.DB.Where("id = ?", account.UserID).First(&user).Error
	if err != nil {
		HandleError(w, "finding user", err, http.StatusInternalServerError)
		return
	}

	err = operations.TouchLastLoginAt(user, a.DB)
	if err != nil {
		http.Error(w, errors.Wrap(err, "touching login timestamp").Error(), http.StatusInternalServerError)
		return
	}

	respondWithSession(a.DB, w, account.UserID, http.StatusOK)
}

func (a *App) signoutOptions(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Methods", "POST")
	w.Header().Set("Access-Control-Allow-Headers", "Authorization, Version")
}

func (a *App) signout(w http.ResponseWriter, r *http.Request) {
	key, err := getCredential(r)
	if err != nil {
		HandleError(w, "getting credential", nil, http.StatusInternalServerError)
		return
	}

	if key == "" {
		w.WriteHeader(http.StatusNoContent)
		return
	}

	err = operations.DeleteSession(a.DB, key)
	if err != nil {
		HandleError(w, "deleting session", nil, http.StatusInternalServerError)
		return
	}

	unsetSessionCookie(w)
	w.WriteHeader(http.StatusNoContent)
}

type registerPayload struct {
	Email    string `json:"email"`
	Password string `json:"password"`
}

func validateRegisterPayload(p registerPayload) error {
	if p.Email == "" {
		return errors.New("email is required")
	}
	if len(p.Password) < 8 {
		return errors.New("Password should be longer than 8 characters")
	}

	return nil
}

func parseRegisterPaylaod(r *http.Request) (registerPayload, bool) {
	var ret registerPayload
	if err := json.NewDecoder(r.Body).Decode(&ret); err != nil {
		return ret, false
	}
	if err := validateRegisterPayload(ret); err != nil {
		return ret, false
	}

	return ret, true
}

func (a *App) register(w http.ResponseWriter, r *http.Request) {
	params, ok := parseRegisterPaylaod(r)
	if !ok {
		http.Error(w, "invalid payload", http.StatusBadRequest)
		return
	}

	var count int
	if err := a.DB.Model(database.Account{}).Where("email = ?", params.Email).Count(&count).Error; err != nil {
		HandleError(w, "checking duplicate user", err, http.StatusInternalServerError)
		return
	}
	if count > 0 {
		http.Error(w, "Duplicate email", http.StatusBadRequest)
		return
	}

	user, err := operations.CreateUser(a.DB, params.Email, params.Password)
	if err != nil {
		HandleError(w, "creating user", err, http.StatusInternalServerError)
		return
	}

	respondWithSession(a.DB, w, user.ID, http.StatusCreated)
}

// respondWithSession makes a HTTP response with the session from the user with the given userID.
// It sets the HTTP-Only cookie for browser clients and also sends a JSON response for non-browser clients.
func respondWithSession(db *gorm.DB, w http.ResponseWriter, userID int, statusCode int) {
	session, err := operations.CreateSession(db, userID)
	if err != nil {
		HandleError(w, "creating session", nil, http.StatusBadRequest)
		return
	}

	setSessionCookie(w, session.Key, session.ExpiresAt)

	response := SessionResponse{
		Key:       session.Key,
		ExpiresAt: session.ExpiresAt.Unix(),
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(statusCode)
	if err := json.NewEncoder(w).Encode(response); err != nil {
		HandleError(w, "encoding response", err, http.StatusInternalServerError)
		return
	}
}
<commit_msg>Display helpful error message to the client (#314)<commit_after>/* Copyright (C) 2019 Monomax Software Pty Ltd
 *
 * This file is part of Dnote.
 *
 * Dnote is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Dnote is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Dnote.  If not, see <https://www.gnu.org/licenses/>.
 */

package handlers

import (
	"encoding/json"
	"net/http"
	"time"

	"github.com/dnote/dnote/pkg/server/database"
	"github.com/dnote/dnote/pkg/server/operations"
	"github.com/jinzhu/gorm"
	"github.com/pkg/errors"
	"golang.org/x/crypto/bcrypt"
)

// ErrLoginFailure is an error for failed login
var ErrLoginFailure = errors.New("Wrong email and password combination")

// SessionResponse is a response containing a session information
type SessionResponse struct {
	Key       string `json:"key"`
	ExpiresAt int64  `json:"expires_at"`
}

func setSessionCookie(w http.ResponseWriter, key string, expires time.Time) {
	cookie := http.Cookie{
		Name:     "id",
		Value:    key,
		Expires:  expires,
		Path:     "/",
		HttpOnly: true,
	}
	http.SetCookie(w, &cookie)
}

func unsetSessionCookie(w http.ResponseWriter) {
	expire := time.Now().Add(time.Hour * -24 * 30)
	cookie := http.Cookie{
		Name:     "id",
		Value:    "",
		Expires:  expire,
		Path:     "/",
		HttpOnly: true,
	}

	w.Header().Set("Cache-Control", "no-cache")
	http.SetCookie(w, &cookie)
}

func touchLastLoginAt(db *gorm.DB, user database.User) error {
	t := time.Now()
	if err := db.Model(&user).Update(database.User{LastLoginAt: &t}).Error; err != nil {
		return errors.Wrap(err, "updating last_login_at")
	}

	return nil
}

type signinPayload struct {
	Email    string `json:"email"`
	Password string `json:"password"`
}

func (a *App) signin(w http.ResponseWriter, r *http.Request) {
	var params signinPayload
	err := json.NewDecoder(r.Body).Decode(&params)
	if err != nil {
		HandleError(w, "decoding payload", err, http.StatusInternalServerError)
		return
	}
	if params.Email == "" || params.Password == "" {
		http.Error(w, ErrLoginFailure.Error(), http.StatusUnauthorized)
		return
	}

	var account database.Account
	conn := a.DB.Where("email = ?", params.Email).First(&account)
	if conn.RecordNotFound() {
		http.Error(w, ErrLoginFailure.Error(), http.StatusUnauthorized)
		return
	} else if conn.Error != nil {
		HandleError(w, "getting user", err, http.StatusInternalServerError)
		return
	}

	password := []byte(params.Password)
	err = bcrypt.CompareHashAndPassword([]byte(account.Password.String), password)
	if err != nil {
		http.Error(w, ErrLoginFailure.Error(), http.StatusUnauthorized)
		return
	}

	var user database.User
	err = a.DB.Where("id = ?", account.UserID).First(&user).Error
	if err != nil {
		HandleError(w, "finding user", err, http.StatusInternalServerError)
		return
	}

	err = operations.TouchLastLoginAt(user, a.DB)
	if err != nil {
		http.Error(w, errors.Wrap(err, "touching login timestamp").Error(), http.StatusInternalServerError)
		return
	}

	respondWithSession(a.DB, w, account.UserID, http.StatusOK)
}

func (a *App) signoutOptions(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Methods", "POST")
	w.Header().Set("Access-Control-Allow-Headers", "Authorization, Version")
}

func (a *App) signout(w http.ResponseWriter, r *http.Request) {
	key, err := getCredential(r)
	if err != nil {
		HandleError(w, "getting credential", nil, http.StatusInternalServerError)
		return
	}

	if key == "" {
		w.WriteHeader(http.StatusNoContent)
		return
	}

	err = operations.DeleteSession(a.DB, key)
	if err != nil {
		HandleError(w, "deleting session", nil, http.StatusInternalServerError)
		return
	}

	unsetSessionCookie(w)
	w.WriteHeader(http.StatusNoContent)
}

type registerPayload struct {
	Email    string `json:"email"`
	Password string `json:"password"`
}

func validateRegisterPayload(p registerPayload) error {
	if p.Email == "" {
		return errors.New("email is required")
	}
	if len(p.Password) < 8 {
		return errors.New("Password should be longer than 8 characters")
	}

	return nil
}

func parseRegisterPaylaod(r *http.Request) (registerPayload, error) {
	var ret registerPayload
	if err := json.NewDecoder(r.Body).Decode(&ret); err != nil {
		return ret, errors.Wrap(err, "decoding json")
	}

	return ret, nil
}

func (a *App) register(w http.ResponseWriter, r *http.Request) {
	params, err := parseRegisterPaylaod(r)
	if err != nil {
		http.Error(w, "invalid payload", http.StatusBadRequest)
		return
	}
	if err := validateRegisterPayload(params); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	var count int
	if err := a.DB.Model(database.Account{}).Where("email = ?", params.Email).Count(&count).Error; err != nil {
		HandleError(w, "checking duplicate user", err, http.StatusInternalServerError)
		return
	}
	if count > 0 {
		http.Error(w, "Duplicate email", http.StatusBadRequest)
		return
	}

	user, err := operations.CreateUser(a.DB, params.Email, params.Password)
	if err != nil {
		HandleError(w, "creating user", err, http.StatusInternalServerError)
		return
	}

	respondWithSession(a.DB, w, user.ID, http.StatusCreated)
}

// respondWithSession makes a HTTP response with the session from the user with the given userID.
// It sets the HTTP-Only cookie for browser clients and also sends a JSON response for non-browser clients.
func respondWithSession(db *gorm.DB, w http.ResponseWriter, userID int, statusCode int) {
	session, err := operations.CreateSession(db, userID)
	if err != nil {
		HandleError(w, "creating session", nil, http.StatusBadRequest)
		return
	}

	setSessionCookie(w, session.Key, session.ExpiresAt)

	response := SessionResponse{
		Key:       session.Key,
		ExpiresAt: session.ExpiresAt.Unix(),
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(statusCode)
	if err := json.NewEncoder(w).Encode(response); err != nil {
		HandleError(w, "encoding response", err, http.StatusInternalServerError)
		return
	}
}
<|endoftext|>


<commit_before>package thumbs

import (
	"context"
	"encoding/json"
	"os"
	"strings"
	"sync"
	"time"

	"golang.org/x/sync/errgroup"

	"github.com/grafana/grafana/pkg/infra/log"
	"github.com/grafana/grafana/pkg/models"
	"github.com/grafana/grafana/pkg/services/live"
	"github.com/grafana/grafana/pkg/services/rendering"
)

type simpleCrawler struct {
	renderService rendering.Service
	threadCount   int

	glive            *live.GrafanaLive
	thumbnailRepo    thumbnailRepo
	mode             CrawlerMode
	thumbnailKind    models.ThumbnailKind
	opts             rendering.Opts
	status           crawlStatus
	statusMutex      sync.RWMutex
	queue            []*models.DashboardWithStaleThumbnail
	queueMutex       sync.Mutex
	log              log.Logger
	renderingSession rendering.Session
}

func newSimpleCrawler(renderService rendering.Service, gl *live.GrafanaLive, repo thumbnailRepo) dashRenderer {
	c := &simpleCrawler{
		renderService: renderService,
		threadCount:   6,
		glive:         gl,
		thumbnailRepo: repo,
		log:           log.New("thumbnails_crawler"),
		status: crawlStatus{
			State:    initializing,
			Complete: 0,
			Queue:    0,
		},
		queue: nil,
	}
	c.broadcastStatus()
	return c
}

func (r *simpleCrawler) next() *models.DashboardWithStaleThumbnail {
	r.queueMutex.Lock()
	defer r.queueMutex.Unlock()

	if r.queue == nil || len(r.queue) < 1 {
		return nil
	}

	v := r.queue[0]
	r.queue = r.queue[1:]
	return v
}

func (r *simpleCrawler) broadcastStatus() {
	s, err := r.Status()
	if err != nil {
		r.log.Warn("Error reading status", "err", err)
		return
	}
	msg, err := json.Marshal(s)
	if err != nil {
		r.log.Warn("Error making message", "err", err)
		return
	}
	err = r.glive.Publish(r.opts.OrgID, "grafana/broadcast/crawler", msg)
	if err != nil {
		r.log.Warn("Error Publish message", "err", err)
		return
	}
}

func (r *simpleCrawler) Run(ctx context.Context, authOpts rendering.AuthOpts, mode CrawlerMode, theme models.Theme, thumbnailKind models.ThumbnailKind) error {
	r.queueMutex.Lock()
	if r.IsRunning() {
		r.queueMutex.Unlock()
		r.log.Info("Already running")
		return nil
	}

	now := time.Now()

	items, err := r.thumbnailRepo.findDashboardsWithStaleThumbnails(ctx, theme, thumbnailKind)
	if err != nil {
		r.log.Error("Error when fetching dashboards with stale thumbnails", "err", err.Error())
		r.queueMutex.Unlock()
		return err
	}

	if len(items) == 0 {
		r.queueMutex.Unlock()
		return nil
	}

	r.mode = mode
	r.thumbnailKind = thumbnailKind
	r.opts = rendering.Opts{
		AuthOpts: authOpts,
		TimeoutOpts: rendering.TimeoutOpts{
			Timeout:                  10 * time.Second,
			RequestTimeoutMultiplier: 3,
		},
		Theme:           theme,
		ConcurrentLimit: 10,
	}
	renderingSession, err := r.renderService.CreateRenderingSession(ctx, r.opts.AuthOpts, rendering.SessionOpts{
		Expiry:                     5 * time.Minute,
		RefreshExpiryOnEachRequest: true,
	})
	if err != nil {
		r.log.Error("Error when creating rendering session", "err", err.Error())
		r.queueMutex.Unlock()
		return err
	}

	r.renderingSession = renderingSession
	r.queue = items
	r.status = crawlStatus{
		Started:  now,
		State:    running,
		Complete: 0,
	}
	r.broadcastStatus()
	r.queueMutex.Unlock()

	r.log.Info("Starting dashboard crawler", "dashboardsToCrawl", len(items), "mode", string(mode), "theme", string(theme), "kind", string(thumbnailKind))

	group, gCtx := errgroup.WithContext(ctx)
	// create a pool of workers
	for i := 0; i < r.threadCount; i++ {
		walkerId := i
		group.Go(func() error {
			r.walk(gCtx, walkerId)
			return nil
		})
	}

	err = group.Wait()
	if err != nil {
		r.log.Error("Crawl ended with an error", "err", err)
	}

	r.crawlFinished()
	r.broadcastStatus()
	return err
}

func (r *simpleCrawler) IsRunning() bool {
	r.statusMutex.Lock()
	defer r.statusMutex.Unlock()
	return r.status.State == running
}

func (r *simpleCrawler) Stop() (crawlStatus, error) {
	r.statusMutex.Lock()
	if r.status.State == running {
		r.status.State = stopping
	}
	r.statusMutex.Unlock()

	return r.Status()
}

func (r *simpleCrawler) Status() (crawlStatus, error) {
	r.statusMutex.RLock()
	defer r.statusMutex.RUnlock()

	status := crawlStatus{
		State:    r.status.State,
		Started:  r.status.Started,
		Complete: r.status.Complete,
		Errors:   r.status.Errors,
		Queue:    len(r.queue),
		Last:     r.status.Last,
	}
	return status, nil
}

func (r *simpleCrawler) newErrorResult() {
	r.statusMutex.Lock()
	defer r.statusMutex.Unlock()

	r.status.Errors++
	r.status.Last = time.Now()
}

func (r *simpleCrawler) newSuccessResult() {
	r.statusMutex.Lock()
	defer r.statusMutex.Unlock()

	r.status.Complete++
	r.status.Last = time.Now()
}

func (r *simpleCrawler) crawlFinished() {
	r.statusMutex.Lock()
	defer r.statusMutex.Unlock()

	r.status.State = stopped
	r.status.Finished = time.Now()
	r.log.Info("Crawler finished", "startTime", r.status.Started, "endTime", r.status.Finished, "durationInSeconds", int64(time.Since(r.status.Started)/time.Second))
}

func (r *simpleCrawler) shouldWalk() bool {
	r.statusMutex.RLock()
	defer r.statusMutex.RUnlock()

	return r.status.State == running
}

func (r *simpleCrawler) walk(ctx context.Context, id int) {
	for {
		if !r.shouldWalk() {
			break
		}

		item := r.next()
		if item == nil {
			break
		}

		url := models.GetKioskModeDashboardUrl(item.Uid, item.Slug, r.opts.Theme)
		r.log.Info("Getting dashboard thumbnail", "walkerId", id, "dashboardUID", item.Uid, "url", url)

		res, err := r.renderService.Render(ctx, rendering.Opts{
			Width:             320,
			Height:            240,
			Path:              strings.TrimPrefix(url, "/"),
			AuthOpts:          r.opts.AuthOpts,
			TimeoutOpts:       r.opts.TimeoutOpts,
			ConcurrentLimit:   r.opts.ConcurrentLimit,
			Theme:             r.opts.Theme,
			DeviceScaleFactor: -5, // negative numbers will render larger and then scale down.
		}, r.renderingSession)
		if err != nil {
			r.log.Warn("Error getting image", "walkerId", id, "dashboardUID", item.Uid, "url", url, "err", err)
			r.newErrorResult()
		} else if res.FilePath == "" {
			r.log.Warn("Error getting image... no response", "walkerId", id, "dashboardUID", item.Uid, "url", url)
			r.newErrorResult()
		} else if strings.Contains(res.FilePath, "public/img") {
			r.log.Warn("Error getting image... internal result", "walkerId", id, "dashboardUID", item.Uid, "url", url, "img", res.FilePath)
			// rendering service returned a static error image - we should not remove that file
			r.newErrorResult()
		} else {
			func() {
				defer func() {
					err := os.Remove(res.FilePath)
					if err != nil {
						r.log.Error("Failed to remove thumbnail temp file", "walkerId", id, "dashboardUID", item.Uid, "url", url, "err", err)
					}
				}()

				thumbnailId, err := r.thumbnailRepo.saveFromFile(ctx, res.FilePath, models.DashboardThumbnailMeta{
					DashboardUID: item.Uid,
					OrgId:        item.OrgId,
					Theme:        r.opts.Theme,
					Kind:         r.thumbnailKind,
				}, item.Version)

				if err != nil {
					r.log.Warn("Error saving image image", "walkerId", id, "dashboardUID", item.Uid, "url", url, "err", err)
					r.newErrorResult()
				} else {
					r.log.Info("Saved thumbnail", "walkerId", id, "dashboardUID", item.Uid, "url", url, "thumbnailId", thumbnailId)
					r.newSuccessResult()
				}
			}()
		}
		r.broadcastStatus()
	}

	r.log.Info("Walker finished", "walkerId", id)
}
<commit_msg>Previews: prevent scheduler from running when image renderer is outdated (#45623)<commit_after>package thumbs

import (
	"context"
	"encoding/json"
	"fmt"
	"os"
	"strings"
	"sync"
	"time"

	"golang.org/x/sync/errgroup"

	"github.com/grafana/grafana/pkg/infra/log"
	"github.com/grafana/grafana/pkg/models"
	"github.com/grafana/grafana/pkg/services/live"
	"github.com/grafana/grafana/pkg/services/rendering"
)

type simpleCrawler struct {
	renderService rendering.Service
	threadCount   int

	glive            *live.GrafanaLive
	thumbnailRepo    thumbnailRepo
	mode             CrawlerMode
	thumbnailKind    models.ThumbnailKind
	opts             rendering.Opts
	status           crawlStatus
	statusMutex      sync.RWMutex
	queue            []*models.DashboardWithStaleThumbnail
	queueMutex       sync.Mutex
	log              log.Logger
	renderingSession rendering.Session
}

func newSimpleCrawler(renderService rendering.Service, gl *live.GrafanaLive, repo thumbnailRepo) dashRenderer {
	c := &simpleCrawler{
		renderService: renderService,
		threadCount:   6,
		glive:         gl,
		thumbnailRepo: repo,
		log:           log.New("thumbnails_crawler"),
		status: crawlStatus{
			State:    initializing,
			Complete: 0,
			Queue:    0,
		},
		queue: nil,
	}
	c.broadcastStatus()
	return c
}

func (r *simpleCrawler) next() *models.DashboardWithStaleThumbnail {
	r.queueMutex.Lock()
	defer r.queueMutex.Unlock()

	if r.queue == nil || len(r.queue) < 1 {
		return nil
	}

	v := r.queue[0]
	r.queue = r.queue[1:]
	return v
}

func (r *simpleCrawler) broadcastStatus() {
	s, err := r.Status()
	if err != nil {
		r.log.Warn("Error reading status", "err", err)
		return
	}
	msg, err := json.Marshal(s)
	if err != nil {
		r.log.Warn("Error making message", "err", err)
		return
	}
	err = r.glive.Publish(r.opts.OrgID, "grafana/broadcast/crawler", msg)
	if err != nil {
		r.log.Warn("Error Publish message", "err", err)
		return
	}
}

func (r *simpleCrawler) Run(ctx context.Context, authOpts rendering.AuthOpts, mode CrawlerMode, theme models.Theme, thumbnailKind models.ThumbnailKind) error {
	res, err := r.renderService.HasCapability(rendering.ScalingDownImages)
	if err != nil {
		return err
	}

	if !res.IsSupported {
		return fmt.Errorf("cant run dashboard crawler - rendering service needs to be updated. "+
			"current version: %s, requiredVersion: %s", r.renderService.Version(), res.SemverConstraint)
	}

	r.queueMutex.Lock()
	if r.IsRunning() {
		r.queueMutex.Unlock()
		r.log.Info("Already running")
		return nil
	}

	now := time.Now()

	items, err := r.thumbnailRepo.findDashboardsWithStaleThumbnails(ctx, theme, thumbnailKind)
	if err != nil {
		r.log.Error("Error when fetching dashboards with stale thumbnails", "err", err.Error())
		r.queueMutex.Unlock()
		return err
	}

	if len(items) == 0 {
		r.queueMutex.Unlock()
		return nil
	}

	r.mode = mode
	r.thumbnailKind = thumbnailKind
	r.opts = rendering.Opts{
		AuthOpts: authOpts,
		TimeoutOpts: rendering.TimeoutOpts{
			Timeout:                  10 * time.Second,
			RequestTimeoutMultiplier: 3,
		},
		Theme:           theme,
		ConcurrentLimit: 10,
	}
	renderingSession, err := r.renderService.CreateRenderingSession(ctx, r.opts.AuthOpts, rendering.SessionOpts{
		Expiry:                     5 * time.Minute,
		RefreshExpiryOnEachRequest: true,
	})
	if err != nil {
		r.log.Error("Error when creating rendering session", "err", err.Error())
		r.queueMutex.Unlock()
		return err
	}

	r.renderingSession = renderingSession
	r.queue = items
	r.status = crawlStatus{
		Started:  now,
		State:    running,
		Complete: 0,
	}
	r.broadcastStatus()
	r.queueMutex.Unlock()

	r.log.Info("Starting dashboard crawler", "dashboardsToCrawl", len(items), "mode", string(mode), "theme", string(theme), "kind", string(thumbnailKind))

	group, gCtx := errgroup.WithContext(ctx)
	// create a pool of workers
	for i := 0; i < r.threadCount; i++ {
		walkerId := i
		group.Go(func() error {
			r.walk(gCtx, walkerId)
			return nil
		})
	}

	err = group.Wait()
	if err != nil {
		r.log.Error("Crawl ended with an error", "err", err)
	}

	r.crawlFinished()
	r.broadcastStatus()
	return err
}

func (r *simpleCrawler) IsRunning() bool {
	r.statusMutex.Lock()
	defer r.statusMutex.Unlock()
	return r.status.State == running
}

func (r *simpleCrawler) Stop() (crawlStatus, error) {
	r.statusMutex.Lock()
	if r.status.State == running {
		r.status.State = stopping
	}
	r.statusMutex.Unlock()

	return r.Status()
}

func (r *simpleCrawler) Status() (crawlStatus, error) {
	r.statusMutex.RLock()
	defer r.statusMutex.RUnlock()

	status := crawlStatus{
		State:    r.status.State,
		Started:  r.status.Started,
		Complete: r.status.Complete,
		Errors:   r.status.Errors,
		Queue:    len(r.queue),
		Last:     r.status.Last,
	}
	return status, nil
}

func (r *simpleCrawler) newErrorResult() {
	r.statusMutex.Lock()
	defer r.statusMutex.Unlock()

	r.status.Errors++
	r.status.Last = time.Now()
}

func (r *simpleCrawler) newSuccessResult() {
	r.statusMutex.Lock()
	defer r.statusMutex.Unlock()

	r.status.Complete++
	r.status.Last = time.Now()
}

func (r *simpleCrawler) crawlFinished() {
	r.statusMutex.Lock()
	defer r.statusMutex.Unlock()

	r.status.State = stopped
	r.status.Finished = time.Now()
	r.log.Info("Crawler finished", "startTime", r.status.Started, "endTime", r.status.Finished, "durationInSeconds", int64(time.Since(r.status.Started)/time.Second))
}

func (r *simpleCrawler) shouldWalk() bool {
	r.statusMutex.RLock()
	defer r.statusMutex.RUnlock()

	return r.status.State == running
}

func (r *simpleCrawler) walk(ctx context.Context, id int) {
	for {
		if !r.shouldWalk() {
			break
		}

		item := r.next()
		if item == nil {
			break
		}

		url := models.GetKioskModeDashboardUrl(item.Uid, item.Slug, r.opts.Theme)
		r.log.Info("Getting dashboard thumbnail", "walkerId", id, "dashboardUID", item.Uid, "url", url)

		res, err := r.renderService.Render(ctx, rendering.Opts{
			Width:             320,
			Height:            240,
			Path:              strings.TrimPrefix(url, "/"),
			AuthOpts:          r.opts.AuthOpts,
			TimeoutOpts:       r.opts.TimeoutOpts,
			ConcurrentLimit:   r.opts.ConcurrentLimit,
			Theme:             r.opts.Theme,
			DeviceScaleFactor: -5, // negative numbers will render larger and then scale down.
		}, r.renderingSession)
		if err != nil {
			r.log.Warn("Error getting image", "walkerId", id, "dashboardUID", item.Uid, "url", url, "err", err)
			r.newErrorResult()
		} else if res.FilePath == "" {
			r.log.Warn("Error getting image... no response", "walkerId", id, "dashboardUID", item.Uid, "url", url)
			r.newErrorResult()
		} else if strings.Contains(res.FilePath, "public/img") {
			r.log.Warn("Error getting image... internal result", "walkerId", id, "dashboardUID", item.Uid, "url", url, "img", res.FilePath)
			// rendering service returned a static error image - we should not remove that file
			r.newErrorResult()
		} else {
			func() {
				defer func() {
					err := os.Remove(res.FilePath)
					if err != nil {
						r.log.Error("Failed to remove thumbnail temp file", "walkerId", id, "dashboardUID", item.Uid, "url", url, "err", err)
					}
				}()

				thumbnailId, err := r.thumbnailRepo.saveFromFile(ctx, res.FilePath, models.DashboardThumbnailMeta{
					DashboardUID: item.Uid,
					OrgId:        item.OrgId,
					Theme:        r.opts.Theme,
					Kind:         r.thumbnailKind,
				}, item.Version)

				if err != nil {
					r.log.Warn("Error saving image image", "walkerId", id, "dashboardUID", item.Uid, "url", url, "err", err)
					r.newErrorResult()
				} else {
					r.log.Info("Saved thumbnail", "walkerId", id, "dashboardUID", item.Uid, "url", url, "thumbnailId", thumbnailId)
					r.newSuccessResult()
				}
			}()
		}
		r.broadcastStatus()
	}

	r.log.Info("Walker finished", "walkerId", id)
}
<|endoftext|>


<commit_before>/*
Copyright 2018 The Skaffold 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 config

import (
	"strings"
)

// SkaffoldOptions are options that are set by command line arguments not included
// in the config file itself
type SkaffoldOptions struct {
	ConfigurationFile string
	Cleanup           bool
	Notification      bool
	Tail              bool
	TailDev           bool
	PortForward       bool
	Profiles          []string
	CustomTag         string
	Namespace         string
	Watch             []string
	Trigger           string
	CustomLabels      []string
	WatchPollInterval int
	DefaultRepo string
}

// Labels returns a map of labels to be applied to all deployed
// k8s objects during the duration of the run
func (opts *SkaffoldOptions) Labels() map[string]string {
	labels := map[string]string{}

	if opts.Cleanup {
		labels["cleanup"] = "true"
	}
	if opts.Tail || opts.TailDev {
		labels["tail"] = "true"
	}
	if opts.Namespace != "" {
		labels["namespace"] = opts.Namespace
	}
	if len(opts.Profiles) > 0 {
		labels["profiles"] = strings.Join(opts.Profiles, "__")
	}
	for _, cl := range opts.CustomLabels {
		l := strings.SplitN(cl, "=", 2)
		if len(l) == 1 {
			labels[l[0]] = ""
			continue
		}
		labels[l[0]] = l[1]
	}
	return labels
}
<commit_msg>linter<commit_after>/*
Copyright 2018 The Skaffold 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 config

import (
	"strings"
)

// SkaffoldOptions are options that are set by command line arguments not included
// in the config file itself
type SkaffoldOptions struct {
	ConfigurationFile string
	Cleanup           bool
	Notification      bool
	Tail              bool
	TailDev           bool
	PortForward       bool
	Profiles          []string
	CustomTag         string
	Namespace         string
	Watch             []string
	Trigger           string
	CustomLabels      []string
	WatchPollInterval int
	DefaultRepo       string
}

// Labels returns a map of labels to be applied to all deployed
// k8s objects during the duration of the run
func (opts *SkaffoldOptions) Labels() map[string]string {
	labels := map[string]string{}

	if opts.Cleanup {
		labels["cleanup"] = "true"
	}
	if opts.Tail || opts.TailDev {
		labels["tail"] = "true"
	}
	if opts.Namespace != "" {
		labels["namespace"] = opts.Namespace
	}
	if len(opts.Profiles) > 0 {
		labels["profiles"] = strings.Join(opts.Profiles, "__")
	}
	for _, cl := range opts.CustomLabels {
		l := strings.SplitN(cl, "=", 2)
		if len(l) == 1 {
			labels[l[0]] = ""
			continue
		}
		labels[l[0]] = l[1]
	}
	return labels
}
<|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 flexvolume

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"sync"

	"github.com/fsnotify/fsnotify"
	"k8s.io/apimachinery/pkg/util/errors"
	"k8s.io/klog"
	utilfs "k8s.io/kubernetes/pkg/util/filesystem"
	"k8s.io/kubernetes/pkg/volume"
	"k8s.io/utils/exec"
	utilstrings "k8s.io/utils/strings"
)

type flexVolumeProber struct {
	mutex          sync.Mutex
	pluginDir      string         // Flexvolume driver directory
	runner         exec.Interface // Interface to use for execing flex calls
	watcher        utilfs.FSWatcher
	factory        PluginFactory
	fs             utilfs.Filesystem
	probeAllNeeded bool
	eventsMap      map[string]volume.ProbeOperation // the key is the driver directory path, the value is the coresponding operation
}

// GetDynamicPluginProber creates dynamic plugin prober
func GetDynamicPluginProber(pluginDir string, runner exec.Interface) volume.DynamicPluginProber {
	return &flexVolumeProber{
		pluginDir: pluginDir,
		watcher:   utilfs.NewFsnotifyWatcher(),
		factory:   pluginFactory{},
		runner:    runner,
		fs:        &utilfs.DefaultFs{},
	}
}

func (prober *flexVolumeProber) Init() error {
	prober.testAndSetProbeAllNeeded(true)
	prober.eventsMap = map[string]volume.ProbeOperation{}

	if err := prober.createPluginDir(); err != nil {
		return err
	}
	if err := prober.initWatcher(); err != nil {
		return err
	}

	return nil
}

// If probeAllNeeded is true, probe all pluginDir
// else probe events in eventsMap
func (prober *flexVolumeProber) Probe() (events []volume.ProbeEvent, err error) {
	if prober.probeAllNeeded {
		prober.testAndSetProbeAllNeeded(false)
		return prober.probeAll()
	}

	return prober.probeMap()
}

func (prober *flexVolumeProber) probeMap() (events []volume.ProbeEvent, err error) {
	// TODO use a concurrent map to avoid Locking the entire map
	prober.mutex.Lock()
	defer prober.mutex.Unlock()
	probeEvents := []volume.ProbeEvent{}
	allErrs := []error{}
	for driverDirPathAbs, op := range prober.eventsMap {
		driverDirName := filepath.Base(driverDirPathAbs) // e.g. driverDirName = vendor~cifs
		probeEvent, pluginErr := prober.newProbeEvent(driverDirName, op)
		if pluginErr != nil {
			allErrs = append(allErrs, pluginErr)
			continue
		}
		probeEvents = append(probeEvents, probeEvent)

		delete(prober.eventsMap, driverDirPathAbs)
	}
	return probeEvents, errors.NewAggregate(allErrs)
}

func (prober *flexVolumeProber) probeAll() (events []volume.ProbeEvent, err error) {
	probeEvents := []volume.ProbeEvent{}
	allErrs := []error{}
	files, err := prober.fs.ReadDir(prober.pluginDir)
	if err != nil {
		return nil, fmt.Errorf("Error reading the Flexvolume directory: %s", err)
	}
	for _, f := range files {
		// only directories with names that do not begin with '.' are counted as plugins
		// and pluginDir/dirname/dirname should be an executable
		// unless dirname contains '~' for escaping namespace
		// e.g. dirname = vendor~cifs
		// then, executable will be pluginDir/dirname/cifs
		if f.IsDir() && filepath.Base(f.Name())[0] != '.' {
			probeEvent, pluginErr := prober.newProbeEvent(f.Name(), volume.ProbeAddOrUpdate)
			if pluginErr != nil {
				allErrs = append(allErrs, pluginErr)
				continue
			}
			probeEvents = append(probeEvents, probeEvent)
		}
	}
	return probeEvents, errors.NewAggregate(allErrs)
}

func (prober *flexVolumeProber) newProbeEvent(driverDirName string, op volume.ProbeOperation) (volume.ProbeEvent, error) {
	probeEvent := volume.ProbeEvent{
		Op: op,
	}
	if op == volume.ProbeAddOrUpdate {
		plugin, pluginErr := prober.factory.NewFlexVolumePlugin(prober.pluginDir, driverDirName, prober.runner)
		if pluginErr != nil {
			pluginErr = fmt.Errorf(
				"Error creating Flexvolume plugin from directory %s, skipping. Error: %s",
				driverDirName, pluginErr)
			return probeEvent, pluginErr
		}
		probeEvent.Plugin = plugin
		probeEvent.PluginName = plugin.GetPluginName()
	} else if op == volume.ProbeRemove {
		driverName := utilstrings.UnescapeQualifiedName(driverDirName)
		probeEvent.PluginName = flexVolumePluginNamePrefix + driverName

	} else {
		return probeEvent, fmt.Errorf("Unknown Operation on directory: %s. ", driverDirName)
	}
	return probeEvent, nil
}

func (prober *flexVolumeProber) handleWatchEvent(event fsnotify.Event) error {
	// event.Name is the watched path.
	if filepath.Base(event.Name)[0] == '.' {
		// Ignore files beginning with '.'
		return nil
	}

	eventPathAbs, err := filepath.Abs(event.Name)
	if err != nil {
		return err
	}
	parentPathAbs := filepath.Dir(eventPathAbs)
	pluginDirAbs, err := filepath.Abs(prober.pluginDir)
	if err != nil {
		return err
	}

	// event of pluginDirAbs
	if eventPathAbs == pluginDirAbs {
		// If the Flexvolume plugin directory is removed, need to recreate it
		// in order to keep it under watch.
		if eventOpIs(event, fsnotify.Remove) {
			if err := prober.createPluginDir(); err != nil {
				return err
			}
			if err := prober.addWatchRecursive(pluginDirAbs); err != nil {
				return err
			}
		}
		return nil
	}

	// watch newly added subdirectories inside a driver directory
	if eventOpIs(event, fsnotify.Create) {
		if err := prober.addWatchRecursive(eventPathAbs); err != nil {
			return err
		}
	}

	eventRelPathToPluginDir, err := filepath.Rel(pluginDirAbs, eventPathAbs)
	if err != nil {
		return err
	}

	// event inside specific driver dir
	if len(eventRelPathToPluginDir) > 0 {
		driverDirName := strings.Split(eventRelPathToPluginDir, string(os.PathSeparator))[0]
		driverDirAbs := filepath.Join(pluginDirAbs, driverDirName)
		// executable is removed, will trigger ProbeRemove event
		if eventOpIs(event, fsnotify.Remove) && (eventRelPathToPluginDir == getExecutablePathRel(driverDirName) || parentPathAbs == pluginDirAbs) {
			prober.updateEventsMap(driverDirAbs, volume.ProbeRemove)
		} else {
			prober.updateEventsMap(driverDirAbs, volume.ProbeAddOrUpdate)
		}
	}

	return nil
}

// getExecutableName returns the executableName of a flex plugin
func getExecutablePathRel(driverDirName string) string {
	parts := strings.Split(driverDirName, "~")
	return filepath.Join(driverDirName, parts[len(parts)-1])
}

func (prober *flexVolumeProber) updateEventsMap(eventDirAbs string, op volume.ProbeOperation) {
	prober.mutex.Lock()
	defer prober.mutex.Unlock()
	if prober.probeAllNeeded {
		return
	}
	prober.eventsMap[eventDirAbs] = op
}

// Recursively adds to watch all directories inside and including the file specified by the given filename.
// If the file is a symlink to a directory, it will watch the symlink but not any of the subdirectories.
//
// Each file or directory change triggers two events: one from the watch on itself, another from the watch
// on its parent directory.
func (prober *flexVolumeProber) addWatchRecursive(filename string) error {
	addWatch := func(path string, info os.FileInfo, err error) error {
		if err == nil && info.IsDir() {
			if err := prober.watcher.AddWatch(path); err != nil {
				klog.Errorf("Error recursively adding watch: %v", err)
			}
		}
		return nil
	}
	return prober.fs.Walk(filename, addWatch)
}

// Creates a new filesystem watcher and adds watches for the plugin directory
// and all of its subdirectories.
func (prober *flexVolumeProber) initWatcher() error {
	err := prober.watcher.Init(func(event fsnotify.Event) {
		if err := prober.handleWatchEvent(event); err != nil {
			klog.Errorf("Flexvolume prober watch: %s", err)
		}
	}, func(err error) {
		klog.Errorf("Received an error from watcher: %s", err)
	})
	if err != nil {
		return fmt.Errorf("Error initializing watcher: %s", err)
	}

	if err := prober.addWatchRecursive(prober.pluginDir); err != nil {
		return fmt.Errorf("Error adding watch on Flexvolume directory: %s", err)
	}

	prober.watcher.Run()

	return nil
}

// Creates the plugin directory, if it doesn't already exist.
func (prober *flexVolumeProber) createPluginDir() error {
	if _, err := prober.fs.Stat(prober.pluginDir); os.IsNotExist(err) {
		klog.Warningf("Flexvolume plugin directory at %s does not exist. Recreating.", prober.pluginDir)
		err := prober.fs.MkdirAll(prober.pluginDir, 0755)
		if err != nil {
			return fmt.Errorf("Error (re-)creating driver directory: %s", err)
		}
	}

	return nil
}

func (prober *flexVolumeProber) testAndSetProbeAllNeeded(newval bool) (oldval bool) {
	prober.mutex.Lock()
	defer prober.mutex.Unlock()
	oldval, prober.probeAllNeeded = prober.probeAllNeeded, newval
	return
}

func eventOpIs(event fsnotify.Event, op fsnotify.Op) bool {
	return event.Op&op == op
}
<commit_msg>fix error 'coresponding'<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 flexvolume

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"sync"

	"github.com/fsnotify/fsnotify"
	"k8s.io/apimachinery/pkg/util/errors"
	"k8s.io/klog"
	utilfs "k8s.io/kubernetes/pkg/util/filesystem"
	"k8s.io/kubernetes/pkg/volume"
	"k8s.io/utils/exec"
	utilstrings "k8s.io/utils/strings"
)

type flexVolumeProber struct {
	mutex          sync.Mutex
	pluginDir      string         // Flexvolume driver directory
	runner         exec.Interface // Interface to use for execing flex calls
	watcher        utilfs.FSWatcher
	factory        PluginFactory
	fs             utilfs.Filesystem
	probeAllNeeded bool
	eventsMap      map[string]volume.ProbeOperation // the key is the driver directory path, the value is the corresponding operation
}

// GetDynamicPluginProber creates dynamic plugin prober
func GetDynamicPluginProber(pluginDir string, runner exec.Interface) volume.DynamicPluginProber {
	return &flexVolumeProber{
		pluginDir: pluginDir,
		watcher:   utilfs.NewFsnotifyWatcher(),
		factory:   pluginFactory{},
		runner:    runner,
		fs:        &utilfs.DefaultFs{},
	}
}

func (prober *flexVolumeProber) Init() error {
	prober.testAndSetProbeAllNeeded(true)
	prober.eventsMap = map[string]volume.ProbeOperation{}

	if err := prober.createPluginDir(); err != nil {
		return err
	}
	if err := prober.initWatcher(); err != nil {
		return err
	}

	return nil
}

// If probeAllNeeded is true, probe all pluginDir
// else probe events in eventsMap
func (prober *flexVolumeProber) Probe() (events []volume.ProbeEvent, err error) {
	if prober.probeAllNeeded {
		prober.testAndSetProbeAllNeeded(false)
		return prober.probeAll()
	}

	return prober.probeMap()
}

func (prober *flexVolumeProber) probeMap() (events []volume.ProbeEvent, err error) {
	// TODO use a concurrent map to avoid Locking the entire map
	prober.mutex.Lock()
	defer prober.mutex.Unlock()
	probeEvents := []volume.ProbeEvent{}
	allErrs := []error{}
	for driverDirPathAbs, op := range prober.eventsMap {
		driverDirName := filepath.Base(driverDirPathAbs) // e.g. driverDirName = vendor~cifs
		probeEvent, pluginErr := prober.newProbeEvent(driverDirName, op)
		if pluginErr != nil {
			allErrs = append(allErrs, pluginErr)
			continue
		}
		probeEvents = append(probeEvents, probeEvent)

		delete(prober.eventsMap, driverDirPathAbs)
	}
	return probeEvents, errors.NewAggregate(allErrs)
}

func (prober *flexVolumeProber) probeAll() (events []volume.ProbeEvent, err error) {
	probeEvents := []volume.ProbeEvent{}
	allErrs := []error{}
	files, err := prober.fs.ReadDir(prober.pluginDir)
	if err != nil {
		return nil, fmt.Errorf("Error reading the Flexvolume directory: %s", err)
	}
	for _, f := range files {
		// only directories with names that do not begin with '.' are counted as plugins
		// and pluginDir/dirname/dirname should be an executable
		// unless dirname contains '~' for escaping namespace
		// e.g. dirname = vendor~cifs
		// then, executable will be pluginDir/dirname/cifs
		if f.IsDir() && filepath.Base(f.Name())[0] != '.' {
			probeEvent, pluginErr := prober.newProbeEvent(f.Name(), volume.ProbeAddOrUpdate)
			if pluginErr != nil {
				allErrs = append(allErrs, pluginErr)
				continue
			}
			probeEvents = append(probeEvents, probeEvent)
		}
	}
	return probeEvents, errors.NewAggregate(allErrs)
}

func (prober *flexVolumeProber) newProbeEvent(driverDirName string, op volume.ProbeOperation) (volume.ProbeEvent, error) {
	probeEvent := volume.ProbeEvent{
		Op: op,
	}
	if op == volume.ProbeAddOrUpdate {
		plugin, pluginErr := prober.factory.NewFlexVolumePlugin(prober.pluginDir, driverDirName, prober.runner)
		if pluginErr != nil {
			pluginErr = fmt.Errorf(
				"Error creating Flexvolume plugin from directory %s, skipping. Error: %s",
				driverDirName, pluginErr)
			return probeEvent, pluginErr
		}
		probeEvent.Plugin = plugin
		probeEvent.PluginName = plugin.GetPluginName()
	} else if op == volume.ProbeRemove {
		driverName := utilstrings.UnescapeQualifiedName(driverDirName)
		probeEvent.PluginName = flexVolumePluginNamePrefix + driverName

	} else {
		return probeEvent, fmt.Errorf("Unknown Operation on directory: %s. ", driverDirName)
	}
	return probeEvent, nil
}

func (prober *flexVolumeProber) handleWatchEvent(event fsnotify.Event) error {
	// event.Name is the watched path.
	if filepath.Base(event.Name)[0] == '.' {
		// Ignore files beginning with '.'
		return nil
	}

	eventPathAbs, err := filepath.Abs(event.Name)
	if err != nil {
		return err
	}
	parentPathAbs := filepath.Dir(eventPathAbs)
	pluginDirAbs, err := filepath.Abs(prober.pluginDir)
	if err != nil {
		return err
	}

	// event of pluginDirAbs
	if eventPathAbs == pluginDirAbs {
		// If the Flexvolume plugin directory is removed, need to recreate it
		// in order to keep it under watch.
		if eventOpIs(event, fsnotify.Remove) {
			if err := prober.createPluginDir(); err != nil {
				return err
			}
			if err := prober.addWatchRecursive(pluginDirAbs); err != nil {
				return err
			}
		}
		return nil
	}

	// watch newly added subdirectories inside a driver directory
	if eventOpIs(event, fsnotify.Create) {
		if err := prober.addWatchRecursive(eventPathAbs); err != nil {
			return err
		}
	}

	eventRelPathToPluginDir, err := filepath.Rel(pluginDirAbs, eventPathAbs)
	if err != nil {
		return err
	}

	// event inside specific driver dir
	if len(eventRelPathToPluginDir) > 0 {
		driverDirName := strings.Split(eventRelPathToPluginDir, string(os.PathSeparator))[0]
		driverDirAbs := filepath.Join(pluginDirAbs, driverDirName)
		// executable is removed, will trigger ProbeRemove event
		if eventOpIs(event, fsnotify.Remove) && (eventRelPathToPluginDir == getExecutablePathRel(driverDirName) || parentPathAbs == pluginDirAbs) {
			prober.updateEventsMap(driverDirAbs, volume.ProbeRemove)
		} else {
			prober.updateEventsMap(driverDirAbs, volume.ProbeAddOrUpdate)
		}
	}

	return nil
}

// getExecutableName returns the executableName of a flex plugin
func getExecutablePathRel(driverDirName string) string {
	parts := strings.Split(driverDirName, "~")
	return filepath.Join(driverDirName, parts[len(parts)-1])
}

func (prober *flexVolumeProber) updateEventsMap(eventDirAbs string, op volume.ProbeOperation) {
	prober.mutex.Lock()
	defer prober.mutex.Unlock()
	if prober.probeAllNeeded {
		return
	}
	prober.eventsMap[eventDirAbs] = op
}

// Recursively adds to watch all directories inside and including the file specified by the given filename.
// If the file is a symlink to a directory, it will watch the symlink but not any of the subdirectories.
//
// Each file or directory change triggers two events: one from the watch on itself, another from the watch
// on its parent directory.
func (prober *flexVolumeProber) addWatchRecursive(filename string) error {
	addWatch := func(path string, info os.FileInfo, err error) error {
		if err == nil && info.IsDir() {
			if err := prober.watcher.AddWatch(path); err != nil {
				klog.Errorf("Error recursively adding watch: %v", err)
			}
		}
		return nil
	}
	return prober.fs.Walk(filename, addWatch)
}

// Creates a new filesystem watcher and adds watches for the plugin directory
// and all of its subdirectories.
func (prober *flexVolumeProber) initWatcher() error {
	err := prober.watcher.Init(func(event fsnotify.Event) {
		if err := prober.handleWatchEvent(event); err != nil {
			klog.Errorf("Flexvolume prober watch: %s", err)
		}
	}, func(err error) {
		klog.Errorf("Received an error from watcher: %s", err)
	})
	if err != nil {
		return fmt.Errorf("Error initializing watcher: %s", err)
	}

	if err := prober.addWatchRecursive(prober.pluginDir); err != nil {
		return fmt.Errorf("Error adding watch on Flexvolume directory: %s", err)
	}

	prober.watcher.Run()

	return nil
}

// Creates the plugin directory, if it doesn't already exist.
func (prober *flexVolumeProber) createPluginDir() error {
	if _, err := prober.fs.Stat(prober.pluginDir); os.IsNotExist(err) {
		klog.Warningf("Flexvolume plugin directory at %s does not exist. Recreating.", prober.pluginDir)
		err := prober.fs.MkdirAll(prober.pluginDir, 0755)
		if err != nil {
			return fmt.Errorf("Error (re-)creating driver directory: %s", err)
		}
	}

	return nil
}

func (prober *flexVolumeProber) testAndSetProbeAllNeeded(newval bool) (oldval bool) {
	prober.mutex.Lock()
	defer prober.mutex.Unlock()
	oldval, prober.probeAllNeeded = prober.probeAllNeeded, newval
	return
}

func eventOpIs(event fsnotify.Event, op fsnotify.Op) bool {
	return event.Op&op == op
}
<|endoftext|>


<commit_before>package pool

import (
	"fmt"
	"testing"
)

var pooltests = []struct {
	workers int
	queue   int
}{
	{1, 10},
	{2, 20},
	{5, 50},
	{10, 100},
	{15, 150},
	{20, 200},
	{100, 1000},
}

func TestPool(t *testing.T) {
	for _, tt := range pooltests {
		title := fmt.Sprintf("%v workers & %v tasks", tt.workers, tt.queue)
		t.Run(title, func(t *testing.T) {
			p := New(tt.workers, tt.queue)
			for i := 0; i < tt.queue; i++ {
				p.Add(func() (interface{}, error) { return i, nil })
			}
			p.Start()
			p.Wait()
		})
	}
}

var poolbenchmarks = []struct {
	workers int
	queue   int
}{
	{100, 1000},
}

func BenchmarkPool(b *testing.B) {
	for _, tt := range poolbenchmarks {
		title := fmt.Sprintf("%v workers & %v tasks", tt.workers, tt.queue)
		b.Run(title, func(b *testing.B) {
			for i := 0; i < b.N; i++ {
				p := New(tt.workers, tt.queue)
				for i := 0; i < tt.queue; i++ {
					p.Add(func() (interface{}, error) { return i, nil })
				}
				p.Start()
				p.Wait()
			}
		})
	}
}
<commit_msg>test worker and queue len<commit_after>package pool

import (
	"fmt"
	"testing"
	"time"
)

var pooltests = []struct {
	workers int
	queue   int
}{
	{1, 10},
	{2, 20},
	{5, 50},
	{10, 100},
	{15, 150},
	{20, 200},
	{100, 1000},
}

func TestPool(t *testing.T) {
	for _, tt := range pooltests {
		title := fmt.Sprintf("%v workers & %v tasks", tt.workers, tt.queue)
		t.Run(title, func(t *testing.T) {
			p := New(tt.workers, tt.queue)
			for i := 0; i < tt.queue; i++ {
				p.Add(func() (interface{}, error) { return i, nil })
			}
			time.Sleep(100 * time.Millisecond)
			if len(p.Workers) != tt.workers {
				t.Errorf("Expected pool to have spun up %v workers. have %v", tt.workers, len(p.Workers))
			}
			if len(p.Tasks) != tt.queue {
				t.Errorf("Expected pool to have queued %v tasks. have %v", tt.queue, len(p.Tasks))
			}
			p.Start()
			p.Wait()
		})
	}
}

var poolbenchmarks = []struct {
	workers int
	queue   int
}{
	{100, 1000},
}

func BenchmarkPool(b *testing.B) {
	for _, tt := range poolbenchmarks {
		title := fmt.Sprintf("%v workers & %v tasks", tt.workers, tt.queue)
		b.Run(title, func(b *testing.B) {
			for i := 0; i < b.N; i++ {
				p := New(tt.workers, tt.queue)
				for i := 0; i < tt.queue; i++ {
					p.Add(func() (interface{}, error) { return i, nil })
				}
				p.Start()
				p.Wait()
			}
		})
	}
}
<|endoftext|>


<commit_before>package instructions

import (
	"fmt"
	"jvmgo/jvm/rtda"
	rtc "jvmgo/jvm/rtda/class"
)

// Push item from run-time constant pool
type ldc struct{ Index8Instruction }

func (self *ldc) Execute(frame *rtda.Frame) {
	_ldc(frame, self.index)
}

// Push item from run-time constant pool (wide index)
type ldc_w struct{ Index16Instruction }

func (self *ldc_w) Execute(frame *rtda.Frame) {
	_ldc(frame, self.index)
}

func _ldc(frame *rtda.Frame, index uint) {
	stack := frame.OperandStack()
	cp := frame.Method().Class().ConstantPool()
	c := cp.GetConstant(index)

	switch c.(type) {
	case int32:
		stack.PushInt(c.(int32))
	case float32:
		stack.PushFloat(c.(float32))
	case *rtc.ConstantString:
		constStr := c.(*rtc.ConstantString)
		if constStr.JStr() == nil {
			jStr := rtda.NewJString(constStr.GoStr(), frame) // already interned
			constStr.SetJStr(jStr)
		}
		stack.PushRef(constStr.JStr())
	case *rtc.ConstantClass: // todo
		class := c.(*rtc.ConstantClass).Class()
		stack.PushRef(class.JClass())
	default:
		fmt.Printf("CCC:::%v\n", c)
		panic("todo ldc!!!")
		// todo
		// ref to String
		// ref to Class
		// ref to MethodType or MethodHandle
	}
}

// Push long or double from run-time constant pool (wide index)
type ldc2_w struct{ Index16Instruction }

func (self *ldc2_w) Execute(frame *rtda.Frame) {
	stack := frame.OperandStack()
	cp := frame.Method().Class().ConstantPool()
	c := cp.GetConstant(self.index)

	switch c.(type) {
	case int64:
		stack.PushLong(c.(int64))
	case float64:
		stack.PushDouble(c.(float64))
		// todo
	}
}
<commit_msg>code refactor<commit_after>package instructions

import (
	"jvmgo/jvm/rtda"
	rtc "jvmgo/jvm/rtda/class"
	"jvmgo/util"
)

// Push item from run-time constant pool
type ldc struct{ Index8Instruction }

func (self *ldc) Execute(frame *rtda.Frame) {
	_ldc(frame, self.index)
}

// Push item from run-time constant pool (wide index)
type ldc_w struct{ Index16Instruction }

func (self *ldc_w) Execute(frame *rtda.Frame) {
	_ldc(frame, self.index)
}

func _ldc(frame *rtda.Frame, index uint) {
	stack := frame.OperandStack()
	cp := frame.Method().Class().ConstantPool()
	c := cp.GetConstant(index)

	switch c.(type) {
	case int32:
		stack.PushInt(c.(int32))
	case float32:
		stack.PushFloat(c.(float32))
	case *rtc.ConstantString:
		kString := c.(*rtc.ConstantString)
		if kString.JStr() == nil {
			strObj := rtda.NewJString(kString.GoStr(), frame) // already interned
			kString.SetJStr(strObj)
		}
		stack.PushRef(kString.JStr())
	case *rtc.ConstantClass: // todo
		kClass := c.(*rtc.ConstantClass)
		classObj := kClass.Class().JClass()
		stack.PushRef(classObj)
	default:
		// todo
		// ref to MethodType or MethodHandle
		util.Panicf("todo: ldc! %v\n", c)
	}
}

// Push long or double from run-time constant pool (wide index)
type ldc2_w struct{ Index16Instruction }

func (self *ldc2_w) Execute(frame *rtda.Frame) {
	stack := frame.OperandStack()
	cp := frame.Method().Class().ConstantPool()
	c := cp.GetConstant(self.index)

	switch c.(type) {
	case int64:
		stack.PushLong(c.(int64))
	case float64:
		stack.PushDouble(c.(float64))
		// todo
	}
}
<|endoftext|>


<commit_before>package swagger

import (
	"github.com/emicklei/go-restful"
	// "github.com/emicklei/hopwatch"
	"log"
	"net/http"
	"reflect"
)

var config Config

// InstallSwaggerService add the WebService that provides the API documentation of all services
// conform the Swagger documentation specifcation. (https://github.com/wordnik/swagger-core/wiki).
// DEPRECATED , use RegisterSwaggerService(...)
func InstallSwaggerService(aSwaggerConfig Config) {
	RegisterSwaggerService(aSwaggerConfig, restful.DefaultContainer)
}

// RegisterSwaggerService add the WebService that provides the API documentation of all services
// conform the Swagger documentation specifcation. (https://github.com/wordnik/swagger-core/wiki).
func RegisterSwaggerService(aSwaggerConfig Config, wsContainer *restful.Container) {
	config = aSwaggerConfig

	ws := new(restful.WebService)
	ws.Path(config.ApiPath)
	ws.Produces(restful.MIME_JSON)
	ws.Route(ws.GET("/").To(getListing))
	ws.Route(ws.GET("/{a}").To(getDeclarations))
	ws.Route(ws.GET("/{a}/{b}").To(getDeclarations))
	ws.Route(ws.GET("/{a}/{b}/{c}").To(getDeclarations))
	ws.Route(ws.GET("/{a}/{b}/{c}/{d}").To(getDeclarations))
	ws.Route(ws.GET("/{a}/{b}/{c}/{d}/{e}").To(getDeclarations))
	ws.Route(ws.GET("/{a}/{b}/{c}/{d}/{e}/{f}").To(getDeclarations))
	ws.Route(ws.GET("/{a}/{b}/{c}/{d}/{e}/{f}/{g}").To(getDeclarations))
	log.Printf("[restful/swagger] listing is available at %v%v", config.WebServicesUrl, config.ApiPath)
	wsContainer.Add(ws)

	// Check paths for UI serving
	if config.SwaggerPath != "" && config.SwaggerFilePath != "" {
		log.Printf("[restful/swagger] %v%v is mapped to folder %v", config.WebServicesUrl, config.SwaggerPath, config.SwaggerFilePath)
		wsContainer.Handle(config.SwaggerPath, http.StripPrefix(config.SwaggerPath, http.FileServer(http.Dir(config.SwaggerFilePath))))
	} else {
		log.Printf("[restful/swagger] Swagger(File)Path is empty ; no UI is served")
	}
}

func getListing(req *restful.Request, resp *restful.Response) {
	listing := ResourceListing{SwaggerVersion: "1.1", BasePath: config.WebServicesUrl}
	for _, each := range config.WebServices {
		// skip the api service itself
		if each.RootPath() != config.ApiPath {
			api := Api{
				Path: config.ApiPath + each.RootPath()}
			//Description: each.Doc}
			listing.Apis = append(listing.Apis, api)
		}
	}
	resp.WriteAsJson(listing)
}

func getDeclarations(req *restful.Request, resp *restful.Response) {
	rootPath := composeRootPath(req)
	// log.Printf("rootPath:%V", rootPath)
	decl := ApiDeclaration{SwaggerVersion: "1.1", BasePath: config.WebServicesUrl, ResourcePath: rootPath}
	for _, each := range config.WebServices {
		// find the webservice
		if each.RootPath() == rootPath {
			// collect any path parameters
			rootParams := []Parameter{}
			for _, param := range each.PathParameters() {
				rootParams = append(rootParams, asSwaggerParameter(param.Data()))
			}
			// aggregate by path
			pathToRoutes := map[string][]restful.Route{}
			for _, other := range each.Routes() {
				routes := pathToRoutes[other.Path]
				pathToRoutes[other.Path] = append(routes, other)
			}
			for path, routes := range pathToRoutes {
				api := Api{Path: path, Models: map[string]Model{}}
				for _, route := range routes {
					operation := Operation{HttpMethod: route.Method,
						Summary:       route.Doc,
						ResponseClass: asDataType(route.WriteSample),
						Nickname:      route.Operation}

					// share root params if any
					for _, swparam := range rootParams {
						operation.Parameters = append(operation.Parameters, swparam)
					}
					// route specific params
					for _, param := range route.ParameterDocs {
						operation.Parameters = append(operation.Parameters, asSwaggerParameter(param.Data()))
					}
					addModelsFromRoute(&api, &operation, route)
					api.Operations = append(api.Operations, operation)
				}
				decl.Apis = append(decl.Apis, api)
			}
		}
	}
	resp.WriteAsJson(decl)
}

// addModelsFromRoute takes any read or write sample from the Route and creates a Swagger model from it.
func addModelsFromRoute(api *Api, operation *Operation, route restful.Route) {
	if route.ReadSample != nil {
		addModelFromSample(api, operation, false, route.ReadSample)
	}
	if route.WriteSample != nil {
		addModelFromSample(api, operation, true, route.WriteSample)
	}
}

// addModelFromSample creates and adds (or overwrites) a Model from a sample resource
func addModelFromSample(api *Api, operation *Operation, isResponse bool, sample interface{}) {
	st := reflect.TypeOf(sample)
	isCollection := false
	if st.Kind() == reflect.Slice || st.Kind() == reflect.Array {
		st = st.Elem()
		isCollection = true
	}
	if isResponse {
		modelName := st.String()
		if isCollection {
			modelName = "Array[" + modelName + "]"
		}
		operation.ResponseClass = modelName
	}
	addModelToApi(api, reflect.TypeOf(sample))
}

func addModelToApi(api *Api, st reflect.Type) {
	modelName := st.String()
	sm := Model{modelName, map[string]ModelProperty{}}
	// check for structure or primitive type
	if st.Kind() == reflect.Struct {
		for i := 0; i < st.NumField(); i++ {
			sf := st.Field(i)
			jsonName := sf.Name
			// see if a tag overrides this
			if override := st.Field(i).Tag.Get("json"); override != "" {
				jsonName = override
			}
			sm.Properties[jsonName] = asModelProperty(sf, api)
		}
	}
	api.Models[modelName] = sm
}

func asModelProperty(sf reflect.StructField, api *Api) ModelProperty {
	prop := ModelProperty{}
	st := sf.Type
	if st.Kind() == reflect.Slice || st.Kind() == reflect.Array {
		prop.Type = "List"
		prop.Items = map[string]string{"$ref": st.Elem().String()}
		// add|overwrite mode for element type
		addModelToApi(api, st.Elem())
	} else {
		prop.Type = st.String() // include pkg path
	}
	return prop
}

func asSwaggerParameter(param restful.ParameterData) Parameter {
	return Parameter{
		Name:        param.Name,
		Description: param.Description,
		ParamType:   asParamType(param.Kind),
		DataType:    param.DataType,
		Required:    param.Required}
}

// Between 1..7 path parameters is supported
func composeRootPath(req *restful.Request) string {
	path := "/" + req.PathParameter("a")
	b := req.PathParameter("b")
	if b == "" {
		return path
	}
	path = path + "/" + b
	c := req.PathParameter("c")
	if c == "" {
		return path
	}
	path = path + "/" + c
	d := req.PathParameter("d")
	if d == "" {
		return path
	}
	path = path + "/" + d
	e := req.PathParameter("e")
	if e == "" {
		return path
	}
	path = path + "/" + e
	f := req.PathParameter("f")
	if f == "" {
		return path
	}
	path = path + "/" + f
	g := req.PathParameter("g")
	if g == "" {
		return path
	}
	return path + "/" + g
}

func asParamType(kind int) string {
	switch {
	case kind == restful.PATH_PARAMETER:
		return "path"
	case kind == restful.QUERY_PARAMETER:
		return "query"
	case kind == restful.BODY_PARAMETER:
		return "body"
	case kind == restful.HEADER_PARAMETER:
		return "header"
	}
	return ""
}

func asDataType(any interface{}) string {
	if any == nil {
		return "void"
	}
	return reflect.TypeOf(any).Name()
}
<commit_msg>Swagger HTTP Origin Header support.<commit_after>package swagger

import (
	"github.com/emicklei/go-restful"
	// "github.com/emicklei/hopwatch"
	"log"
	"net/http"
	"reflect"
)

var config Config

// InstallSwaggerService add the WebService that provides the API documentation of all services
// conform the Swagger documentation specifcation. (https://github.com/wordnik/swagger-core/wiki).
// DEPRECATED , use RegisterSwaggerService(...)
func InstallSwaggerService(aSwaggerConfig Config) {
	RegisterSwaggerService(aSwaggerConfig, restful.DefaultContainer)
}

// RegisterSwaggerService add the WebService that provides the API documentation of all services
// conform the Swagger documentation specifcation. (https://github.com/wordnik/swagger-core/wiki).
func RegisterSwaggerService(aSwaggerConfig Config, wsContainer *restful.Container) {
	config = aSwaggerConfig

	ws := new(restful.WebService)
	ws.Path(config.ApiPath)
	ws.Produces(restful.MIME_JSON)
	ws.Filter(enableCORS)
	ws.Route(ws.GET("/").To(getListing))
	ws.Route(ws.GET("/{a}").To(getDeclarations))
	ws.Route(ws.GET("/{a}/{b}").To(getDeclarations))
	ws.Route(ws.GET("/{a}/{b}/{c}").To(getDeclarations))
	ws.Route(ws.GET("/{a}/{b}/{c}/{d}").To(getDeclarations))
	ws.Route(ws.GET("/{a}/{b}/{c}/{d}/{e}").To(getDeclarations))
	ws.Route(ws.GET("/{a}/{b}/{c}/{d}/{e}/{f}").To(getDeclarations))
	ws.Route(ws.GET("/{a}/{b}/{c}/{d}/{e}/{f}/{g}").To(getDeclarations))
	log.Printf("[restful/swagger] listing is available at %v%v", config.WebServicesUrl, config.ApiPath)
	wsContainer.Add(ws)

	// Check paths for UI serving
	if config.SwaggerPath != "" && config.SwaggerFilePath != "" {
		log.Printf("[restful/swagger] %v%v is mapped to folder %v", config.WebServicesUrl, config.SwaggerPath, config.SwaggerFilePath)
		wsContainer.Handle(config.SwaggerPath, http.StripPrefix(config.SwaggerPath, http.FileServer(http.Dir(config.SwaggerFilePath))))
	} else {
		log.Printf("[restful/swagger] Swagger(File)Path is empty ; no UI is served")
	}
}

func enableCORS(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
    if origin := req.HeaderParameter("Origin"); origin != "" {
        resp.AddHeader("Access-Control-Allow-Origin", origin)
    }
    chain.ProcessFilter(req, resp)
}

func getListing(req *restful.Request, resp *restful.Response) {
	listing := ResourceListing{SwaggerVersion: "1.1", BasePath: config.WebServicesUrl}
	for _, each := range config.WebServices {
		// skip the api service itself
		if each.RootPath() != config.ApiPath {
			api := Api{
				Path: config.ApiPath + each.RootPath()}
			//Description: each.Doc}
			listing.Apis = append(listing.Apis, api)
		}
	}
	resp.WriteAsJson(listing)
}

func getDeclarations(req *restful.Request, resp *restful.Response) {
	rootPath := composeRootPath(req)
	// log.Printf("rootPath:%V", rootPath)
	decl := ApiDeclaration{SwaggerVersion: "1.1", BasePath: config.WebServicesUrl, ResourcePath: rootPath}
	for _, each := range config.WebServices {
		// find the webservice
		if each.RootPath() == rootPath {
			// collect any path parameters
			rootParams := []Parameter{}
			for _, param := range each.PathParameters() {
				rootParams = append(rootParams, asSwaggerParameter(param.Data()))
			}
			// aggregate by path
			pathToRoutes := map[string][]restful.Route{}
			for _, other := range each.Routes() {
				routes := pathToRoutes[other.Path]
				pathToRoutes[other.Path] = append(routes, other)
			}
			for path, routes := range pathToRoutes {
				api := Api{Path: path, Models: map[string]Model{}}
				for _, route := range routes {
					operation := Operation{HttpMethod: route.Method,
						Summary:       route.Doc,
						ResponseClass: asDataType(route.WriteSample),
						Nickname:      route.Operation}

					// share root params if any
					for _, swparam := range rootParams {
						operation.Parameters = append(operation.Parameters, swparam)
					}
					// route specific params
					for _, param := range route.ParameterDocs {
						operation.Parameters = append(operation.Parameters, asSwaggerParameter(param.Data()))
					}
					addModelsFromRoute(&api, &operation, route)
					api.Operations = append(api.Operations, operation)
				}
				decl.Apis = append(decl.Apis, api)
			}
		}
	}
	resp.WriteAsJson(decl)
}

// addModelsFromRoute takes any read or write sample from the Route and creates a Swagger model from it.
func addModelsFromRoute(api *Api, operation *Operation, route restful.Route) {
	if route.ReadSample != nil {
		addModelFromSample(api, operation, false, route.ReadSample)
	}
	if route.WriteSample != nil {
		addModelFromSample(api, operation, true, route.WriteSample)
	}
}

// addModelFromSample creates and adds (or overwrites) a Model from a sample resource
func addModelFromSample(api *Api, operation *Operation, isResponse bool, sample interface{}) {
	st := reflect.TypeOf(sample)
	isCollection := false
	if st.Kind() == reflect.Slice || st.Kind() == reflect.Array {
		st = st.Elem()
		isCollection = true
	}
	if isResponse {
		modelName := st.String()
		if isCollection {
			modelName = "Array[" + modelName + "]"
		}
		operation.ResponseClass = modelName
	}
	addModelToApi(api, reflect.TypeOf(sample))
}

func addModelToApi(api *Api, st reflect.Type) {
	modelName := st.String()
	sm := Model{modelName, map[string]ModelProperty{}}
	// check for structure or primitive type
	if st.Kind() == reflect.Struct {
		for i := 0; i < st.NumField(); i++ {
			sf := st.Field(i)
			jsonName := sf.Name
			// see if a tag overrides this
			if override := st.Field(i).Tag.Get("json"); override != "" {
				jsonName = override
			}
			sm.Properties[jsonName] = asModelProperty(sf, api)
		}
	}
	api.Models[modelName] = sm
}

func asModelProperty(sf reflect.StructField, api *Api) ModelProperty {
	prop := ModelProperty{}
	st := sf.Type
	if st.Kind() == reflect.Slice || st.Kind() == reflect.Array {
		prop.Type = "List"
		prop.Items = map[string]string{"$ref": st.Elem().String()}
		// add|overwrite mode for element type
		addModelToApi(api, st.Elem())
	} else {
		prop.Type = st.String() // include pkg path
	}
	return prop
}

func asSwaggerParameter(param restful.ParameterData) Parameter {
	return Parameter{
		Name:        param.Name,
		Description: param.Description,
		ParamType:   asParamType(param.Kind),
		DataType:    param.DataType,
		Required:    param.Required}
}

// Between 1..7 path parameters is supported
func composeRootPath(req *restful.Request) string {
	path := "/" + req.PathParameter("a")
	b := req.PathParameter("b")
	if b == "" {
		return path
	}
	path = path + "/" + b
	c := req.PathParameter("c")
	if c == "" {
		return path
	}
	path = path + "/" + c
	d := req.PathParameter("d")
	if d == "" {
		return path
	}
	path = path + "/" + d
	e := req.PathParameter("e")
	if e == "" {
		return path
	}
	path = path + "/" + e
	f := req.PathParameter("f")
	if f == "" {
		return path
	}
	path = path + "/" + f
	g := req.PathParameter("g")
	if g == "" {
		return path
	}
	return path + "/" + g
}

func asParamType(kind int) string {
	switch {
	case kind == restful.PATH_PARAMETER:
		return "path"
	case kind == restful.QUERY_PARAMETER:
		return "query"
	case kind == restful.BODY_PARAMETER:
		return "body"
	case kind == restful.HEADER_PARAMETER:
		return "header"
	}
	return ""
}

func asDataType(any interface{}) string {
	if any == nil {
		return "void"
	}
	return reflect.TypeOf(any).Name()
}
<|endoftext|>