<commit_before>package formbuilder

import (
	"os"

	ncommon "github.com/admpub/nging/application/library/common"

	"github.com/coscms/forms"
	"github.com/coscms/forms/common"
	"github.com/coscms/forms/config"
	"github.com/coscms/forms/fields"

	"github.com/webx-top/com"
	"github.com/webx-top/echo"
	"github.com/webx-top/echo/formfilter"
	"github.com/webx-top/echo/middleware/render/driver"
	"github.com/webx-top/validation"
)

// New 表单
//@param m: dbschema
func New(c echo.Context, m interface{}, jsonFile string, options ...Option) (*forms.Forms, error) {
	form := forms.New()
	form.Style = common.BOOTSTRAP
	for _, option := range options {
		if option == nil {
			continue
		}
		option(c, form)
	}
	form.SetLabelFunc(func(txt string) string {
		return c.T(txt)
	})
	var cfg *config.Config
	renderer := c.Renderer().(driver.Driver)
	jsonFile += `.form.json`
	jsonFile = renderer.TmplPath(c, jsonFile)
	if len(jsonFile) == 0 {
		return nil
	}
	b, err := renderer.RawContent(jsonFile)
	if err != nil {
		if os.IsNotExist(err) && renderer.Manager() != nil {
			form.SetModel(m)
			cfg = form.ToConfig()
			var jsonb []byte
			jsonb, err = form.ToJSONBlob(cfg)
			if err == nil {
				err = renderer.Manager().SetTemplate(jsonFile, jsonb)
				if err == nil {
					c.Logger().Infof(c.T(`生成表单配置文件“%v”成功。`), jsonFile)
				}
			}
		}
	} else {
		cfg, err = forms.Unmarshal(b, jsonFile)
	}
	if err != nil {
		return nil, err
	}
	if cfg == nil {
		cfg = form.NewConfig()
	}
	form.Init(cfg, m)
	if c.IsPost() {
		opts := []formfilter.Options{formfilter.Include(cfg.GetNames()...)}
		if customs, ok := c.Internal().Get(`formfilter.Options`).([]formfilter.Options); ok {
			opts = append(opts, customs...)
		}
		err = c.MustBind(m, formfilter.Build(opts...))
		if err == nil {
			form.ValidFromConfig()
			valid := form.Validate()
			if valid.HasError() {
				err = valid.Errors[0]
			}
		}
		if err != nil {
			if vErr, ok := err.(*validation.ValidationError); ok {
				c.Data().SetInfo(vErr.Message, 0).SetZone(vErr.Field)
			} else {
				c.Data().SetError(err)
			}
		}
	}
	setNextURLField := func() {
		if len(cfg.Action) == 0 {
			form.SetParam(`action`, c.Request().URI())
		}
		nextURL := c.Form(ncommon.DefaultReturnToURLVarName)
		if len(nextURL) == 0 {
			nextURL = c.Referer()
		}
		form.Elements(fields.HiddenField(ncommon.DefaultReturnToURLVarName).SetValue(nextURL))
	}
	csrfToken, ok := c.Get(`csrf`).(string)
	if ok {
		form.AddBeforeRender(func() {
			form.Elements(fields.HiddenField(`csrf`).SetValue(csrfToken))
			setNextURLField()
		})
	} else {
		form.AddBeforeRender(setNextURLField)
	}
	wrap := forms.NewForms(form)
	c.Set(`forms`, wrap)
	// 手动调用:
	// wrap.ParseFromConfig()
	return wrap, nil
}

// NewModel 表单
//@param m: dbschema
func NewModel(c echo.Context, m interface{}, cfg *config.Config, options ...Option) *forms.Forms {
	form := forms.New()
	for _, option := range options {
		if option == nil {
			continue
		}
		option(c, form)
	}
	form.SetLabelFunc(func(txt string) string {
		return c.T(txt)
	})
	if cfg == nil {
		cfg = form.NewConfig()
	}
	form.Init(cfg, m)
	if c.IsPost() {
		opts := []formfilter.Options{formfilter.Include(cfg.GetNames()...)}
		if customs, ok := c.Internal().Get(`formfilter.Options`).([]formfilter.Options); ok {
			opts = append(opts, customs...)
		}
		err := c.MustBind(m, formfilter.Build(opts...))
		if err == nil {
			validFields, _ := c.Internal().Get(`formbuilder.validFields`).([]string)
			err = form.Valid(validFields...)
		}
		if err != nil {
			if vErr, ok := err.(*validation.ValidationError); ok {
				c.Data().SetInfo(vErr.Message, 0).SetZone(vErr.Field)
			} else {
				c.Data().SetError(err)
			}
		}
	}
	setNextURLField := func() {
		if len(cfg.Action) == 0 {
			form.SetParam(`action`, c.Request().URI())
		}
		nextURL := c.Form(ncommon.DefaultReturnToURLVarName)
		if len(nextURL) == 0 {
			nextURL = c.Referer()
		}
		form.Elements(fields.HiddenField(ncommon.DefaultReturnToURLVarName).SetValue(nextURL))
	}
	csrfToken, ok := c.Get(`csrf`).(string)
	if ok {
		form.AddBeforeRender(func() {
			form.Elements(fields.HiddenField(`csrf`).SetValue(csrfToken))
			setNextURLField()
		})
	} else {
		form.AddBeforeRender(setNextURLField)
	}
	form.AddClass("form-horizontal").SetParam("role", "form")
	wrap := forms.NewForms(form)
	c.Set(`forms`, wrap)
	// 手动调用:
	// wrap.ParseFromConfig()
	return wrap
}

// NewConfig 表单配置
func NewConfig(theme, tmpl, method, action string) *config.Config {
	cfg := forms.NewConfig()
	cfg.Theme = theme
	cfg.Template = tmpl
	cfg.Method = method
	cfg.Action = action
	return cfg
}

// NewSnippet 表单片段
func NewSnippet(theme ...string) *forms.Form {
	cfg := forms.NewConfig()
	if len(theme) > 0 {
		cfg.Theme = theme[0]
	}
	cfg.Template = common.TmplDir(cfg.Theme) + `/allfields.html`
	form := forms.NewWithConfig(cfg)
	return form
}

func ClearCache() {
	common.ClearCachedConfig()
	common.ClearCachedTemplate()
}

func DelCachedConfig(file string) bool {
	return common.DelCachedConfig(file)
}

func AddChoiceByKV(field fields.FieldInterface, kvData *echo.KVData, checkedKeys ...string) fields.FieldInterface {
	for _, kv := range kvData.Slice() {
		var checked bool
		if kv.H != nil {
			checked = kv.H.Bool(`checked`) || kv.H.Bool(`selected`)
		}
		if len(checkedKeys) > 0 {
			checked = com.InSlice(kv.K, checkedKeys)
		}
		field.AddChoice(kv.K, kv.V, checked)
	}
	return field
}

func SetChoiceByKV(field fields.FieldInterface, kvData *echo.KVData, checkedKeys ...string) fields.FieldInterface {
	choices := []fields.InputChoice{}
	for _, kv := range kvData.Slice() {
		var checked bool
		if kv.H != nil {
			checked = kv.H.Bool(`checked`) || kv.H.Bool(`selected`)
		}
		if len(checkedKeys) > 0 {
			checked = com.InSlice(kv.K, checkedKeys)
		}
		choices = append(choices, fields.InputChoice{
			ID:      kv.K,
			Val:     kv.V,
			Checked: checked,
		})
	}

	field.SetChoices(choices)
	return field
}
<commit_msg>update<commit_after>package formbuilder

import (
	"errors"
	"os"

	ncommon "github.com/admpub/nging/application/library/common"

	"github.com/coscms/forms"
	"github.com/coscms/forms/common"
	"github.com/coscms/forms/config"
	"github.com/coscms/forms/fields"

	"github.com/webx-top/com"
	"github.com/webx-top/echo"
	"github.com/webx-top/echo/formfilter"
	"github.com/webx-top/echo/middleware/render/driver"
	"github.com/webx-top/validation"
)

var ErrJSONConfigFileNameInvalid = errors.New("*.form.json name invalid")

// New 表单
//@param m: dbschema
func New(c echo.Context, m interface{}, jsonFile string, options ...Option) (*forms.Forms, error) {
	form := forms.New()
	form.Style = common.BOOTSTRAP
	for _, option := range options {
		if option == nil {
			continue
		}
		option(c, form)
	}
	form.SetLabelFunc(func(txt string) string {
		return c.T(txt)
	})
	var cfg *config.Config
	renderer := c.Renderer().(driver.Driver)
	jsonFile += `.form.json`
	jsonFile = renderer.TmplPath(c, jsonFile)
	if len(jsonFile) == 0 {
		return nil, ErrJSONConfigFileNameInvalid
	}
	b, err := renderer.RawContent(jsonFile)
	if err != nil {
		if os.IsNotExist(err) && renderer.Manager() != nil {
			form.SetModel(m)
			cfg = form.ToConfig()
			var jsonb []byte
			jsonb, err = form.ToJSONBlob(cfg)
			if err == nil {
				err = renderer.Manager().SetTemplate(jsonFile, jsonb)
				if err == nil {
					c.Logger().Infof(c.T(`生成表单配置文件“%v”成功。`), jsonFile)
				}
			}
		}
	} else {
		cfg, err = forms.Unmarshal(b, jsonFile)
	}
	if err != nil {
		return nil, err
	}
	if cfg == nil {
		cfg = form.NewConfig()
	}
	form.Init(cfg, m)
	if c.IsPost() {
		opts := []formfilter.Options{formfilter.Include(cfg.GetNames()...)}
		if customs, ok := c.Internal().Get(`formfilter.Options`).([]formfilter.Options); ok {
			opts = append(opts, customs...)
		}
		err = c.MustBind(m, formfilter.Build(opts...))
		if err == nil {
			form.ValidFromConfig()
			valid := form.Validate()
			if valid.HasError() {
				err = valid.Errors[0]
			}
		}
		if err != nil {
			if vErr, ok := err.(*validation.ValidationError); ok {
				c.Data().SetInfo(vErr.Message, 0).SetZone(vErr.Field)
			} else {
				c.Data().SetError(err)
			}
		}
	}
	setNextURLField := func() {
		if len(cfg.Action) == 0 {
			form.SetParam(`action`, c.Request().URI())
		}
		nextURL := c.Form(ncommon.DefaultReturnToURLVarName)
		if len(nextURL) == 0 {
			nextURL = c.Referer()
		}
		form.Elements(fields.HiddenField(ncommon.DefaultReturnToURLVarName).SetValue(nextURL))
	}
	csrfToken, ok := c.Get(`csrf`).(string)
	if ok {
		form.AddBeforeRender(func() {
			form.Elements(fields.HiddenField(`csrf`).SetValue(csrfToken))
			setNextURLField()
		})
	} else {
		form.AddBeforeRender(setNextURLField)
	}
	wrap := forms.NewForms(form)
	c.Set(`forms`, wrap)
	// 手动调用:
	// wrap.ParseFromConfig()
	return wrap, nil
}

// NewModel 表单
//@param m: dbschema
func NewModel(c echo.Context, m interface{}, cfg *config.Config, options ...Option) *forms.Forms {
	form := forms.New()
	for _, option := range options {
		if option == nil {
			continue
		}
		option(c, form)
	}
	form.SetLabelFunc(func(txt string) string {
		return c.T(txt)
	})
	if cfg == nil {
		cfg = form.NewConfig()
	}
	form.Init(cfg, m)
	if c.IsPost() {
		opts := []formfilter.Options{formfilter.Include(cfg.GetNames()...)}
		if customs, ok := c.Internal().Get(`formfilter.Options`).([]formfilter.Options); ok {
			opts = append(opts, customs...)
		}
		err := c.MustBind(m, formfilter.Build(opts...))
		if err == nil {
			validFields, _ := c.Internal().Get(`formbuilder.validFields`).([]string)
			err = form.Valid(validFields...)
		}
		if err != nil {
			if vErr, ok := err.(*validation.ValidationError); ok {
				c.Data().SetInfo(vErr.Message, 0).SetZone(vErr.Field)
			} else {
				c.Data().SetError(err)
			}
		}
	}
	setNextURLField := func() {
		if len(cfg.Action) == 0 {
			form.SetParam(`action`, c.Request().URI())
		}
		nextURL := c.Form(ncommon.DefaultReturnToURLVarName)
		if len(nextURL) == 0 {
			nextURL = c.Referer()
		}
		form.Elements(fields.HiddenField(ncommon.DefaultReturnToURLVarName).SetValue(nextURL))
	}
	csrfToken, ok := c.Get(`csrf`).(string)
	if ok {
		form.AddBeforeRender(func() {
			form.Elements(fields.HiddenField(`csrf`).SetValue(csrfToken))
			setNextURLField()
		})
	} else {
		form.AddBeforeRender(setNextURLField)
	}
	form.AddClass("form-horizontal").SetParam("role", "form")
	wrap := forms.NewForms(form)
	c.Set(`forms`, wrap)
	// 手动调用:
	// wrap.ParseFromConfig()
	return wrap
}

// NewConfig 表单配置
func NewConfig(theme, tmpl, method, action string) *config.Config {
	cfg := forms.NewConfig()
	cfg.Theme = theme
	cfg.Template = tmpl
	cfg.Method = method
	cfg.Action = action
	return cfg
}

// NewSnippet 表单片段
func NewSnippet(theme ...string) *forms.Form {
	cfg := forms.NewConfig()
	if len(theme) > 0 {
		cfg.Theme = theme[0]
	}
	cfg.Template = common.TmplDir(cfg.Theme) + `/allfields.html`
	form := forms.NewWithConfig(cfg)
	return form
}

func ClearCache() {
	common.ClearCachedConfig()
	common.ClearCachedTemplate()
}

func DelCachedConfig(file string) bool {
	return common.DelCachedConfig(file)
}

func AddChoiceByKV(field fields.FieldInterface, kvData *echo.KVData, checkedKeys ...string) fields.FieldInterface {
	for _, kv := range kvData.Slice() {
		var checked bool
		if kv.H != nil {
			checked = kv.H.Bool(`checked`) || kv.H.Bool(`selected`)
		}
		if len(checkedKeys) > 0 {
			checked = com.InSlice(kv.K, checkedKeys)
		}
		field.AddChoice(kv.K, kv.V, checked)
	}
	return field
}

func SetChoiceByKV(field fields.FieldInterface, kvData *echo.KVData, checkedKeys ...string) fields.FieldInterface {
	choices := []fields.InputChoice{}
	for _, kv := range kvData.Slice() {
		var checked bool
		if kv.H != nil {
			checked = kv.H.Bool(`checked`) || kv.H.Bool(`selected`)
		}
		if len(checkedKeys) > 0 {
			checked = com.InSlice(kv.K, checkedKeys)
		}
		choices = append(choices, fields.InputChoice{
			ID:      kv.K,
			Val:     kv.V,
			Checked: checked,
		})
	}

	field.SetChoices(choices)
	return field
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"log"
	"os"
	"strconv"
	"strings"
	"time"

	"golang.org/x/net/context"

	"github.com/codegangsta/cli"
	"github.com/crackcomm/crawl"
	"github.com/crackcomm/crawl/nsq/nsqcrawl"
	"github.com/golang/glog"
	"github.com/nsqio/go-nsq"
)

func main() {
	defer glog.Flush()

	// CRAWL_DEBUG environment variable turns on debug mode
	// crawler then can spit out logs using glog.V(3)
	var verbosity string
	if yes, _ := strconv.ParseBool(os.Getenv("CRAWL_DEBUG")); yes {
		verbosity = "-v=3"
	}

	// We are setting glog to log to stderr
	flag.CommandLine.Parse([]string{"-logtostderr", verbosity})

	app := cli.NewApp()
	app.Name = "crawl-schedule"
	app.HelpName = app.Name
	app.Version = "0.0.1"
	app.ArgsUsage = "<url>"
	app.Usage = "schedules a crawl request in nsq"
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:   "nsq-addr",
			EnvVar: "NSQ_ADDR",
			Usage:  "nsq address (required)",
		},
		cli.StringFlag{
			Name:   "topic",
			EnvVar: "TOPIC",
			Usage:  "crawl requests nsq topic (required)",
			Value:  "crawl_requests",
		},
		cli.StringSliceFlag{
			Name:  "form-value",
			Usage: "form value in format (format: key=value)",
		},
		cli.StringSliceFlag{
			Name:  "metadata",
			Usage: "metadata value in format (format: key=value)",
		},
		cli.StringSliceFlag{
			Name:  "callback",
			Usage: "crawl request callbacks (required)",
		},
		cli.StringFlag{
			Name:  "referer",
			Usage: "crawl request referer",
		},
		cli.StringFlag{
			Name:  "method",
			Usage: "crawl request referer",
			Value: "GET",
		},
		cli.DurationFlag{
			Name:  "timeout",
			Usage: "request timeout",
		},
	}
	app.Before = func(c *cli.Context) error {
		var errs []string
		if len(c.String("topic")) == 0 {
			errs = append(errs, "Topic cannot be empty")
		}
		if len(c.String("nsq-addr")) == 0 {
			errs = append(errs, "At least one --nsq-addr is required")
		}
		if len(c.StringSlice("callback")) == 0 {
			errs = append(errs, "At least one --callback is required")
		}
		if len(c.Args()) != 1 {
			errs = append(errs, "At least one url is required in arguments.")
		}
		if len(errs) != 0 {
			errs = append([]string{"Errors:"}, errs...)
			return errors.New(strings.Join(errs, "\n"))
		}
		return nil
	}
	app.Action = func(c *cli.Context) {
		form, err := listToMap(c.StringSlice("form-value"))
		if err != nil {
			glog.Fatalf("Form values error: %v", err)
		}

		metadata, err := listToMap(c.StringSlice("metadata"))
		if err != nil {
			glog.Fatalf("Metadata error: %v", err)
		}

		request := &crawl.Request{
			URL:       strings.Trim(c.Args().First(), `"'`),
			Method:    c.String("method"),
			Referer:   c.String("referer"),
			Callbacks: c.StringSlice("callback"),
			Form:      form,
			Metadata:  mapStringsToInterfaces(metadata),
		}

		if glog.V(3) {
			body, _ := json.MarshalIndent(request, "", "  ")
			glog.Infof("Scheduling request: %s", body)
		}

		ctx := context.Background()

		// Set context deadline
		if timeout := c.Duration("timeout"); timeout > 0 {
			ctx, _ = context.WithDeadline(ctx, time.Now().Add(timeout))
		}

		// Create nsq queue
		q := nsqcrawl.NewProducer(c.String("topic"))
		defer q.Close()

		// Connect to nsq
		cfg := nsq.NewConfig()
		cfg.OutputBufferTimeout = 0
		if err := q.Producer.ConnectConfig(c.String("nsq-addr"), cfg); err != nil {
			glog.Fatalf("Error connecting to nsq: %v", err)
		}

		// Configure NSQ producer logger
		q.Producer.SetLogger(log.New(os.Stdout, "[nsq]", 0), nsq.LogLevelError)

		// Schedule request
		if err := q.Schedule(ctx, request); err != nil {
			glog.Fatalf("schedule error: %v", err)
		}
	}

	if err := app.Run(os.Args); err != nil {
		glog.Fatal(err)
	}
}

func listToMap(list []string) (result map[string]string, err error) {
	result = make(map[string]string)
	for _, keyValue := range list {
		i := strings.Index(keyValue, "=")
		if i <= 0 {
			return nil, fmt.Errorf("%q is not valid", keyValue)
		}
		key := keyValue[:i]
		value := keyValue[i+1:]
		result[key] = value
	}
	return
}

func mapStringsToInterfaces(input map[string]string) (result map[string]interface{}) {
	result = make(map[string]interface{})
	for key, value := range input {
		result[key] = value
	}
	return
}
<commit_msg>crawl-schedule: context metadata<commit_after>package main

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"log"
	"os"
	"strconv"
	"strings"
	"time"

	"google.golang.org/grpc/metadata"

	"golang.org/x/net/context"

	"github.com/codegangsta/cli"
	"github.com/crackcomm/crawl"
	"github.com/crackcomm/crawl/nsq/nsqcrawl"
	"github.com/golang/glog"
	"github.com/nsqio/go-nsq"
)

func main() {
	defer glog.Flush()

	// CRAWL_DEBUG environment variable turns on debug mode
	// crawler then can spit out logs using glog.V(3)
	var verbosity string
	if yes, _ := strconv.ParseBool(os.Getenv("CRAWL_DEBUG")); yes {
		verbosity = "-v=3"
	}

	// We are setting glog to log to stderr
	flag.CommandLine.Parse([]string{"-logtostderr", verbosity})

	app := cli.NewApp()
	app.Name = "crawl-schedule"
	app.HelpName = app.Name
	app.Version = "0.0.1"
	app.ArgsUsage = "<url>"
	app.Usage = "schedules a crawl request in nsq"
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:   "nsq-addr",
			EnvVar: "NSQ_ADDR",
			Usage:  "nsq address (required)",
		},
		cli.StringFlag{
			Name:   "topic",
			EnvVar: "TOPIC",
			Usage:  "crawl requests nsq topic (required)",
			Value:  "crawl_requests",
		},
		cli.StringSliceFlag{
			Name:  "form-value",
			Usage: "form value in format (format: key=value)",
		},
		cli.StringSliceFlag{
			Name:  "metadata",
			Usage: "metadata value in format (format: key=value)",
		},
		cli.StringSliceFlag{
			Name:  "callback",
			Usage: "crawl request callbacks (required)",
		},
		cli.StringFlag{
			Name:  "referer",
			Usage: "crawl request referer",
		},
		cli.StringFlag{
			Name:  "method",
			Usage: "crawl request referer",
			Value: "GET",
		},
		cli.DurationFlag{
			Name:  "timeout",
			Usage: "request timeout",
		},
	}
	app.Before = func(c *cli.Context) error {
		var errs []string
		if len(c.String("topic")) == 0 {
			errs = append(errs, "Topic cannot be empty")
		}
		if len(c.String("nsq-addr")) == 0 {
			errs = append(errs, "At least one --nsq-addr is required")
		}
		if len(c.StringSlice("callback")) == 0 {
			errs = append(errs, "At least one --callback is required")
		}
		if len(c.Args()) != 1 {
			errs = append(errs, "At least one url is required in arguments.")
		}
		if len(errs) != 0 {
			errs = append([]string{"Errors:"}, errs...)
			return errors.New(strings.Join(errs, "\n"))
		}
		return nil
	}
	app.Action = func(c *cli.Context) {
		form, err := listToMap(c.StringSlice("form-value"))
		if err != nil {
			glog.Fatalf("Form values error: %v", err)
		}

		md, err := listToMap(c.StringSlice("metadata"))
		if err != nil {
			glog.Fatalf("Metadata error: %v", err)
		}

		request := &crawl.Request{
			URL:       strings.Trim(c.Args().First(), `"'`),
			Method:    c.String("method"),
			Referer:   c.String("referer"),
			Callbacks: c.StringSlice("callback"),
			Form:      form,
		}

		ctx := context.Background()
		if len(md) > 0 {
			ctx = metadata.NewContext(ctx, mapToMd(md))
		}

		if glog.V(3) {
			body, _ := json.MarshalIndent(request, "", "  ")
			glog.Infof("Scheduling request: %s", body)
		}

		// Set context deadline
		if timeout := c.Duration("timeout"); timeout > 0 {
			ctx, _ = context.WithDeadline(ctx, time.Now().Add(timeout))
		}

		// Create nsq queue
		q := nsqcrawl.NewProducer(c.String("topic"))
		defer q.Close()

		// Connect to nsq
		cfg := nsq.NewConfig()
		cfg.OutputBufferTimeout = 0
		if err := q.Producer.ConnectConfig(c.String("nsq-addr"), cfg); err != nil {
			glog.Fatalf("Error connecting to nsq: %v", err)
		}

		// Configure NSQ producer logger
		q.Producer.SetLogger(log.New(os.Stdout, "[nsq]", 0), nsq.LogLevelError)

		// Schedule request
		if err := q.Schedule(ctx, request); err != nil {
			glog.Fatalf("schedule error: %v", err)
		}
	}

	if err := app.Run(os.Args); err != nil {
		glog.Fatal(err)
	}
}

func mapToMd(input map[string]string) (md metadata.MD) {
	md = make(metadata.MD)
	for key, value := range input {
		md[key] = []string{value}
	}
	return
}

func listToMap(list []string) (result map[string]string, err error) {
	result = make(map[string]string)
	for _, keyValue := range list {
		i := strings.Index(keyValue, "=")
		if i <= 0 {
			return nil, fmt.Errorf("%q is not valid", keyValue)
		}
		key := keyValue[:i]
		value := keyValue[i+1:]
		result[key] = value
	}
	return
}

func mapStringsToInterfaces(input map[string]string) (result map[string]interface{}) {
	result = make(map[string]interface{})
	for key, value := range input {
		result[key] = value
	}
	return
}
<|endoftext|>


<commit_before>package teles

import (
	"bufio"
	"io"
	"net"
	"os"
	"strconv"
	"strings"
	"time"
)

type Connection struct {
	Server   string
	Timeout  time.Duration
	Socket   *net.TCPConn
	File     *os.File
	Attempts int
	Reader   *bufio.Reader
}

// Create a TCP socket for the connection
func (c *Connection) createSocket() error {
	addr, err := net.ResolveTCPAddr("tcp", c.Server)
	if err != nil {
		return err
	}
	c.Socket, err = net.DialTCP("tcp", nil, addr)
	if err != nil {
		return err
	}
	c.Reader = bufio.NewReader(c.Socket)
	if c.Attempts == 0 {
		c.Attempts = 3
	}
	return nil
}

// Sends a command to the server
func (c *Connection) Send(cmd string) error {
	if c.Socket == nil || c.Socket.LocalAddr() == nil {
		err := c.createSocket()
		if err != nil {
			return &TelesError{ErrorString: err.Error()}
		}
	}
	for i := 0; i < c.Attempts; i++ {
		_, err := c.Socket.Write([]byte(cmd + "\n"))

		if err != nil {
			c.createSocket()
			break
		}
	}

	return errSendFailed(cmd, strconv.Itoa(c.Attempts))
}

// Returns a single line from the socket file
func (c *Connection) Read() (line string, err error) {
	if c.Socket == nil || c.Socket.LocalAddr() == nil {
		err := c.createSocket()
		if err != nil {
			return "", &TelesError{ErrorString: err.Error()}
		}
	}

	l, rerr := c.Reader.ReadString('\n')
	if rerr != nil && rerr != io.EOF {
		return l, &TelesError{ErrorString: rerr.Error()}
	}
	return strings.TrimRight(l, "\r\n"), nil
}

// Reads a response block from the server. The servers responses are between
// `start` and `end` which can be optionally provided. Returns an array of
// the lines within the block.
func (c *Connection) ReadBlock() (lines []string, err error) {
	first, err := c.Read()
	if err != nil {
		return lines, err
	}
	if first != "START" {
		return lines, &TelesError{ErrorString: "Did not get block start START! Got '" + string(first) + "'!"}
	}

	for {
		line, err := c.Read()
		if err != nil {
			return lines, err
		}
		if line == "END" || line == "" {
			break
		}
		lines = append(lines, string(line))
	}
	return lines, nil
}

// Convenience wrapper around `send` and `read`. Sends a command,
// and reads the response, performing a retry if necessary.
func (c *Connection) SendAndReceive(cmd string) (string, error) {
	err := c.Send(cmd)
	if err != nil {
		return "", err
	}
	return c.Read()
}

func (c *Connection) responseBlockToMap() (map[string]string, error) {
	lines, err := c.ReadBlock()
	if err != nil {
		return nil, err
	}
	theMap := make(map[string]string)
	for _, line := range lines {
		split := strings.SplitN(line, " ", 2)
		theMap[split[0]] = split[1]
	}
	return theMap, nil
}
<commit_msg>Don't want to return an error here<commit_after>package teles

import (
	"bufio"
	"io"
	"net"
	"os"
	"strconv"
	"strings"
	"time"
)

type Connection struct {
	Server   string
	Timeout  time.Duration
	Socket   *net.TCPConn
	File     *os.File
	Attempts int
	Reader   *bufio.Reader
}

// Create a TCP socket for the connection
func (c *Connection) createSocket() error {
	addr, err := net.ResolveTCPAddr("tcp", c.Server)
	if err != nil {
		return err
	}
	c.Socket, err = net.DialTCP("tcp", nil, addr)
	if err != nil {
		return err
	}
	c.Reader = bufio.NewReader(c.Socket)
	if c.Attempts == 0 {
		c.Attempts = 3
	}
	return nil
}

// Sends a command to the server
func (c *Connection) Send(cmd string) error {
	if c.Socket == nil || c.Socket.LocalAddr() == nil {
		err := c.createSocket()
		if err != nil {
			return &TelesError{ErrorString: err.Error()}
		}
	}
	for i := 0; i < c.Attempts; i++ {
		_, err := c.Socket.Write([]byte(cmd + "\n"))

		if err != nil {
			c.createSocket()
			break
		}
		return nil
	}

	return errSendFailed(cmd, strconv.Itoa(c.Attempts))
}

// Returns a single line from the socket file
func (c *Connection) Read() (line string, err error) {
	if c.Socket == nil || c.Socket.LocalAddr() == nil {
		err := c.createSocket()
		if err != nil {
			return "", &TelesError{ErrorString: err.Error()}
		}
	}

	l, rerr := c.Reader.ReadString('\n')
	if rerr != nil && rerr != io.EOF {
		return l, &TelesError{ErrorString: rerr.Error()}
	}
	return strings.TrimRight(l, "\r\n"), nil
}

// Reads a response block from the server. The servers responses are between
// `start` and `end` which can be optionally provided. Returns an array of
// the lines within the block.
func (c *Connection) ReadBlock() (lines []string, err error) {
	first, err := c.Read()
	if err != nil {
		return lines, err
	}
	if first != "START" {
		return lines, &TelesError{ErrorString: "Did not get block start START! Got '" + string(first) + "'!"}
	}

	for {
		line, err := c.Read()
		if err != nil {
			return lines, err
		}
		if line == "END" || line == "" {
			break
		}
		lines = append(lines, string(line))
	}
	return lines, nil
}

// Convenience wrapper around `send` and `read`. Sends a command,
// and reads the response, performing a retry if necessary.
func (c *Connection) SendAndReceive(cmd string) (string, error) {
	err := c.Send(cmd)
	if err != nil {
		return "", err
	}
	return c.Read()
}

func (c *Connection) responseBlockToMap() (map[string]string, error) {
	lines, err := c.ReadBlock()
	if err != nil {
		return nil, err
	}
	theMap := make(map[string]string)
	for _, line := range lines {
		split := strings.SplitN(line, " ", 2)
		theMap[split[0]] = split[1]
	}
	return theMap, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2014 Google Inc. All rights reserved.

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

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

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

package apiserver

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"path"
	"runtime/debug"
	"strings"
	"time"

	"code.google.com/p/go.net/websocket"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/healthz"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/httplog"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
	"github.com/golang/glog"
)

// errNotFound is an error which indicates that a specified resource is not found.
type errNotFound string

// Error returns a string representation of the err.
func (err errNotFound) Error() string {
	return string(err)
}

// IsNotFound determines if the err is an error which indicates that a specified resource was not found.
func IsNotFound(err error) bool {
	_, ok := err.(errNotFound)
	return ok
}

// NewNotFoundErr returns a new error which indicates that the resource of the kind and the name was not found.
func NewNotFoundErr(kind, name string) error {
	return errNotFound(fmt.Sprintf("%s %q not found", kind, name))
}

// APIServer is an HTTPHandler that delegates to RESTStorage objects.
// It handles URLs of the form:
// ${prefix}/${storage_key}[/${object_name}]
// Where 'prefix' is an arbitrary string, and 'storage_key' points to a RESTStorage object stored in storage.
//
// TODO: consider migrating this to go-restful which is a more full-featured version of the same thing.
type APIServer struct {
	prefix  string
	storage map[string]RESTStorage
	ops     *Operations
	mux     *http.ServeMux
}

// New creates a new APIServer object.
// 'storage' contains a map of handlers.
// 'prefix' is the hosting path prefix.
func New(storage map[string]RESTStorage, prefix string) *APIServer {
	s := &APIServer{
		storage: storage,
		prefix:  strings.TrimRight(prefix, "/"),
		ops:     NewOperations(),
		mux:     http.NewServeMux(),
	}

	s.mux.Handle("/logs/", http.StripPrefix("/logs/", http.FileServer(http.Dir("/var/log/"))))
	s.mux.HandleFunc(s.prefix+"/", s.handleREST)
	healthz.InstallHandler(s.mux)

	s.mux.HandleFunc("/version", s.handleVersionReq)
	s.mux.HandleFunc("/", handleIndex)

	// Handle both operations and operations/* with the same handler
	s.mux.HandleFunc(s.operationPrefix(), s.handleOperationRequest)
	s.mux.HandleFunc(s.operationPrefix()+"/", s.handleOperationRequest)

	s.mux.HandleFunc(s.watchPrefix()+"/", s.handleWatch)

	s.mux.HandleFunc("/proxy/minion/", s.handleProxyMinion)

	return s
}

// handleVersionReq writes the server's version information.
func (server *APIServer) handleVersionReq(w http.ResponseWriter, req *http.Request) {
	server.writeRawJSON(http.StatusOK, version.Get(), w)
}

// HTTP Handler interface
func (s *APIServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	defer func() {
		if x := recover(); x != nil {
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprint(w, "apiserver panic. Look in log for details.")
			glog.Infof("APIServer panic'd on %v %v: %#v\n%s\n", req.Method, req.RequestURI, x, debug.Stack())
		}
	}()
	defer httplog.MakeLogged(req, &w).StacktraceWhen(
		httplog.StatusIsNot(
			http.StatusOK,
			http.StatusAccepted,
			http.StatusConflict,
			http.StatusNotFound,
		),
	).Log()

	// Dispatch via our mux.
	s.mux.ServeHTTP(w, req)
}

// handleREST handles requests to all our RESTStorage objects.
func (s *APIServer) handleREST(w http.ResponseWriter, req *http.Request) {
	if !strings.HasPrefix(req.URL.Path, s.prefix) {
		notFound(w, req)
		return
	}
	requestParts := strings.Split(req.URL.Path[len(s.prefix):], "/")[1:]
	if len(requestParts) < 1 {
		notFound(w, req)
		return
	}
	storage := s.storage[requestParts[0]]
	if storage == nil {
		httplog.LogOf(w).Addf("'%v' has no storage object", requestParts[0])
		notFound(w, req)
		return
	}

	s.handleRESTStorage(requestParts, req, w, storage)
}

// write writes an API object in wire format.
func (s *APIServer) write(statusCode int, object interface{}, w http.ResponseWriter) {
	output, err := api.Encode(object)
	if err != nil {
		internalError(err, w)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(statusCode)
	w.Write(output)
}

// writeRawJSON writes a non-API object in JSON.
func (s *APIServer) writeRawJSON(statusCode int, object interface{}, w http.ResponseWriter) {
	output, err := json.Marshal(object)
	if err != nil {
		internalError(err, w)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(statusCode)
	w.Write(output)
}

// finishReq finishes up a request, waiting until the operation finishes or, after a timeout, creating an
// Operation to receive the result and returning its ID down the writer.
func (s *APIServer) finishReq(out <-chan interface{}, sync bool, timeout time.Duration, w http.ResponseWriter) {
	op := s.ops.NewOperation(out)
	if sync {
		op.WaitFor(timeout)
	}
	obj, complete := op.StatusOrResult()
	if complete {
		status := http.StatusOK
		switch stat := obj.(type) {
		case api.Status:
			httplog.LogOf(w).Addf("programmer error: use *api.Status as a result, not api.Status.")
			if stat.Code != 0 {
				status = stat.Code
			}
		case *api.Status:
			if stat.Code != 0 {
				status = stat.Code
			}
		}
		s.write(status, obj, w)
	} else {
		s.write(http.StatusAccepted, obj, w)
	}
}

// handleRESTStorage is the main dispatcher for a storage object.  It switches on the HTTP method, and then
// on path length, according to the following table:
//   Method     Path          Action
//   GET        /foo          list
//   GET        /foo/bar      get 'bar'
//   POST       /foo          create
//   PUT        /foo/bar      update 'bar'
//   DELETE     /foo/bar      delete 'bar'
// Returns 404 if the method/pattern doesn't match one of these entries
// The s accepts several query parameters:
//    sync=[false|true] Synchronous request (only applies to create, update, delete operations)
//    timeout=<duration> Timeout for synchronous requests, only applies if sync=true
//    labels=<label-selector> Used for filtering list operations
func (s *APIServer) handleRESTStorage(parts []string, req *http.Request, w http.ResponseWriter, storage RESTStorage) {
	sync := req.URL.Query().Get("sync") == "true"
	timeout := parseTimeout(req.URL.Query().Get("timeout"))
	switch req.Method {
	case "GET":
		switch len(parts) {
		case 1:
			selector, err := labels.ParseSelector(req.URL.Query().Get("labels"))
			if err != nil {
				internalError(err, w)
				return
			}
			list, err := storage.List(selector)
			if err != nil {
				internalError(err, w)
				return
			}
			s.write(http.StatusOK, list, w)
		case 2:
			item, err := storage.Get(parts[1])
			if IsNotFound(err) {
				notFound(w, req)
				return
			}
			if err != nil {
				internalError(err, w)
				return
			}
			s.write(http.StatusOK, item, w)
		default:
			notFound(w, req)
		}
	case "POST":
		if len(parts) != 1 {
			notFound(w, req)
			return
		}
		body, err := readBody(req)
		if err != nil {
			internalError(err, w)
			return
		}
		obj, err := storage.Extract(body)
		if IsNotFound(err) {
			notFound(w, req)
			return
		}
		if err != nil {
			internalError(err, w)
			return
		}
		out, err := storage.Create(obj)
		if IsNotFound(err) {
			notFound(w, req)
			return
		}
		if err != nil {
			internalError(err, w)
			return
		}
		s.finishReq(out, sync, timeout, w)
	case "DELETE":
		if len(parts) != 2 {
			notFound(w, req)
			return
		}
		out, err := storage.Delete(parts[1])
		if IsNotFound(err) {
			notFound(w, req)
			return
		}
		if err != nil {
			internalError(err, w)
			return
		}
		s.finishReq(out, sync, timeout, w)
	case "PUT":
		if len(parts) != 2 {
			notFound(w, req)
			return
		}
		body, err := readBody(req)
		if err != nil {
			internalError(err, w)
			return
		}
		obj, err := storage.Extract(body)
		if IsNotFound(err) {
			notFound(w, req)
			return
		}
		if err != nil {
			internalError(err, w)
			return
		}
		out, err := storage.Update(obj)
		if IsNotFound(err) {
			notFound(w, req)
			return
		}
		if err != nil {
			internalError(err, w)
			return
		}
		s.finishReq(out, sync, timeout, w)
	default:
		notFound(w, req)
	}
}

func (s *APIServer) operationPrefix() string {
	return path.Join(s.prefix, "operations")
}

func (s *APIServer) handleOperationRequest(w http.ResponseWriter, req *http.Request) {
	opPrefix := s.operationPrefix()
	if !strings.HasPrefix(req.URL.Path, opPrefix) {
		notFound(w, req)
		return
	}
	trimmed := strings.TrimLeft(req.URL.Path[len(opPrefix):], "/")
	parts := strings.Split(trimmed, "/")
	if len(parts) > 1 {
		notFound(w, req)
		return
	}
	if req.Method != "GET" {
		notFound(w, req)
		return
	}
	if len(parts) == 0 {
		// List outstanding operations.
		list := s.ops.List()
		s.write(http.StatusOK, list, w)
		return
	}

	op := s.ops.Get(parts[0])
	if op == nil {
		notFound(w, req)
		return
	}

	obj, complete := op.StatusOrResult()
	if complete {
		s.write(http.StatusOK, obj, w)
	} else {
		s.write(http.StatusAccepted, obj, w)
	}
}

func (s *APIServer) watchPrefix() string {
	return path.Join(s.prefix, "watch")
}

func (s *APIServer) handleWatch(w http.ResponseWriter, req *http.Request) {
	prefix := s.watchPrefix()
	if !strings.HasPrefix(req.URL.Path, prefix) {
		notFound(w, req)
		return
	}
	parts := strings.Split(req.URL.Path[len(prefix):], "/")[1:]
	if req.Method != "GET" || len(parts) < 1 {
		notFound(w, req)
	}
	storage := s.storage[parts[0]]
	if storage == nil {
		notFound(w, req)
	}
	if watcher, ok := storage.(ResourceWatcher); ok {
		var watching watch.Interface
		var err error
		if id := req.URL.Query().Get("id"); id != "" {
			watching, err = watcher.WatchSingle(id)
		} else {
			watching, err = watcher.WatchAll()
		}
		if err != nil {
			internalError(err, w)
			return
		}

		// TODO: This is one watch per connection. We want to multiplex, so that
		// multiple watches of the same thing don't create two watches downstream.
		watchServer := &WatchServer{watching}
		if req.Header.Get("Connection") == "Upgrade" && req.Header.Get("Upgrade") == "websocket" {
			websocket.Handler(watchServer.HandleWS).ServeHTTP(httplog.Unlogged(w), req)
		} else {
			watchServer.ServeHTTP(w, req)
		}
		return
	}

	notFound(w, req)
}

func parseTimeout(str string) time.Duration {
	if str != "" {
		timeout, err := time.ParseDuration(str)
		if err == nil {
			return timeout
		}
		glog.Errorf("Failed to parse: %#v '%s'", err, str)
	}
	return 30 * time.Second
}

func readBody(req *http.Request) ([]byte, error) {
	defer req.Body.Close()
	return ioutil.ReadAll(req.Body)
}
<commit_msg>Rename write -> writeJSON<commit_after>/*
Copyright 2014 Google Inc. All rights reserved.

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

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

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

package apiserver

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"path"
	"runtime/debug"
	"strings"
	"time"

	"code.google.com/p/go.net/websocket"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/healthz"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/httplog"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
	"github.com/golang/glog"
)

// errNotFound is an error which indicates that a specified resource is not found.
type errNotFound string

// Error returns a string representation of the err.
func (err errNotFound) Error() string {
	return string(err)
}

// IsNotFound determines if the err is an error which indicates that a specified resource was not found.
func IsNotFound(err error) bool {
	_, ok := err.(errNotFound)
	return ok
}

// NewNotFoundErr returns a new error which indicates that the resource of the kind and the name was not found.
func NewNotFoundErr(kind, name string) error {
	return errNotFound(fmt.Sprintf("%s %q not found", kind, name))
}

// APIServer is an HTTPHandler that delegates to RESTStorage objects.
// It handles URLs of the form:
// ${prefix}/${storage_key}[/${object_name}]
// Where 'prefix' is an arbitrary string, and 'storage_key' points to a RESTStorage object stored in storage.
//
// TODO: consider migrating this to go-restful which is a more full-featured version of the same thing.
type APIServer struct {
	prefix  string
	storage map[string]RESTStorage
	ops     *Operations
	mux     *http.ServeMux
}

// New creates a new APIServer object.
// 'storage' contains a map of handlers.
// 'prefix' is the hosting path prefix.
func New(storage map[string]RESTStorage, prefix string) *APIServer {
	s := &APIServer{
		storage: storage,
		prefix:  strings.TrimRight(prefix, "/"),
		ops:     NewOperations(),
		mux:     http.NewServeMux(),
	}

	s.mux.Handle("/logs/", http.StripPrefix("/logs/", http.FileServer(http.Dir("/var/log/"))))
	s.mux.HandleFunc(s.prefix+"/", s.handleREST)
	healthz.InstallHandler(s.mux)

	s.mux.HandleFunc("/version", s.handleVersionReq)
	s.mux.HandleFunc("/", handleIndex)

	// Handle both operations and operations/* with the same handler
	s.mux.HandleFunc(s.operationPrefix(), s.handleOperationRequest)
	s.mux.HandleFunc(s.operationPrefix()+"/", s.handleOperationRequest)

	s.mux.HandleFunc(s.watchPrefix()+"/", s.handleWatch)

	s.mux.HandleFunc("/proxy/minion/", s.handleProxyMinion)

	return s
}

// handleVersionReq writes the server's version information.
func (s *APIServer) handleVersionReq(w http.ResponseWriter, req *http.Request) {
	writeRawJSON(http.StatusOK, version.Get(), w)
}

// HTTP Handler interface
func (s *APIServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	defer func() {
		if x := recover(); x != nil {
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprint(w, "apiserver panic. Look in log for details.")
			glog.Infof("APIServer panic'd on %v %v: %#v\n%s\n", req.Method, req.RequestURI, x, debug.Stack())
		}
	}()
	defer httplog.MakeLogged(req, &w).StacktraceWhen(
		httplog.StatusIsNot(
			http.StatusOK,
			http.StatusAccepted,
			http.StatusConflict,
			http.StatusNotFound,
		),
	).Log()

	// Dispatch via our mux.
	s.mux.ServeHTTP(w, req)
}

// handleREST handles requests to all our RESTStorage objects.
func (s *APIServer) handleREST(w http.ResponseWriter, req *http.Request) {
	if !strings.HasPrefix(req.URL.Path, s.prefix) {
		notFound(w, req)
		return
	}
	requestParts := strings.Split(req.URL.Path[len(s.prefix):], "/")[1:]
	if len(requestParts) < 1 {
		notFound(w, req)
		return
	}
	storage := s.storage[requestParts[0]]
	if storage == nil {
		httplog.LogOf(w).Addf("'%v' has no storage object", requestParts[0])
		notFound(w, req)
		return
	}

	s.handleRESTStorage(requestParts, req, w, storage)
}

// finishReq finishes up a request, waiting until the operation finishes or, after a timeout, creating an
// Operation to receive the result and returning its ID down the writer.
func (s *APIServer) finishReq(out <-chan interface{}, sync bool, timeout time.Duration, w http.ResponseWriter) {
	op := s.ops.NewOperation(out)
	if sync {
		op.WaitFor(timeout)
	}
	obj, complete := op.StatusOrResult()
	if complete {
		status := http.StatusOK
		switch stat := obj.(type) {
		case api.Status:
			httplog.LogOf(w).Addf("programmer error: use *api.Status as a result, not api.Status.")
			if stat.Code != 0 {
				status = stat.Code
			}
		case *api.Status:
			if stat.Code != 0 {
				status = stat.Code
			}
		}
		writeJSON(status, obj, w)
	} else {
		writeJSON(http.StatusAccepted, obj, w)
	}
}

// handleRESTStorage is the main dispatcher for a storage object.  It switches on the HTTP method, and then
// on path length, according to the following table:
//   Method     Path          Action
//   GET        /foo          list
//   GET        /foo/bar      get 'bar'
//   POST       /foo          create
//   PUT        /foo/bar      update 'bar'
//   DELETE     /foo/bar      delete 'bar'
// Returns 404 if the method/pattern doesn't match one of these entries
// The s accepts several query parameters:
//    sync=[false|true] Synchronous request (only applies to create, update, delete operations)
//    timeout=<duration> Timeout for synchronous requests, only applies if sync=true
//    labels=<label-selector> Used for filtering list operations
func (s *APIServer) handleRESTStorage(parts []string, req *http.Request, w http.ResponseWriter, storage RESTStorage) {
	sync := req.URL.Query().Get("sync") == "true"
	timeout := parseTimeout(req.URL.Query().Get("timeout"))
	switch req.Method {
	case "GET":
		switch len(parts) {
		case 1:
			selector, err := labels.ParseSelector(req.URL.Query().Get("labels"))
			if err != nil {
				internalError(err, w)
				return
			}
			list, err := storage.List(selector)
			if err != nil {
				internalError(err, w)
				return
			}
			writeJSON(http.StatusOK, list, w)
		case 2:
			item, err := storage.Get(parts[1])
			if IsNotFound(err) {
				notFound(w, req)
				return
			}
			if err != nil {
				internalError(err, w)
				return
			}
			writeJSON(http.StatusOK, item, w)
		default:
			notFound(w, req)
		}
	case "POST":
		if len(parts) != 1 {
			notFound(w, req)
			return
		}
		body, err := readBody(req)
		if err != nil {
			internalError(err, w)
			return
		}
		obj, err := storage.Extract(body)
		if IsNotFound(err) {
			notFound(w, req)
			return
		}
		if err != nil {
			internalError(err, w)
			return
		}
		out, err := storage.Create(obj)
		if IsNotFound(err) {
			notFound(w, req)
			return
		}
		if err != nil {
			internalError(err, w)
			return
		}
		s.finishReq(out, sync, timeout, w)
	case "DELETE":
		if len(parts) != 2 {
			notFound(w, req)
			return
		}
		out, err := storage.Delete(parts[1])
		if IsNotFound(err) {
			notFound(w, req)
			return
		}
		if err != nil {
			internalError(err, w)
			return
		}
		s.finishReq(out, sync, timeout, w)
	case "PUT":
		if len(parts) != 2 {
			notFound(w, req)
			return
		}
		body, err := readBody(req)
		if err != nil {
			internalError(err, w)
			return
		}
		obj, err := storage.Extract(body)
		if IsNotFound(err) {
			notFound(w, req)
			return
		}
		if err != nil {
			internalError(err, w)
			return
		}
		out, err := storage.Update(obj)
		if IsNotFound(err) {
			notFound(w, req)
			return
		}
		if err != nil {
			internalError(err, w)
			return
		}
		s.finishReq(out, sync, timeout, w)
	default:
		notFound(w, req)
	}
}

func (s *APIServer) operationPrefix() string {
	return path.Join(s.prefix, "operations")
}

func (s *APIServer) handleOperationRequest(w http.ResponseWriter, req *http.Request) {
	opPrefix := s.operationPrefix()
	if !strings.HasPrefix(req.URL.Path, opPrefix) {
		notFound(w, req)
		return
	}
	trimmed := strings.TrimLeft(req.URL.Path[len(opPrefix):], "/")
	parts := strings.Split(trimmed, "/")
	if len(parts) > 1 {
		notFound(w, req)
		return
	}
	if req.Method != "GET" {
		notFound(w, req)
		return
	}
	if len(parts) == 0 {
		// List outstanding operations.
		list := s.ops.List()
		writeJSON(http.StatusOK, list, w)
		return
	}

	op := s.ops.Get(parts[0])
	if op == nil {
		notFound(w, req)
		return
	}

	obj, complete := op.StatusOrResult()
	if complete {
		writeJSON(http.StatusOK, obj, w)
	} else {
		writeJSON(http.StatusAccepted, obj, w)
	}
}

func (s *APIServer) watchPrefix() string {
	return path.Join(s.prefix, "watch")
}

func (s *APIServer) handleWatch(w http.ResponseWriter, req *http.Request) {
	prefix := s.watchPrefix()
	if !strings.HasPrefix(req.URL.Path, prefix) {
		notFound(w, req)
		return
	}
	parts := strings.Split(req.URL.Path[len(prefix):], "/")[1:]
	if req.Method != "GET" || len(parts) < 1 {
		notFound(w, req)
	}
	storage := s.storage[parts[0]]
	if storage == nil {
		notFound(w, req)
	}
	if watcher, ok := storage.(ResourceWatcher); ok {
		var watching watch.Interface
		var err error
		if id := req.URL.Query().Get("id"); id != "" {
			watching, err = watcher.WatchSingle(id)
		} else {
			watching, err = watcher.WatchAll()
		}
		if err != nil {
			internalError(err, w)
			return
		}

		// TODO: This is one watch per connection. We want to multiplex, so that
		// multiple watches of the same thing don't create two watches downstream.
		watchServer := &WatchServer{watching}
		if req.Header.Get("Connection") == "Upgrade" && req.Header.Get("Upgrade") == "websocket" {
			websocket.Handler(watchServer.HandleWS).ServeHTTP(httplog.Unlogged(w), req)
		} else {
			watchServer.ServeHTTP(w, req)
		}
		return
	}

	notFound(w, req)
}

func writeJSON(statusCode int, object interface{}, w http.ResponseWriter) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(statusCode)
	output, err := api.Encode(object)
	if err != nil {
		internalError(err, w)
		return
	}
	w.Write(output)
}

// writeRawJSON writes a non-API object in JSON.
func writeRawJSON(statusCode int, object interface{}, w http.ResponseWriter) {
	output, err := json.Marshal(object)
	if err != nil {
		internalError(err, w)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(statusCode)
	w.Write(output)
}

func parseTimeout(str string) time.Duration {
	if str != "" {
		timeout, err := time.ParseDuration(str)
		if err == nil {
			return timeout
		}
		glog.Errorf("Failed to parse: %#v '%s'", err, str)
	}
	return 30 * time.Second
}

func readBody(req *http.Request) ([]byte, error) {
	defer req.Body.Close()
	return ioutil.ReadAll(req.Body)
}
<|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 apiserver

import (
	"fmt"
	"net/http"
	"time"

	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install"
	v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
	"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
	externalinformers "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions"
	"k8s.io/apiextensions-apiserver/pkg/controller/apiapproval"
	"k8s.io/apiextensions-apiserver/pkg/controller/establish"
	"k8s.io/apiextensions-apiserver/pkg/controller/finalizer"
	"k8s.io/apiextensions-apiserver/pkg/controller/nonstructuralschema"
	openapicontroller "k8s.io/apiextensions-apiserver/pkg/controller/openapi"
	"k8s.io/apiextensions-apiserver/pkg/controller/status"
	"k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/runtime/serializer"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/apimachinery/pkg/version"
	"k8s.io/apiserver/pkg/endpoints/discovery"
	genericregistry "k8s.io/apiserver/pkg/registry/generic"
	"k8s.io/apiserver/pkg/registry/rest"
	genericapiserver "k8s.io/apiserver/pkg/server"
	serverstorage "k8s.io/apiserver/pkg/server/storage"
	"k8s.io/apiserver/pkg/util/webhook"
)

var (
	Scheme = runtime.NewScheme()
	Codecs = serializer.NewCodecFactory(Scheme)

	// if you modify this, make sure you update the crEncoder
	unversionedVersion = schema.GroupVersion{Group: "", Version: "v1"}
	unversionedTypes   = []runtime.Object{
		&metav1.Status{},
		&metav1.WatchEvent{},
		&metav1.APIVersions{},
		&metav1.APIGroupList{},
		&metav1.APIGroup{},
		&metav1.APIResourceList{},
	}
)

func init() {
	install.Install(Scheme)

	// we need to add the options to empty v1
	metav1.AddToGroupVersion(Scheme, schema.GroupVersion{Group: "", Version: "v1"})

	Scheme.AddUnversionedTypes(unversionedVersion, unversionedTypes...)
}

type ExtraConfig struct {
	CRDRESTOptionsGetter genericregistry.RESTOptionsGetter

	// MasterCount is used to detect whether cluster is HA, and if it is
	// the CRD Establishing will be hold by 5 seconds.
	MasterCount int

	// ServiceResolver is used in CR webhook converters to resolve webhook's service names
	ServiceResolver webhook.ServiceResolver
	// AuthResolverWrapper is used in CR webhook converters
	AuthResolverWrapper webhook.AuthenticationInfoResolverWrapper
}

type Config struct {
	GenericConfig *genericapiserver.RecommendedConfig
	ExtraConfig   ExtraConfig
}

type completedConfig struct {
	GenericConfig genericapiserver.CompletedConfig
	ExtraConfig   *ExtraConfig
}

type CompletedConfig struct {
	// Embed a private pointer that cannot be instantiated outside of this package.
	*completedConfig
}

type CustomResourceDefinitions struct {
	GenericAPIServer *genericapiserver.GenericAPIServer

	// provided for easier embedding
	Informers externalinformers.SharedInformerFactory
}

// Complete fills in any fields not set that are required to have valid data. It's mutating the receiver.
func (cfg *Config) Complete() CompletedConfig {
	c := completedConfig{
		cfg.GenericConfig.Complete(),
		&cfg.ExtraConfig,
	}

	c.GenericConfig.EnableDiscovery = false
	if c.GenericConfig.Version == nil {
		c.GenericConfig.Version = &version.Info{
			Major: "0",
			Minor: "1",
		}
	}

	return CompletedConfig{&c}
}

// New returns a new instance of CustomResourceDefinitions from the given config.
func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget) (*CustomResourceDefinitions, error) {
	genericServer, err := c.GenericConfig.New("apiextensions-apiserver", delegationTarget)
	if err != nil {
		return nil, err
	}

	s := &CustomResourceDefinitions{
		GenericAPIServer: genericServer,
	}

	// used later  to filter the served resource by those that have expired.
	resourceExpirationEvaluator, err := genericapiserver.NewResourceExpirationEvaluator(*c.GenericConfig.Version)
	if err != nil {
		return nil, err
	}

	apiResourceConfig := c.GenericConfig.MergedResourceConfig
	apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(apiextensions.GroupName, Scheme, metav1.ParameterCodec, Codecs)
	if resourceExpirationEvaluator.ShouldServeForVersion(1, 22) && apiResourceConfig.VersionEnabled(v1beta1.SchemeGroupVersion) {
		storage := map[string]rest.Storage{}
		// customresourcedefinitions
		customResourceDefinitionStorage, err := customresourcedefinition.NewREST(Scheme, c.GenericConfig.RESTOptionsGetter)
		if err != nil {
			return nil, err
		}
		storage["customresourcedefinitions"] = customResourceDefinitionStorage
		storage["customresourcedefinitions/status"] = customresourcedefinition.NewStatusREST(Scheme, customResourceDefinitionStorage)

		apiGroupInfo.VersionedResourcesStorageMap[v1beta1.SchemeGroupVersion.Version] = storage
	}
	if apiResourceConfig.VersionEnabled(v1.SchemeGroupVersion) {
		storage := map[string]rest.Storage{}
		// customresourcedefinitions
		customResourceDefinitionStorage, err := customresourcedefinition.NewREST(Scheme, c.GenericConfig.RESTOptionsGetter)
		if err != nil {
			return nil, err
		}
		storage["customresourcedefinitions"] = customResourceDefinitionStorage
		storage["customresourcedefinitions/status"] = customresourcedefinition.NewStatusREST(Scheme, customResourceDefinitionStorage)

		apiGroupInfo.VersionedResourcesStorageMap[v1.SchemeGroupVersion.Version] = storage
	}

	if err := s.GenericAPIServer.InstallAPIGroup(&apiGroupInfo); err != nil {
		return nil, err
	}

	crdClient, err := clientset.NewForConfig(s.GenericAPIServer.LoopbackClientConfig)
	if err != nil {
		// it's really bad that this is leaking here, but until we can fix the test (which I'm pretty sure isn't even testing what it wants to test),
		// we need to be able to move forward
		return nil, fmt.Errorf("failed to create clientset: %v", err)
	}
	s.Informers = externalinformers.NewSharedInformerFactory(crdClient, 5*time.Minute)

	delegateHandler := delegationTarget.UnprotectedHandler()
	if delegateHandler == nil {
		delegateHandler = http.NotFoundHandler()
	}

	versionDiscoveryHandler := &versionDiscoveryHandler{
		discovery: map[schema.GroupVersion]*discovery.APIVersionHandler{},
		delegate:  delegateHandler,
	}
	groupDiscoveryHandler := &groupDiscoveryHandler{
		discovery: map[string]*discovery.APIGroupHandler{},
		delegate:  delegateHandler,
	}
	establishingController := establish.NewEstablishingController(s.Informers.Apiextensions().V1().CustomResourceDefinitions(), crdClient.ApiextensionsV1())
	crdHandler, err := NewCustomResourceDefinitionHandler(
		versionDiscoveryHandler,
		groupDiscoveryHandler,
		s.Informers.Apiextensions().V1().CustomResourceDefinitions(),
		delegateHandler,
		c.ExtraConfig.CRDRESTOptionsGetter,
		c.GenericConfig.AdmissionControl,
		establishingController,
		c.ExtraConfig.ServiceResolver,
		c.ExtraConfig.AuthResolverWrapper,
		c.ExtraConfig.MasterCount,
		s.GenericAPIServer.Authorizer,
		c.GenericConfig.RequestTimeout,
		time.Duration(c.GenericConfig.MinRequestTimeout)*time.Second,
		apiGroupInfo.StaticOpenAPISpec,
		c.GenericConfig.MaxRequestBodyBytes,
	)
	if err != nil {
		return nil, err
	}
	s.GenericAPIServer.Handler.NonGoRestfulMux.Handle("/apis", crdHandler)
	s.GenericAPIServer.Handler.NonGoRestfulMux.HandlePrefix("/apis/", crdHandler)

	discoveryController := NewDiscoveryController(s.Informers.Apiextensions().V1().CustomResourceDefinitions(), versionDiscoveryHandler, groupDiscoveryHandler)
	namingController := status.NewNamingConditionController(s.Informers.Apiextensions().V1().CustomResourceDefinitions(), crdClient.ApiextensionsV1())
	nonStructuralSchemaController := nonstructuralschema.NewConditionController(s.Informers.Apiextensions().V1().CustomResourceDefinitions(), crdClient.ApiextensionsV1())
	apiApprovalController := apiapproval.NewKubernetesAPIApprovalPolicyConformantConditionController(s.Informers.Apiextensions().V1().CustomResourceDefinitions(), crdClient.ApiextensionsV1())
	finalizingController := finalizer.NewCRDFinalizer(
		s.Informers.Apiextensions().V1().CustomResourceDefinitions(),
		crdClient.ApiextensionsV1(),
		crdHandler,
	)
	openapiController := openapicontroller.NewController(s.Informers.Apiextensions().V1().CustomResourceDefinitions())

	s.GenericAPIServer.AddPostStartHookOrDie("start-apiextensions-informers", func(context genericapiserver.PostStartHookContext) error {
		s.Informers.Start(context.StopCh)
		return nil
	})
	s.GenericAPIServer.AddPostStartHookOrDie("start-apiextensions-controllers", func(context genericapiserver.PostStartHookContext) error {
		// OpenAPIVersionedService and StaticOpenAPISpec are populated in generic apiserver PrepareRun().
		// Together they serve the /openapi/v2 endpoint on a generic apiserver. A generic apiserver may
		// choose to not enable OpenAPI by having null openAPIConfig, and thus OpenAPIVersionedService
		// and StaticOpenAPISpec are both null. In that case we don't run the CRD OpenAPI controller.
		if s.GenericAPIServer.OpenAPIVersionedService != nil && s.GenericAPIServer.StaticOpenAPISpec != nil {
			go openapiController.Run(s.GenericAPIServer.StaticOpenAPISpec, s.GenericAPIServer.OpenAPIVersionedService, context.StopCh)
		}

		go namingController.Run(context.StopCh)
		go establishingController.Run(context.StopCh)
		go nonStructuralSchemaController.Run(5, context.StopCh)
		go apiApprovalController.Run(5, context.StopCh)
		go finalizingController.Run(5, context.StopCh)

		discoverySyncedCh := make(chan struct{})
		go discoveryController.Run(context.StopCh, discoverySyncedCh)
		select {
		case <-context.StopCh:
		case <-discoverySyncedCh:
		}

		return nil
	})
	// we don't want to report healthy until we can handle all CRDs that have already been registered.  Waiting for the informer
	// to sync makes sure that the lister will be valid before we begin.  There may still be races for CRDs added after startup,
	// but we won't go healthy until we can handle the ones already present.
	s.GenericAPIServer.AddPostStartHookOrDie("crd-informer-synced", func(context genericapiserver.PostStartHookContext) error {
		return wait.PollImmediateUntil(100*time.Millisecond, func() (bool, error) {
			return s.Informers.Apiextensions().V1().CustomResourceDefinitions().Informer().HasSynced(), nil
		}, context.StopCh)
	})

	return s, nil
}

func DefaultAPIResourceConfigSource() *serverstorage.ResourceConfig {
	ret := serverstorage.NewResourceConfig()
	// NOTE: GroupVersions listed here will be enabled by default. Don't put alpha versions in the list.
	ret.EnableVersions(
		v1beta1.SchemeGroupVersion,
		v1.SchemeGroupVersion,
	)

	return ret
}
<commit_msg>Drop beta REST APIs removed in 1.22<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 apiserver

import (
	"fmt"
	"net/http"
	"time"

	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install"
	v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
	"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
	externalinformers "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions"
	"k8s.io/apiextensions-apiserver/pkg/controller/apiapproval"
	"k8s.io/apiextensions-apiserver/pkg/controller/establish"
	"k8s.io/apiextensions-apiserver/pkg/controller/finalizer"
	"k8s.io/apiextensions-apiserver/pkg/controller/nonstructuralschema"
	openapicontroller "k8s.io/apiextensions-apiserver/pkg/controller/openapi"
	"k8s.io/apiextensions-apiserver/pkg/controller/status"
	"k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/runtime/serializer"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/apimachinery/pkg/version"
	"k8s.io/apiserver/pkg/endpoints/discovery"
	genericregistry "k8s.io/apiserver/pkg/registry/generic"
	"k8s.io/apiserver/pkg/registry/rest"
	genericapiserver "k8s.io/apiserver/pkg/server"
	serverstorage "k8s.io/apiserver/pkg/server/storage"
	"k8s.io/apiserver/pkg/util/webhook"
)

var (
	Scheme = runtime.NewScheme()
	Codecs = serializer.NewCodecFactory(Scheme)

	// if you modify this, make sure you update the crEncoder
	unversionedVersion = schema.GroupVersion{Group: "", Version: "v1"}
	unversionedTypes   = []runtime.Object{
		&metav1.Status{},
		&metav1.WatchEvent{},
		&metav1.APIVersions{},
		&metav1.APIGroupList{},
		&metav1.APIGroup{},
		&metav1.APIResourceList{},
	}
)

func init() {
	install.Install(Scheme)

	// we need to add the options to empty v1
	metav1.AddToGroupVersion(Scheme, schema.GroupVersion{Group: "", Version: "v1"})

	Scheme.AddUnversionedTypes(unversionedVersion, unversionedTypes...)
}

type ExtraConfig struct {
	CRDRESTOptionsGetter genericregistry.RESTOptionsGetter

	// MasterCount is used to detect whether cluster is HA, and if it is
	// the CRD Establishing will be hold by 5 seconds.
	MasterCount int

	// ServiceResolver is used in CR webhook converters to resolve webhook's service names
	ServiceResolver webhook.ServiceResolver
	// AuthResolverWrapper is used in CR webhook converters
	AuthResolverWrapper webhook.AuthenticationInfoResolverWrapper
}

type Config struct {
	GenericConfig *genericapiserver.RecommendedConfig
	ExtraConfig   ExtraConfig
}

type completedConfig struct {
	GenericConfig genericapiserver.CompletedConfig
	ExtraConfig   *ExtraConfig
}

type CompletedConfig struct {
	// Embed a private pointer that cannot be instantiated outside of this package.
	*completedConfig
}

type CustomResourceDefinitions struct {
	GenericAPIServer *genericapiserver.GenericAPIServer

	// provided for easier embedding
	Informers externalinformers.SharedInformerFactory
}

// Complete fills in any fields not set that are required to have valid data. It's mutating the receiver.
func (cfg *Config) Complete() CompletedConfig {
	c := completedConfig{
		cfg.GenericConfig.Complete(),
		&cfg.ExtraConfig,
	}

	c.GenericConfig.EnableDiscovery = false
	if c.GenericConfig.Version == nil {
		c.GenericConfig.Version = &version.Info{
			Major: "0",
			Minor: "1",
		}
	}

	return CompletedConfig{&c}
}

// New returns a new instance of CustomResourceDefinitions from the given config.
func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget) (*CustomResourceDefinitions, error) {
	genericServer, err := c.GenericConfig.New("apiextensions-apiserver", delegationTarget)
	if err != nil {
		return nil, err
	}

	s := &CustomResourceDefinitions{
		GenericAPIServer: genericServer,
	}

	apiResourceConfig := c.GenericConfig.MergedResourceConfig
	apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(apiextensions.GroupName, Scheme, metav1.ParameterCodec, Codecs)
	if apiResourceConfig.VersionEnabled(v1.SchemeGroupVersion) {
		storage := map[string]rest.Storage{}
		// customresourcedefinitions
		customResourceDefinitionStorage, err := customresourcedefinition.NewREST(Scheme, c.GenericConfig.RESTOptionsGetter)
		if err != nil {
			return nil, err
		}
		storage["customresourcedefinitions"] = customResourceDefinitionStorage
		storage["customresourcedefinitions/status"] = customresourcedefinition.NewStatusREST(Scheme, customResourceDefinitionStorage)

		apiGroupInfo.VersionedResourcesStorageMap[v1.SchemeGroupVersion.Version] = storage
	}

	if err := s.GenericAPIServer.InstallAPIGroup(&apiGroupInfo); err != nil {
		return nil, err
	}

	crdClient, err := clientset.NewForConfig(s.GenericAPIServer.LoopbackClientConfig)
	if err != nil {
		// it's really bad that this is leaking here, but until we can fix the test (which I'm pretty sure isn't even testing what it wants to test),
		// we need to be able to move forward
		return nil, fmt.Errorf("failed to create clientset: %v", err)
	}
	s.Informers = externalinformers.NewSharedInformerFactory(crdClient, 5*time.Minute)

	delegateHandler := delegationTarget.UnprotectedHandler()
	if delegateHandler == nil {
		delegateHandler = http.NotFoundHandler()
	}

	versionDiscoveryHandler := &versionDiscoveryHandler{
		discovery: map[schema.GroupVersion]*discovery.APIVersionHandler{},
		delegate:  delegateHandler,
	}
	groupDiscoveryHandler := &groupDiscoveryHandler{
		discovery: map[string]*discovery.APIGroupHandler{},
		delegate:  delegateHandler,
	}
	establishingController := establish.NewEstablishingController(s.Informers.Apiextensions().V1().CustomResourceDefinitions(), crdClient.ApiextensionsV1())
	crdHandler, err := NewCustomResourceDefinitionHandler(
		versionDiscoveryHandler,
		groupDiscoveryHandler,
		s.Informers.Apiextensions().V1().CustomResourceDefinitions(),
		delegateHandler,
		c.ExtraConfig.CRDRESTOptionsGetter,
		c.GenericConfig.AdmissionControl,
		establishingController,
		c.ExtraConfig.ServiceResolver,
		c.ExtraConfig.AuthResolverWrapper,
		c.ExtraConfig.MasterCount,
		s.GenericAPIServer.Authorizer,
		c.GenericConfig.RequestTimeout,
		time.Duration(c.GenericConfig.MinRequestTimeout)*time.Second,
		apiGroupInfo.StaticOpenAPISpec,
		c.GenericConfig.MaxRequestBodyBytes,
	)
	if err != nil {
		return nil, err
	}
	s.GenericAPIServer.Handler.NonGoRestfulMux.Handle("/apis", crdHandler)
	s.GenericAPIServer.Handler.NonGoRestfulMux.HandlePrefix("/apis/", crdHandler)

	discoveryController := NewDiscoveryController(s.Informers.Apiextensions().V1().CustomResourceDefinitions(), versionDiscoveryHandler, groupDiscoveryHandler)
	namingController := status.NewNamingConditionController(s.Informers.Apiextensions().V1().CustomResourceDefinitions(), crdClient.ApiextensionsV1())
	nonStructuralSchemaController := nonstructuralschema.NewConditionController(s.Informers.Apiextensions().V1().CustomResourceDefinitions(), crdClient.ApiextensionsV1())
	apiApprovalController := apiapproval.NewKubernetesAPIApprovalPolicyConformantConditionController(s.Informers.Apiextensions().V1().CustomResourceDefinitions(), crdClient.ApiextensionsV1())
	finalizingController := finalizer.NewCRDFinalizer(
		s.Informers.Apiextensions().V1().CustomResourceDefinitions(),
		crdClient.ApiextensionsV1(),
		crdHandler,
	)
	openapiController := openapicontroller.NewController(s.Informers.Apiextensions().V1().CustomResourceDefinitions())

	s.GenericAPIServer.AddPostStartHookOrDie("start-apiextensions-informers", func(context genericapiserver.PostStartHookContext) error {
		s.Informers.Start(context.StopCh)
		return nil
	})
	s.GenericAPIServer.AddPostStartHookOrDie("start-apiextensions-controllers", func(context genericapiserver.PostStartHookContext) error {
		// OpenAPIVersionedService and StaticOpenAPISpec are populated in generic apiserver PrepareRun().
		// Together they serve the /openapi/v2 endpoint on a generic apiserver. A generic apiserver may
		// choose to not enable OpenAPI by having null openAPIConfig, and thus OpenAPIVersionedService
		// and StaticOpenAPISpec are both null. In that case we don't run the CRD OpenAPI controller.
		if s.GenericAPIServer.OpenAPIVersionedService != nil && s.GenericAPIServer.StaticOpenAPISpec != nil {
			go openapiController.Run(s.GenericAPIServer.StaticOpenAPISpec, s.GenericAPIServer.OpenAPIVersionedService, context.StopCh)
		}

		go namingController.Run(context.StopCh)
		go establishingController.Run(context.StopCh)
		go nonStructuralSchemaController.Run(5, context.StopCh)
		go apiApprovalController.Run(5, context.StopCh)
		go finalizingController.Run(5, context.StopCh)

		discoverySyncedCh := make(chan struct{})
		go discoveryController.Run(context.StopCh, discoverySyncedCh)
		select {
		case <-context.StopCh:
		case <-discoverySyncedCh:
		}

		return nil
	})
	// we don't want to report healthy until we can handle all CRDs that have already been registered.  Waiting for the informer
	// to sync makes sure that the lister will be valid before we begin.  There may still be races for CRDs added after startup,
	// but we won't go healthy until we can handle the ones already present.
	s.GenericAPIServer.AddPostStartHookOrDie("crd-informer-synced", func(context genericapiserver.PostStartHookContext) error {
		return wait.PollImmediateUntil(100*time.Millisecond, func() (bool, error) {
			return s.Informers.Apiextensions().V1().CustomResourceDefinitions().Informer().HasSynced(), nil
		}, context.StopCh)
	})

	return s, nil
}

func DefaultAPIResourceConfigSource() *serverstorage.ResourceConfig {
	ret := serverstorage.NewResourceConfig()
	// NOTE: GroupVersions listed here will be enabled by default. Don't put alpha versions in the list.
	ret.EnableVersions(
		v1beta1.SchemeGroupVersion,
		v1.SchemeGroupVersion,
	)

	return ret
}
<|endoftext|>


<commit_before>package manager

import (
	"fmt"
	"reflect"

	"github.com/pkg/errors"
	"github.com/rancher/rancher/pkg/catalog/utils"
	v3 "github.com/rancher/types/apis/management.cattle.io/v3"
	"github.com/sirupsen/logrus"
	kerrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/selection"
)

const (
	CatalogNameLabel  = "catalog.cattle.io/name"
	TemplateNameLabel = "catalog.cattle.io/template_name"
)

func (m *Manager) createTemplate(template v3.CatalogTemplate, catalog *v3.Catalog) error {
	template.Labels = labels.Merge(template.Labels, map[string]string{
		CatalogNameLabel: catalog.Name,
	})
	versionFiles := make([]v3.TemplateVersionSpec, len(template.Spec.Versions))
	copy(versionFiles, template.Spec.Versions)
	for i := range template.Spec.Versions {
		template.Spec.Versions[i].Files = nil
		template.Spec.Versions[i].Readme = ""
		template.Spec.Versions[i].AppReadme = ""
	}
	logrus.Debugf("Creating template %s", template.Name)
	createdTemplate, err := m.templateClient.Create(&template)
	if err != nil {
		return errors.Wrapf(err, "failed to create template %s", template.Name)
	}
	return m.createTemplateVersions(catalog.Name, versionFiles, createdTemplate)
}

func (m *Manager) getTemplateMap(catalogName string, namespace string) (map[string]*v3.CatalogTemplate, error) {
	r, _ := labels.NewRequirement(CatalogNameLabel, selection.Equals, []string{catalogName})
	templateList, err := m.templateLister.List(namespace, labels.NewSelector().Add(*r))
	if err != nil {
		return nil, errors.Wrapf(err, "failed to list templates for %v", catalogName)
	}
	templateMap := map[string]*v3.CatalogTemplate{}
	for _, t := range templateList {
		templateMap[t.Name] = t
	}
	return templateMap, nil
}

func (m *Manager) updateTemplate(template *v3.CatalogTemplate, toUpdate v3.CatalogTemplate) error {
	r, err := labels.NewRequirement(TemplateNameLabel, selection.Equals, []string{template.Name})
	if err != nil {
		return errors.Wrapf(err, "failed to find template version with label %v for %v", TemplateNameLabel, template.Name)
	}
	templateVersions, err := m.templateVersionLister.List(template.Namespace, labels.NewSelector().Add(*r))
	if err != nil {
		return errors.Wrapf(err, "failed to list templateVersions")
	}
	tvByVersion := map[string]*v3.CatalogTemplateVersion{}
	for _, ver := range templateVersions {
		tvByVersion[ver.Spec.Version] = ver
	}
	/*
		For each templateVersion in toUpdate, if spec doesn't match, do update
		For version that doesn't exist, create a new one
	*/
	for _, toUpdateVer := range toUpdate.Spec.Versions {
		templateVersion := &v3.CatalogTemplateVersion{}
		templateVersion.Spec = toUpdateVer
		if tv, ok := tvByVersion[toUpdateVer.Version]; ok {
			if !reflect.DeepEqual(tv.Spec, toUpdateVer) {
				logrus.Debugf("Updating templateVersion %v", tv.Name)
				newObject := tv.DeepCopy()
				newObject.Spec = templateVersion.Spec
				if _, err := m.templateVersionClient.Update(newObject); err != nil {
					return err
				}
			}
		} else {
			toCreate := &v3.CatalogTemplateVersion{}
			toCreate.Name = fmt.Sprintf("%s-%v", template.Name, toUpdateVer.Version)
			toCreate.Namespace = template.Namespace
			toCreate.Labels = map[string]string{
				TemplateNameLabel: template.Name,
			}
			toCreate.Spec = templateVersion.Spec
			logrus.Debugf("Creating templateVersion %v", toCreate.Name)
			if _, err := m.templateVersionClient.Create(toCreate); err != nil {
				return err
			}
		}
	}

	// find existing templateVersion that is not in toUpdate.Versions
	toUpdateTvs := map[string]struct{}{}
	for _, toUpdateVer := range toUpdate.Spec.Versions {
		toUpdateTvs[toUpdateVer.Version] = struct{}{}
	}
	for v, tv := range tvByVersion {
		if _, ok := toUpdateTvs[v]; !ok {
			logrus.Infof("Deleting templateVersion %s", tv.Name)
			if err := m.templateVersionClient.DeleteNamespaced(template.Namespace, tv.Name, &metav1.DeleteOptions{}); err != nil && !kerrors.IsNotFound(err) {
				return err
			}
		}
	}

	for i := range toUpdate.Spec.Versions {
		toUpdate.Spec.Versions[i].Files = nil
		toUpdate.Spec.Versions[i].Readme = ""
		toUpdate.Spec.Versions[i].AppReadme = ""
	}
	newObj := template.DeepCopy()
	newObj.Spec = toUpdate.Spec
	newObj.Labels = mergeLabels(template.Labels, toUpdate.Labels)
	if _, err := m.templateClient.Update(newObj); err != nil {
		return err
	}
	return nil
}

// merge any label from set2 into set1 and delete label
func mergeLabels(set1, set2 map[string]string) map[string]string {
	if set1 == nil {
		set1 = map[string]string{}
	}
	for k, v := range set2 {
		set1[k] = v
	}
	for k := range set1 {
		if set2 != nil {
			if _, ok := set2[k]; !ok && k != CatalogNameLabel {
				delete(set1, k)
			}
		} else {
			if k != CatalogNameLabel {
				delete(set1, k)
			}
		}

	}
	return set1
}

func (m *Manager) getTemplateVersion(templateName string, namespace string) (map[string]struct{}, error) {
	//because templates is a cluster resource now so we set namespace to "" when listing it.
	r, err := labels.NewRequirement(TemplateNameLabel, selection.Equals, []string{templateName})
	if err != nil {
		return nil, errors.Wrapf(err, "failed to find template version with label %v for %v", TemplateNameLabel, templateName)
	}
	templateVersions, err := m.templateVersionLister.List(namespace, labels.NewSelector().Add(*r))
	if err != nil {
		return nil, errors.Wrapf(err, "failed to list template version(s) for %v: ", templateName)
	}
	tVersion := map[string]struct{}{}
	for _, ver := range templateVersions {
		tVersion[ver.Name] = struct{}{}
	}
	return tVersion, nil
}

func (m *Manager) createTemplateVersions(catalogName string, versionsSpec []v3.TemplateVersionSpec, template *v3.CatalogTemplate) error {
	for _, spec := range versionsSpec {
		templateVersion := &v3.CatalogTemplateVersion{}
		templateVersion.Spec = spec
		templateVersion.Status = v3.TemplateVersionStatus{HelmVersion: template.Status.HelmVersion}
		templateVersion.Name = getValidTemplateNameWithVersion(template.Name, spec.Version)
		templateVersion.Namespace = template.Namespace
		templateVersion.Labels = map[string]string{
			TemplateNameLabel: template.Name,
		}
		//help with garbage collection on delete
		ownerRef := []metav1.OwnerReference{{
			Name:       template.Name,
			APIVersion: "management.cattle.io/v3",
			UID:        template.UID,
			Kind:       template.Kind,
		}}
		templateVersion.OwnerReferences = ownerRef

		logrus.Debugf("Creating templateVersion %s", templateVersion.Name)
		if _, err := m.templateVersionClient.Create(templateVersion); err != nil && !kerrors.IsAlreadyExists(err) {
			return err
		}
	}
	return nil
}

func showUpgradeLinks(version, upgradeVersion string) bool {
	if !utils.VersionGreaterThan(upgradeVersion, version) {
		return false
	}
	return true
}
<commit_msg>Add helm version tocreate on update<commit_after>package manager

import (
	"fmt"
	"reflect"

	"github.com/pkg/errors"
	"github.com/rancher/rancher/pkg/catalog/utils"
	v3 "github.com/rancher/types/apis/management.cattle.io/v3"
	"github.com/sirupsen/logrus"
	kerrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/selection"
)

const (
	CatalogNameLabel  = "catalog.cattle.io/name"
	TemplateNameLabel = "catalog.cattle.io/template_name"
)

func (m *Manager) createTemplate(template v3.CatalogTemplate, catalog *v3.Catalog) error {
	template.Labels = labels.Merge(template.Labels, map[string]string{
		CatalogNameLabel: catalog.Name,
	})
	versionFiles := make([]v3.TemplateVersionSpec, len(template.Spec.Versions))
	copy(versionFiles, template.Spec.Versions)
	for i := range template.Spec.Versions {
		template.Spec.Versions[i].Files = nil
		template.Spec.Versions[i].Readme = ""
		template.Spec.Versions[i].AppReadme = ""
	}
	logrus.Debugf("Creating template %s", template.Name)
	createdTemplate, err := m.templateClient.Create(&template)
	if err != nil {
		return errors.Wrapf(err, "failed to create template %s", template.Name)
	}
	return m.createTemplateVersions(catalog.Name, versionFiles, createdTemplate)
}

func (m *Manager) getTemplateMap(catalogName string, namespace string) (map[string]*v3.CatalogTemplate, error) {
	r, _ := labels.NewRequirement(CatalogNameLabel, selection.Equals, []string{catalogName})
	templateList, err := m.templateLister.List(namespace, labels.NewSelector().Add(*r))
	if err != nil {
		return nil, errors.Wrapf(err, "failed to list templates for %v", catalogName)
	}
	templateMap := map[string]*v3.CatalogTemplate{}
	for _, t := range templateList {
		templateMap[t.Name] = t
	}
	return templateMap, nil
}

func (m *Manager) updateTemplate(template *v3.CatalogTemplate, toUpdate v3.CatalogTemplate) error {
	r, err := labels.NewRequirement(TemplateNameLabel, selection.Equals, []string{template.Name})
	if err != nil {
		return errors.Wrapf(err, "failed to find template version with label %v for %v", TemplateNameLabel, template.Name)
	}
	templateVersions, err := m.templateVersionLister.List(template.Namespace, labels.NewSelector().Add(*r))
	if err != nil {
		return errors.Wrapf(err, "failed to list templateVersions")
	}
	tvByVersion := map[string]*v3.CatalogTemplateVersion{}
	for _, ver := range templateVersions {
		tvByVersion[ver.Spec.Version] = ver
	}
	/*
		For each templateVersion in toUpdate, if spec doesn't match, do update
		For version that doesn't exist, create a new one
	*/
	for _, toUpdateVer := range toUpdate.Spec.Versions {
		templateVersion := &v3.CatalogTemplateVersion{}
		templateVersion.Spec = toUpdateVer
		if tv, ok := tvByVersion[toUpdateVer.Version]; ok {
			if !reflect.DeepEqual(tv.Spec, toUpdateVer) {
				logrus.Debugf("Updating templateVersion %v", tv.Name)
				newObject := tv.DeepCopy()
				newObject.Spec = templateVersion.Spec
				if _, err := m.templateVersionClient.Update(newObject); err != nil {
					return err
				}
			}
		} else {
			toCreate := &v3.CatalogTemplateVersion{}
			toCreate.Name = fmt.Sprintf("%s-%v", template.Name, toUpdateVer.Version)
			toCreate.Namespace = template.Namespace
			toCreate.Labels = map[string]string{
				TemplateNameLabel: template.Name,
			}
			toCreate.Spec = templateVersion.Spec
			toCreate.Status = v3.TemplateVersionStatus{HelmVersion: template.Status.HelmVersion}
			logrus.Debugf("Creating templateVersion %v", toCreate.Name)
			if _, err := m.templateVersionClient.Create(toCreate); err != nil {
				return err
			}
		}
	}

	// find existing templateVersion that is not in toUpdate.Versions
	toUpdateTvs := map[string]struct{}{}
	for _, toUpdateVer := range toUpdate.Spec.Versions {
		toUpdateTvs[toUpdateVer.Version] = struct{}{}
	}
	for v, tv := range tvByVersion {
		if _, ok := toUpdateTvs[v]; !ok {
			logrus.Infof("Deleting templateVersion %s", tv.Name)
			if err := m.templateVersionClient.DeleteNamespaced(template.Namespace, tv.Name, &metav1.DeleteOptions{}); err != nil && !kerrors.IsNotFound(err) {
				return err
			}
		}
	}

	for i := range toUpdate.Spec.Versions {
		toUpdate.Spec.Versions[i].Files = nil
		toUpdate.Spec.Versions[i].Readme = ""
		toUpdate.Spec.Versions[i].AppReadme = ""
	}
	newObj := template.DeepCopy()
	newObj.Spec = toUpdate.Spec
	newObj.Labels = mergeLabels(template.Labels, toUpdate.Labels)
	if _, err := m.templateClient.Update(newObj); err != nil {
		return err
	}
	return nil
}

// merge any label from set2 into set1 and delete label
func mergeLabels(set1, set2 map[string]string) map[string]string {
	if set1 == nil {
		set1 = map[string]string{}
	}
	for k, v := range set2 {
		set1[k] = v
	}
	for k := range set1 {
		if set2 != nil {
			if _, ok := set2[k]; !ok && k != CatalogNameLabel {
				delete(set1, k)
			}
		} else {
			if k != CatalogNameLabel {
				delete(set1, k)
			}
		}

	}
	return set1
}

func (m *Manager) getTemplateVersion(templateName string, namespace string) (map[string]struct{}, error) {
	//because templates is a cluster resource now so we set namespace to "" when listing it.
	r, err := labels.NewRequirement(TemplateNameLabel, selection.Equals, []string{templateName})
	if err != nil {
		return nil, errors.Wrapf(err, "failed to find template version with label %v for %v", TemplateNameLabel, templateName)
	}
	templateVersions, err := m.templateVersionLister.List(namespace, labels.NewSelector().Add(*r))
	if err != nil {
		return nil, errors.Wrapf(err, "failed to list template version(s) for %v: ", templateName)
	}
	tVersion := map[string]struct{}{}
	for _, ver := range templateVersions {
		tVersion[ver.Name] = struct{}{}
	}
	return tVersion, nil
}

func (m *Manager) createTemplateVersions(catalogName string, versionsSpec []v3.TemplateVersionSpec, template *v3.CatalogTemplate) error {
	for _, spec := range versionsSpec {
		templateVersion := &v3.CatalogTemplateVersion{}
		templateVersion.Spec = spec
		templateVersion.Status = v3.TemplateVersionStatus{HelmVersion: template.Status.HelmVersion}
		templateVersion.Name = getValidTemplateNameWithVersion(template.Name, spec.Version)
		templateVersion.Namespace = template.Namespace
		templateVersion.Labels = map[string]string{
			TemplateNameLabel: template.Name,
		}
		//help with garbage collection on delete
		ownerRef := []metav1.OwnerReference{{
			Name:       template.Name,
			APIVersion: "management.cattle.io/v3",
			UID:        template.UID,
			Kind:       template.Kind,
		}}
		templateVersion.OwnerReferences = ownerRef

		logrus.Debugf("Creating templateVersion %s", templateVersion.Name)
		if _, err := m.templateVersionClient.Create(templateVersion); err != nil && !kerrors.IsAlreadyExists(err) {
			return err
		}
	}
	return nil
}

func showUpgradeLinks(version, upgradeVersion string) bool {
	if !utils.VersionGreaterThan(upgradeVersion, version) {
		return false
	}
	return true
}
<|endoftext|>


<commit_before>// Package connector implements the connectors handling third-party data sources.
package connector

import (
	"time"

	"github.com/facette/facette/pkg/types"
)

const (
	_ = iota
	// OperGroupTypeNone represents a null operation group mode.
	OperGroupTypeNone
	// OperGroupTypeAvg represents a AVG operation group mode.
	OperGroupTypeAvg
	// OperGroupTypeSum represents a SUM operation group mode.
	OperGroupTypeSum
)

var (
	// Connectors represents the list of all available connector handlers.
	Connectors = make(map[string]func(*chan [2]string, map[string]string) (interface{}, error))
)

// Connector represents the main interface of a connector handler.
type Connector interface {
	GetPlots(query *GroupQuery, startTime, endTime time.Time, step time.Duration,
		percentiles []float64) (map[string]*PlotResult, error)
	Refresh(chan error)
}

// MetricQuery represents a metric entry in a SerieQuery.
type MetricQuery struct {
	Name       string
	SourceName string
}

// SerieQuery represents a serie entry in a GroupQuery.
type SerieQuery struct {
	Name   string
	Metric *MetricQuery
	Scale  float64
}

// GroupQuery represents a plot group query.
type GroupQuery struct {
	Name   string
	Type   int
	Series []*SerieQuery
	Scale  float64
}

// PlotResult represents a plot request result.
type PlotResult struct {
	Plots []types.PlotValue
	Info  map[string]types.PlotValue
}
<commit_msg>Coding style<commit_after>// Package connector implements the connectors handling third-party data sources.
package connector

import (
	"time"

	"github.com/facette/facette/pkg/types"
)

// Connector represents the main interface of a connector handler.
type Connector interface {
	GetPlots(query *GroupQuery, startTime, endTime time.Time, step time.Duration,
		percentiles []float64) (map[string]*PlotResult, error)
	Refresh(chan error)
}

// MetricQuery represents a metric entry in a SerieQuery.
type MetricQuery struct {
	Name       string
	SourceName string
}

// SerieQuery represents a serie entry in a GroupQuery.
type SerieQuery struct {
	Name   string
	Metric *MetricQuery
	Scale  float64
}

// GroupQuery represents a plot group query.
type GroupQuery struct {
	Name   string
	Type   int
	Series []*SerieQuery
	Scale  float64
}

// PlotResult represents a plot request result.
type PlotResult struct {
	Plots []types.PlotValue
	Info  map[string]types.PlotValue
}

const (
	_ = iota
	// OperGroupTypeNone represents a null operation group mode.
	OperGroupTypeNone
	// OperGroupTypeAvg represents a AVG operation group mode.
	OperGroupTypeAvg
	// OperGroupTypeSum represents a SUM operation group mode.
	OperGroupTypeSum
)

var (
	// Connectors represents the list of all available connector handlers.
	Connectors = make(map[string]func(*chan [2]string, map[string]string) (interface{}, error))
)
<|endoftext|>


<commit_before>// Poseidon
// Copyright (c) The Poseidon Authors.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT
// LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR
// A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
//
// See the Apache Version 2.0 License for specific language governing
// permissions and limitations under the License.

package k8sclient

import (
	"fmt"

	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/pkg/api"
	"k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/pkg/fields"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/tools/clientcmd"
)

type Node struct {
	ID string
}

type Pod struct {
	ID string
}

func StartNodeWatcher(clientset *kubernetes.Clientset) chan *Node {
	nodeCh := make(chan *Node, 100)
	nodeListWatcher := cache.NewListWatchFromClient(clientset.Core().RESTClient(), "nodes", api.NamespaceDefault, fields.Everything())
	_, nodeInformer := cache.NewInformer(
		nodeListWatcher,
		&v1.Node{},
		0,
		cache.ResourceEventHandlerFuncs{
			AddFunc: func(nodeObj interface{}) {
				node := nodeObj.(*v1.Node)
				if node.Spec.Unschedulable {
					return
				}
				nodeCh <- &Node{
					ID: node.Name,
				}
			},
			UpdateFunc: func(oldNodeObj, newNodeObj interface{}) {},
			DeleteFunc: func(nodeObj interface{}) {},
		},
	)
	stopCh := make(chan struct{})
	go nodeInformer.Run(stopCh)
	return nodeCh
}

func StartPodWatcher(clientset *kubernetes.Clientset) chan *Pod {
	podCh := make(chan *Pod, 100)
	podListWatcher := cache.NewListWatchFromClient(clientset.Core().RESTClient(), "pods", api.NamespaceDefault, fields.Everything())
	_, podInformer := cache.NewInformer(
		podListWatcher,
		&v1.Pod{},
		0,
		cache.ResourceEventHandlerFuncs{
			AddFunc: func(podObj interface{}) {
				pod := podObj.(*v1.Pod)
				podCh <- &Pod{
					ID: pod.Name,
				}
			},
			UpdateFunc: func(oldPodObj, newPodObj interface{}) {},
			DeleteFunc: func(podObj interface{}) {},
		},
	)
	stopCh := make(chan struct{})
	go podInformer.Run(stopCh)
	return podCh
}

func New(kubeConfig string) (int, int) {
	config, err := clientcmd.BuildConfigFromFlags("", kubeConfig)
	if err != nil {
		panic(err.Error())
	}
	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		panic(err.Error())
	}
	nodeCh := StartNodeWatcher(clientset)
	podCh := StartPodWatcher(clientset)
	for {
		select {
		case <-nodeCh:
			fmt.Println("New node")
		case <-podCh:
			fmt.Println("New pod")
		}
	}

	return 1, 1
}
<commit_msg>Working node & pod watches.<commit_after>// Poseidon
// Copyright (c) The Poseidon Authors.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT
// LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR
// A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
//
// See the Apache Version 2.0 License for specific language governing
// permissions and limitations under the License.

package k8sclient

import (
	"fmt"

	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/pkg/fields"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/tools/clientcmd"
)

const nodeBufferSize = 1000
const podBufferSize = 1000

type Node struct {
	ID string
}

type Pod struct {
	ID string
}

func StartNodeWatcher(clientset *kubernetes.Clientset) chan *Node {
	nodeCh := make(chan *Node, nodeBufferSize)
	nodeListWatcher := cache.NewListWatchFromClient(clientset.Core().RESTClient(), "nodes", v1.NamespaceAll, fields.Everything())
	_, nodeInformer := cache.NewInformer(
		nodeListWatcher,
		&v1.Node{},
		0,
		cache.ResourceEventHandlerFuncs{
			AddFunc: func(nodeObj interface{}) {
				node := nodeObj.(*v1.Node)
				if node.Spec.Unschedulable {
					return
				}
				nodeCh <- &Node{
					ID: node.Name,
				}
			},
			UpdateFunc: func(oldNodeObj, newNodeObj interface{}) {},
			DeleteFunc: func(nodeObj interface{}) {},
		},
	)
	stopCh := make(chan struct{})
	go nodeInformer.Run(stopCh)
	return nodeCh
}

func StartPodWatcher(clientset *kubernetes.Clientset) chan *Pod {
	podCh := make(chan *Pod, podBufferSize)
	podListWatcher := cache.NewListWatchFromClient(clientset.Core().RESTClient(), "pods", v1.NamespaceDefault, fields.Everything())
	_, podInformer := cache.NewInformer(
		podListWatcher,
		&v1.Pod{},
		0,
		cache.ResourceEventHandlerFuncs{
			AddFunc: func(podObj interface{}) {
				pod := podObj.(*v1.Pod)
				podCh <- &Pod{
					ID: pod.Name,
				}
			},
			UpdateFunc: func(oldPodObj, newPodObj interface{}) {},
			DeleteFunc: func(podObj interface{}) {},
		},
	)
	stopCh := make(chan struct{})
	go podInformer.Run(stopCh)
	return podCh
}

func New(kubeConfig string) (int, int) {
	config, err := clientcmd.BuildConfigFromFlags("", kubeConfig)
	if err != nil {
		panic(err.Error())
	}
	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		panic(err.Error())
	}
	nodeCh := StartNodeWatcher(clientset)
	podCh := StartPodWatcher(clientset)
	for {
		select {
		case <-nodeCh:
			fmt.Println("New node")
		case <-podCh:
			fmt.Println("New pod")
		}
	}

	return 1, 1
}
<|endoftext|>