<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.
*/

// This file should be consistent with pkg/api/annotation_key_constants.go.

package v1

const (
	// ImagePolicyFailedOpenKey is added to pods created by failing open when the image policy
	// webhook backend fails.
	ImagePolicyFailedOpenKey string = "alpha.image-policy.k8s.io/failed-open"

	// PodPresetOptOutAnnotationKey represents the annotation key for a pod to exempt itself from pod preset manipulation
	PodPresetOptOutAnnotationKey string = "podpreset.admission.kubernetes.io/exclude"

	// MirrorAnnotationKey represents the annotation key set by kubelets when creating mirror pods
	MirrorPodAnnotationKey string = "kubernetes.io/config.mirror"

	// TolerationsAnnotationKey represents the key of tolerations data (json serialized)
	// in the Annotations of a Pod.
	TolerationsAnnotationKey string = "scheduler.alpha.kubernetes.io/tolerations"

	// TaintsAnnotationKey represents the key of taints data (json serialized)
	// in the Annotations of a Node.
	TaintsAnnotationKey string = "scheduler.alpha.kubernetes.io/taints"

	// SeccompPodAnnotationKey represents the key of a seccomp profile applied
	// to all containers of a pod.
	SeccompPodAnnotationKey string = "seccomp.security.alpha.kubernetes.io/pod"

	// SeccompContainerAnnotationKeyPrefix represents the key of a seccomp profile applied
	// to one container of a pod.
	SeccompContainerAnnotationKeyPrefix string = "container.seccomp.security.alpha.kubernetes.io/"

	// CreatedByAnnotation represents the key used to store the spec(json)
	// used to create the resource.
	CreatedByAnnotation = "kubernetes.io/created-by"

	// PreferAvoidPodsAnnotationKey represents the key of preferAvoidPods data (json serialized)
	// in the Annotations of a Node.
	PreferAvoidPodsAnnotationKey string = "scheduler.alpha.kubernetes.io/preferAvoidPods"

	// SysctlsPodAnnotationKey represents the key of sysctls which are set for the infrastructure
	// container of a pod. The annotation value is a comma separated list of sysctl_name=value
	// key-value pairs. Only a limited set of whitelisted and isolated sysctls is supported by
	// the kubelet. Pods with other sysctls will fail to launch.
	SysctlsPodAnnotationKey string = "security.alpha.kubernetes.io/sysctls"

	// UnsafeSysctlsPodAnnotationKey represents the key of sysctls which are set for the infrastructure
	// container of a pod. The annotation value is a comma separated list of sysctl_name=value
	// key-value pairs. Unsafe sysctls must be explicitly enabled for a kubelet. They are properly
	// namespaced to a pod or a container, but their isolation is usually unclear or weak. Their use
	// is at-your-own-risk. Pods that attempt to set an unsafe sysctl that is not enabled for a kubelet
	// will fail to launch.
	UnsafeSysctlsPodAnnotationKey string = "security.alpha.kubernetes.io/unsafe-sysctls"

	// ObjectTTLAnnotations represents a suggestion for kubelet for how long it can cache
	// an object (e.g. secret, config map) before fetching it again from apiserver.
	// This annotation can be attached to node.
	ObjectTTLAnnotationKey string = "node.alpha.kubernetes.io/ttl"

	// annotation key prefix used to identify non-convertible json paths.
	NonConvertibleAnnotationPrefix = "non-convertible.kubernetes.io"

	kubectlPrefix = "kubectl.kubernetes.io/"

	// LastAppliedConfigAnnotation is the annotation used to store the previous
	// configuration of a resource for use in a three way diff by UpdateApplyAnnotation.
	LastAppliedConfigAnnotation = kubectlPrefix + "last-applied-configuration"

	// AnnotationLoadBalancerSourceRangesKey is the key of the annotation on a service to set allowed ingress ranges on their LoadBalancers
	//
	// It should be a comma-separated list of CIDRs, e.g. `0.0.0.0/0` to
	// allow full access (the default) or `18.0.0.0/8,56.0.0.0/8` to allow
	// access only from the CIDRs currently allocated to MIT & the USPS.
	//
	// Not all cloud providers support this annotation, though AWS & GCE do.
	AnnotationLoadBalancerSourceRangesKey = "service.beta.kubernetes.io/load-balancer-source-ranges"
)
<commit_msg>mark created-by annotation as deprecated<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.
*/

// This file should be consistent with pkg/api/annotation_key_constants.go.

package v1

const (
	// ImagePolicyFailedOpenKey is added to pods created by failing open when the image policy
	// webhook backend fails.
	ImagePolicyFailedOpenKey string = "alpha.image-policy.k8s.io/failed-open"

	// PodPresetOptOutAnnotationKey represents the annotation key for a pod to exempt itself from pod preset manipulation
	PodPresetOptOutAnnotationKey string = "podpreset.admission.kubernetes.io/exclude"

	// MirrorAnnotationKey represents the annotation key set by kubelets when creating mirror pods
	MirrorPodAnnotationKey string = "kubernetes.io/config.mirror"

	// TolerationsAnnotationKey represents the key of tolerations data (json serialized)
	// in the Annotations of a Pod.
	TolerationsAnnotationKey string = "scheduler.alpha.kubernetes.io/tolerations"

	// TaintsAnnotationKey represents the key of taints data (json serialized)
	// in the Annotations of a Node.
	TaintsAnnotationKey string = "scheduler.alpha.kubernetes.io/taints"

	// SeccompPodAnnotationKey represents the key of a seccomp profile applied
	// to all containers of a pod.
	SeccompPodAnnotationKey string = "seccomp.security.alpha.kubernetes.io/pod"

	// SeccompContainerAnnotationKeyPrefix represents the key of a seccomp profile applied
	// to one container of a pod.
	SeccompContainerAnnotationKeyPrefix string = "container.seccomp.security.alpha.kubernetes.io/"

	// CreatedByAnnotation represents the key used to store the spec(json)
	// used to create the resource.
	// This field is deprecated in favor of ControllerRef (see #44407).
	// TODO(#50720): Remove this field in v1.9.
	CreatedByAnnotation = "kubernetes.io/created-by"

	// PreferAvoidPodsAnnotationKey represents the key of preferAvoidPods data (json serialized)
	// in the Annotations of a Node.
	PreferAvoidPodsAnnotationKey string = "scheduler.alpha.kubernetes.io/preferAvoidPods"

	// SysctlsPodAnnotationKey represents the key of sysctls which are set for the infrastructure
	// container of a pod. The annotation value is a comma separated list of sysctl_name=value
	// key-value pairs. Only a limited set of whitelisted and isolated sysctls is supported by
	// the kubelet. Pods with other sysctls will fail to launch.
	SysctlsPodAnnotationKey string = "security.alpha.kubernetes.io/sysctls"

	// UnsafeSysctlsPodAnnotationKey represents the key of sysctls which are set for the infrastructure
	// container of a pod. The annotation value is a comma separated list of sysctl_name=value
	// key-value pairs. Unsafe sysctls must be explicitly enabled for a kubelet. They are properly
	// namespaced to a pod or a container, but their isolation is usually unclear or weak. Their use
	// is at-your-own-risk. Pods that attempt to set an unsafe sysctl that is not enabled for a kubelet
	// will fail to launch.
	UnsafeSysctlsPodAnnotationKey string = "security.alpha.kubernetes.io/unsafe-sysctls"

	// ObjectTTLAnnotations represents a suggestion for kubelet for how long it can cache
	// an object (e.g. secret, config map) before fetching it again from apiserver.
	// This annotation can be attached to node.
	ObjectTTLAnnotationKey string = "node.alpha.kubernetes.io/ttl"

	// annotation key prefix used to identify non-convertible json paths.
	NonConvertibleAnnotationPrefix = "non-convertible.kubernetes.io"

	kubectlPrefix = "kubectl.kubernetes.io/"

	// LastAppliedConfigAnnotation is the annotation used to store the previous
	// configuration of a resource for use in a three way diff by UpdateApplyAnnotation.
	LastAppliedConfigAnnotation = kubectlPrefix + "last-applied-configuration"

	// AnnotationLoadBalancerSourceRangesKey is the key of the annotation on a service to set allowed ingress ranges on their LoadBalancers
	//
	// It should be a comma-separated list of CIDRs, e.g. `0.0.0.0/0` to
	// allow full access (the default) or `18.0.0.0/8,56.0.0.0/8` to allow
	// access only from the CIDRs currently allocated to MIT & the USPS.
	//
	// Not all cloud providers support this annotation, though AWS & GCE do.
	AnnotationLoadBalancerSourceRangesKey = "service.beta.kubernetes.io/load-balancer-source-ranges"
)
<|endoftext|>


<commit_before>package storage

import (
	"bufio"
	"encoding/json"
	"errors"
	"io"
	"path/filepath"
	"unicode/utf8"
)

// ErrDuplicatePath occurs when a tar archive has more than one entry for the
// same file path
var ErrDuplicatePath = errors.New("duplicates of file paths not supported")

// Packer describes the methods to pack Entries to a storage destination
type Packer interface {
	// AddEntry packs the Entry and returns its position
	AddEntry(e Entry) (int, error)
}

// Unpacker describes the methods to read Entries from a source
type Unpacker interface {
	// Next returns the next Entry being unpacked, or error, until io.EOF
	Next() (*Entry, error)
}

/* TODO(vbatts) figure out a good model for this
type PackUnpacker interface {
	Packer
	Unpacker
}
*/

type jsonUnpacker struct {
	r     io.Reader
	b     *bufio.Reader
	isEOF bool
	seen  seenNames
}

func (jup *jsonUnpacker) Next() (*Entry, error) {
	var e Entry
	if jup.isEOF {
		// since ReadBytes() will return read bytes AND an EOF, we handle it this
		// round-a-bout way so we can Unmarshal the tail with relevant errors, but
		// still get an io.EOF when the stream is ended.
		return nil, io.EOF
	}
	line, err := jup.b.ReadBytes('\n')
	if err != nil && err != io.EOF {
		return nil, err
	} else if err == io.EOF {
		jup.isEOF = true
	}

	err = json.Unmarshal(line, &e)
	if err != nil && jup.isEOF {
		// if the remainder actually _wasn't_ a remaining json structure, then just EOF
		return nil, io.EOF
	}

	// check for dup name
	if e.Type == FileType {
		cName := filepath.Clean(e.GetName())
		if _, ok := jup.seen[cName]; ok {
			return nil, ErrDuplicatePath
		}
		jup.seen[cName] = struct{}{}
	}

	return &e, err
}

// NewJSONUnpacker provides an Unpacker that reads Entries (SegmentType and
// FileType) as a json document.
//
// Each Entry read are expected to be delimited by new line.
func NewJSONUnpacker(r io.Reader) Unpacker {
	return &jsonUnpacker{
		r:    r,
		b:    bufio.NewReader(r),
		seen: seenNames{},
	}
}

type jsonPacker struct {
	w    io.Writer
	e    *json.Encoder
	pos  int
	seen seenNames
}

type seenNames map[string]struct{}

func (jp *jsonPacker) AddEntry(e Entry) (int, error) {
	// if Name is not valid utf8, switch it to raw first.
	if e.Name != "" {
		if !utf8.ValidString(e.Name) {
			e.NameRaw = []byte(e.Name)
			e.Name = ""
		}
	}

	// check early for dup name
	if e.Type == FileType {
		cName := filepath.Clean(e.GetName())
		if _, ok := jp.seen[cName]; ok {
			return -1, ErrDuplicatePath
		}
		jp.seen[cName] = struct{}{}
	}

	e.Position = jp.pos
	err := jp.e.Encode(e)
	if err != nil {
		return -1, err
	}

	// made it this far, increment now
	jp.pos++
	return e.Position, nil
}

// NewJSONPacker provides a Packer that writes each Entry (SegmentType and
// FileType) as a json document.
//
// The Entries are delimited by new line.
func NewJSONPacker(w io.Writer) Packer {
	return &jsonPacker{
		w:    w,
		e:    json.NewEncoder(w),
		seen: seenNames{},
	}
}

/*
TODO(vbatts) perhaps have a more compact packer/unpacker, maybe using msgapck
(https://github.com/ugorji/go)

Even though, since our jsonUnpacker and jsonPacker just take
io.Reader/io.Writer, then we can get away with passing them a
gzip.Reader/gzip.Writer
*/
<commit_msg>Optimize JSON decoding<commit_after>package storage

import (
	"encoding/json"
	"errors"
	"io"
	"path/filepath"
	"unicode/utf8"
)

// ErrDuplicatePath occurs when a tar archive has more than one entry for the
// same file path
var ErrDuplicatePath = errors.New("duplicates of file paths not supported")

// Packer describes the methods to pack Entries to a storage destination
type Packer interface {
	// AddEntry packs the Entry and returns its position
	AddEntry(e Entry) (int, error)
}

// Unpacker describes the methods to read Entries from a source
type Unpacker interface {
	// Next returns the next Entry being unpacked, or error, until io.EOF
	Next() (*Entry, error)
}

/* TODO(vbatts) figure out a good model for this
type PackUnpacker interface {
	Packer
	Unpacker
}
*/

type jsonUnpacker struct {
	seen seenNames
	dec  *json.Decoder
}

func (jup *jsonUnpacker) Next() (*Entry, error) {
	var e Entry
	err := jup.dec.Decode(&e)
	if err != nil {
		return nil, err
	}

	// check for dup name
	if e.Type == FileType {
		cName := filepath.Clean(e.GetName())
		if _, ok := jup.seen[cName]; ok {
			return nil, ErrDuplicatePath
		}
		jup.seen[cName] = struct{}{}
	}

	return &e, err
}

// NewJSONUnpacker provides an Unpacker that reads Entries (SegmentType and
// FileType) as a json document.
//
// Each Entry read are expected to be delimited by new line.
func NewJSONUnpacker(r io.Reader) Unpacker {
	return &jsonUnpacker{
		dec:  json.NewDecoder(r),
		seen: seenNames{},
	}
}

type jsonPacker struct {
	w    io.Writer
	e    *json.Encoder
	pos  int
	seen seenNames
}

type seenNames map[string]struct{}

func (jp *jsonPacker) AddEntry(e Entry) (int, error) {
	// if Name is not valid utf8, switch it to raw first.
	if e.Name != "" {
		if !utf8.ValidString(e.Name) {
			e.NameRaw = []byte(e.Name)
			e.Name = ""
		}
	}

	// check early for dup name
	if e.Type == FileType {
		cName := filepath.Clean(e.GetName())
		if _, ok := jp.seen[cName]; ok {
			return -1, ErrDuplicatePath
		}
		jp.seen[cName] = struct{}{}
	}

	e.Position = jp.pos
	err := jp.e.Encode(e)
	if err != nil {
		return -1, err
	}

	// made it this far, increment now
	jp.pos++
	return e.Position, nil
}

// NewJSONPacker provides a Packer that writes each Entry (SegmentType and
// FileType) as a json document.
//
// The Entries are delimited by new line.
func NewJSONPacker(w io.Writer) Packer {
	return &jsonPacker{
		w:    w,
		e:    json.NewEncoder(w),
		seen: seenNames{},
	}
}

/*
TODO(vbatts) perhaps have a more compact packer/unpacker, maybe using msgapck
(https://github.com/ugorji/go)

Even though, since our jsonUnpacker and jsonPacker just take
io.Reader/io.Writer, then we can get away with passing them a
gzip.Reader/gzip.Writer
*/
<|endoftext|>


<commit_before>// Package main provides a simple UBER hypermedia drive todo list server
package main

import (
	"container/list"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"regexp"
	"strconv"

	"github.com/gorilla/handlers"
	"github.com/gorilla/mux"

	"golang.org/x/net/context"
)

type ContextHandler interface {
	ServeHTTPWithContext(context.Context, http.ResponseWriter, *http.Request)
}

type ContextHandlerFunc func(context.Context, http.ResponseWriter, *http.Request)

func (h ContextHandlerFunc) ServeHTTPWithContext(ctx context.Context, w http.ResponseWriter, req *http.Request) {
	h(ctx, w, req)
}

type ContextAdapter struct {
	ctx     context.Context
	handler ContextHandler
}

func (ca ContextAdapter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	ca.handler.ServeHTTPWithContext(ca.ctx, w, req)
}

type udata struct {
	ID         string   `json:"id,omitempty"`
	Name       string   `json:"name,omitempty"`
	Rel        []string `json:"rel,omitempty"`
	Label      string   `json:"label,omitempty"`
	URL        string   `json:"url,omitempty"`
	Template   bool     `json:"template,omitempty"`
	Action     string   `json:"action,omitempty"`
	Transclude bool     `json:"transclude,omitempty"`
	Model      string   `json:"model,omitempty"`
	Sending    string   `json:"sending,omitempty"`
	Accepting  []string `json:"accepting,omitempty"`
	Value      string   `json:"value,omitempty"`
	Data       []udata  `json:"data,omitempty"`
}

func (ud *udoc) appendItem(taskid, value string) {
	task := udata{ID: taskid,
		Rel:  []string{"item"},
		Name: "tasks",
		Data: []udata{
			udata{Rel: []string{"complete"}, URL: "/tasks/complete/", Model: fmt.Sprintf("id=%s", taskid), Action: "append"},
			udata{Name: "text", Value: value}}}

	ud.Uber.Data[1].Data = append(ud.Uber.Data[1].Data, task)
}

type ubody struct {
	Version string  `json:"version"`
	Data    []udata `json:"data,omitempty"`
	Error   []udata `json:"error,omitempty"`
}

type udoc struct {
	Uber ubody `json:"uber"`
}

var (
	taskctx = context.Background()
)

func init() {
	taskctx = context.WithValue(taskctx, "tasks", list.New())
	taskctx = context.WithValue(taskctx, "logger", log.New(os.Stdout, "taskd: ", log.LstdFlags))
	http.Handle("/", handlers.CompressHandler(handlers.LoggingHandler(os.Stdout, router())))
}

func main() {
	http.ListenAndServe(":3006", nil)
}

func router() *mux.Router {
	r := mux.NewRouter()
	r.Handle("/tasks", http.Handler(ContextAdapter{ctx: taskctx, handler: ContextHandlerFunc(tasklist)})).Methods("GET")
	r.Handle("/tasks", http.Handler(ContextAdapter{ctx: taskctx, handler: ContextHandlerFunc(taskadd)})).Methods("POST")
	r.Handle("/tasks/complete", http.Handler(ContextAdapter{ctx: taskctx, handler: ContextHandlerFunc(taskcomplete)})).Methods("POST")
	r.Handle("/tasks/search", http.Handler(ContextAdapter{ctx: taskctx, handler: ContextHandlerFunc(tasksearch)})).Methods("GET")
	return r
}

func taskadd(ctx context.Context, w http.ResponseWriter, req *http.Request) {
	body, err := ioutil.ReadAll(req.Body)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write(mkError("ServerError", "reason", "Cannot read HTTP request body"))
		return
	}

	re := regexp.MustCompile("text=(([[:word:]]|[[:space:]])*)")
	sm := re.FindStringSubmatch(string(body))
	if sm == nil {
		w.WriteHeader(http.StatusBadRequest)
		w.Write(mkError("ClientError", "reason", "Unrecognized add task body"))
		return
	}

	tasks := ctx.Value("tasks").(*list.List)
	tasks.PushBack(sm[1])

	w.WriteHeader(http.StatusNoContent)
}

func taskcomplete(ctx context.Context, w http.ResponseWriter, req *http.Request) {
	body, err := ioutil.ReadAll(req.Body)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write(mkError("ServerError", "reason", "Cannot read HTTP request body"))
		return
	}

	re := regexp.MustCompile("id=[[:alpha:]]+([[:digit:]]+)")
	sm := re.FindStringSubmatch(string(body))
	if sm == nil {
		w.WriteHeader(http.StatusBadRequest)
		w.Write(mkError("ClientError", "reason", "Unrecognized complete text body"))
		return
	}

	completed := false
	taskid, err := strconv.Atoi(sm[1])
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write(mkError("ServerError", "reason", "Cannot read HTTP request body"))
		return
	}

	tasks := ctx.Value("tasks").(*list.List)

	if tasks.Len() < taskid {
		w.WriteHeader(http.StatusNotFound)
		w.Write(mkError("ClientError", "reason", "No such task"))
		return
	}

	for t, i := tasks.Front(), 1; t != nil; t = t.Next() {
		if i == taskid {
			completed = true
			tasks.Remove(t)
		}
		i++
	}

	if !completed {
		w.WriteHeader(http.StatusNotFound)
		w.Write(mkError("ClientError", "reason", "No such task"))
		return
	}

	w.WriteHeader(http.StatusNoContent)
}

func tasklist(ctx context.Context, w http.ResponseWriter, req *http.Request) {
	tasks := ctx.Value("tasks").(*list.List)

	resp := mkEmptylist()
	if resp == nil {
		panic("can't generate base UBER document")
	}

	for t, i := tasks.Front(), 0; t != nil; t = t.Next() {
		resp.appendItem(fmt.Sprintf("task%d", i+1), t.Value.(string))
		i++
	}

	bs, err := json.Marshal(resp)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write(mkError("ServerError", "reason", "Cannot read HTTP request body"))
		return
	}

	w.WriteHeader(http.StatusOK)
	w.Write(bs)
}

func tasksearch(ctx context.Context, w http.ResponseWriter, req *http.Request) {
	tasks := ctx.Value("tasks").(*list.List)

	qt := req.URL.Query().Get("text")
	if len(qt) <= 0 {
		w.WriteHeader(http.StatusBadRequest)
		w.Write(mkError("ClientError", "reason", "Missing text parameter"))
		return
	}

	resp := mkEmptylist()
	if resp == nil {
		panic("can't generate base UBER document")
	}

	for t, i := tasks.Front(), 0; t != nil; t = t.Next() {
		if qt == t.Value.(string) {
			resp.appendItem(fmt.Sprintf("task%d", i+1), t.Value.(string))
			i++
		}
	}

	bs, err := json.Marshal(resp)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write(mkError("ServerError", "reason", "Cannot read HTTP request body"))
	}

	w.WriteHeader(http.StatusOK)
	w.Write(bs)
}

func mkEmptylist() *udoc {
	links := udata{
		ID: "links",
		Data: []udata{
			udata{ID: "alps",
				Rel:    []string{"profile"},
				URL:    "/tasks-alps.xml",
				Action: "read",
				Data:   []udata{}},
			udata{ID: "list",
				Name:   "links",
				Rel:    []string{"collection"},
				URL:    "/tasks/",
				Action: "read",
				Data:   []udata{}},
			udata{ID: "search",
				Name:   "links",
				Rel:    []string{"search"},
				URL:    "/tasks/search",
				Action: "read",
				Model:  "?text={text}",
				Data:   []udata{}},
			udata{ID: "add",
				Name:   "links",
				Rel:    []string{"add"},
				URL:    "/tasks/",
				Action: "append",
				Model:  "text={text}",
				Data:   []udata{}}}}

	return &udoc{ubody{Version: "1.0", Data: []udata{links, udata{ID: "tasks", Data: []udata{}}}, Error: []udata{}}}
}

func mkError(name, rel, value string) []byte {
	bs, err := json.Marshal(udoc{ubody{Version: "1.0", Error: []udata{udata{Name: name, Rel: []string{rel}, Value: value}}}})
	if err != nil {
		panic(err)
	}
	return bs
}
<commit_msg>Move appendItem<commit_after>// Package main provides a simple UBER hypermedia drive todo list server
package main

import (
	"container/list"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"regexp"
	"strconv"

	"github.com/gorilla/handlers"
	"github.com/gorilla/mux"

	"golang.org/x/net/context"
)

type ContextHandler interface {
	ServeHTTPWithContext(context.Context, http.ResponseWriter, *http.Request)
}

type ContextHandlerFunc func(context.Context, http.ResponseWriter, *http.Request)

func (h ContextHandlerFunc) ServeHTTPWithContext(ctx context.Context, w http.ResponseWriter, req *http.Request) {
	h(ctx, w, req)
}

type ContextAdapter struct {
	ctx     context.Context
	handler ContextHandler
}

func (ca ContextAdapter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	ca.handler.ServeHTTPWithContext(ca.ctx, w, req)
}

type udata struct {
	ID         string   `json:"id,omitempty"`
	Name       string   `json:"name,omitempty"`
	Rel        []string `json:"rel,omitempty"`
	Label      string   `json:"label,omitempty"`
	URL        string   `json:"url,omitempty"`
	Template   bool     `json:"template,omitempty"`
	Action     string   `json:"action,omitempty"`
	Transclude bool     `json:"transclude,omitempty"`
	Model      string   `json:"model,omitempty"`
	Sending    string   `json:"sending,omitempty"`
	Accepting  []string `json:"accepting,omitempty"`
	Value      string   `json:"value,omitempty"`
	Data       []udata  `json:"data,omitempty"`
}

type ubody struct {
	Version string  `json:"version"`
	Data    []udata `json:"data,omitempty"`
	Error   []udata `json:"error,omitempty"`
}

type udoc struct {
	Uber ubody `json:"uber"`
}

func (ud *udoc) appendItem(taskid, value string) {
	task := udata{ID: taskid,
		Rel:  []string{"item"},
		Name: "tasks",
		Data: []udata{
			udata{Rel: []string{"complete"}, URL: "/tasks/complete/", Model: fmt.Sprintf("id=%s", taskid), Action: "append"},
			udata{Name: "text", Value: value}}}

	ud.Uber.Data[1].Data = append(ud.Uber.Data[1].Data, task)
}

var (
	taskctx = context.Background()
)

func init() {
	taskctx = context.WithValue(taskctx, "tasks", list.New())
	taskctx = context.WithValue(taskctx, "logger", log.New(os.Stdout, "taskd: ", log.LstdFlags))
	http.Handle("/", handlers.CompressHandler(handlers.LoggingHandler(os.Stdout, router())))
}

func main() {
	http.ListenAndServe(":3006", nil)
}

func router() *mux.Router {
	r := mux.NewRouter()
	r.Handle("/tasks", http.Handler(ContextAdapter{ctx: taskctx, handler: ContextHandlerFunc(tasklist)})).Methods("GET")
	r.Handle("/tasks", http.Handler(ContextAdapter{ctx: taskctx, handler: ContextHandlerFunc(taskadd)})).Methods("POST")
	r.Handle("/tasks/complete", http.Handler(ContextAdapter{ctx: taskctx, handler: ContextHandlerFunc(taskcomplete)})).Methods("POST")
	r.Handle("/tasks/search", http.Handler(ContextAdapter{ctx: taskctx, handler: ContextHandlerFunc(tasksearch)})).Methods("GET")
	return r
}

func taskadd(ctx context.Context, w http.ResponseWriter, req *http.Request) {
	body, err := ioutil.ReadAll(req.Body)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write(mkError("ServerError", "reason", "Cannot read HTTP request body"))
		return
	}

	re := regexp.MustCompile("text=(([[:word:]]|[[:space:]])*)")
	sm := re.FindStringSubmatch(string(body))
	if sm == nil {
		w.WriteHeader(http.StatusBadRequest)
		w.Write(mkError("ClientError", "reason", "Unrecognized add task body"))
		return
	}

	tasks := ctx.Value("tasks").(*list.List)
	tasks.PushBack(sm[1])

	w.WriteHeader(http.StatusNoContent)
}

func taskcomplete(ctx context.Context, w http.ResponseWriter, req *http.Request) {
	body, err := ioutil.ReadAll(req.Body)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write(mkError("ServerError", "reason", "Cannot read HTTP request body"))
		return
	}

	re := regexp.MustCompile("id=[[:alpha:]]+([[:digit:]]+)")
	sm := re.FindStringSubmatch(string(body))
	if sm == nil {
		w.WriteHeader(http.StatusBadRequest)
		w.Write(mkError("ClientError", "reason", "Unrecognized complete text body"))
		return
	}

	completed := false
	taskid, err := strconv.Atoi(sm[1])
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write(mkError("ServerError", "reason", "Cannot read HTTP request body"))
		return
	}

	tasks := ctx.Value("tasks").(*list.List)

	if tasks.Len() < taskid {
		w.WriteHeader(http.StatusNotFound)
		w.Write(mkError("ClientError", "reason", "No such task"))
		return
	}

	for t, i := tasks.Front(), 1; t != nil; t = t.Next() {
		if i == taskid {
			completed = true
			tasks.Remove(t)
		}
		i++
	}

	if !completed {
		w.WriteHeader(http.StatusNotFound)
		w.Write(mkError("ClientError", "reason", "No such task"))
		return
	}

	w.WriteHeader(http.StatusNoContent)
}

func tasklist(ctx context.Context, w http.ResponseWriter, req *http.Request) {
	tasks := ctx.Value("tasks").(*list.List)

	resp := mkEmptylist()
	if resp == nil {
		panic("can't generate base UBER document")
	}

	for t, i := tasks.Front(), 0; t != nil; t = t.Next() {
		resp.appendItem(fmt.Sprintf("task%d", i+1), t.Value.(string))
		i++
	}

	bs, err := json.Marshal(resp)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write(mkError("ServerError", "reason", "Cannot read HTTP request body"))
		return
	}

	w.WriteHeader(http.StatusOK)
	w.Write(bs)
}

func tasksearch(ctx context.Context, w http.ResponseWriter, req *http.Request) {
	tasks := ctx.Value("tasks").(*list.List)

	qt := req.URL.Query().Get("text")
	if len(qt) <= 0 {
		w.WriteHeader(http.StatusBadRequest)
		w.Write(mkError("ClientError", "reason", "Missing text parameter"))
		return
	}

	resp := mkEmptylist()
	if resp == nil {
		panic("can't generate base UBER document")
	}

	for t, i := tasks.Front(), 0; t != nil; t = t.Next() {
		if qt == t.Value.(string) {
			resp.appendItem(fmt.Sprintf("task%d", i+1), t.Value.(string))
			i++
		}
	}

	bs, err := json.Marshal(resp)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write(mkError("ServerError", "reason", "Cannot read HTTP request body"))
	}

	w.WriteHeader(http.StatusOK)
	w.Write(bs)
}

func mkEmptylist() *udoc {
	links := udata{
		ID: "links",
		Data: []udata{
			udata{ID: "alps",
				Rel:    []string{"profile"},
				URL:    "/tasks-alps.xml",
				Action: "read",
				Data:   []udata{}},
			udata{ID: "list",
				Name:   "links",
				Rel:    []string{"collection"},
				URL:    "/tasks/",
				Action: "read",
				Data:   []udata{}},
			udata{ID: "search",
				Name:   "links",
				Rel:    []string{"search"},
				URL:    "/tasks/search",
				Action: "read",
				Model:  "?text={text}",
				Data:   []udata{}},
			udata{ID: "add",
				Name:   "links",
				Rel:    []string{"add"},
				URL:    "/tasks/",
				Action: "append",
				Model:  "text={text}",
				Data:   []udata{}}}}

	return &udoc{ubody{Version: "1.0", Data: []udata{links, udata{ID: "tasks", Data: []udata{}}}, Error: []udata{}}}
}

func mkError(name, rel, value string) []byte {
	bs, err := json.Marshal(udoc{ubody{Version: "1.0", Error: []udata{udata{Name: name, Rel: []string{rel}, Value: value}}}})
	if err != nil {
		panic(err)
	}
	return bs
}
<|endoftext|>


<commit_before>package lsp

import (
	"context"
	"path"

	"github.com/sourcegraph/go-lsp"

	"github.com/thought-machine/please/src/core"
	"github.com/thought-machine/please/src/parse/asp"
)

// diagSource
const diagSource = "plz tool langserver"

func (h *Handler) diagnose(d *doc) {
	last := []lsp.Diagnostic{}
	for ast := range d.Diagnostics {
		if diags := h.diagnostics(d, ast); !diagnosticsEqual(diags, last) {
			h.Conn.Notify(context.Background(), "textDocument/publishDiagnostics", &lsp.PublishDiagnosticsParams{
				URI:         lsp.DocumentURI("file://" + path.Join(h.root, d.Filename)),
				Diagnostics: diags,
			})
			last = diags
		}
	}
}

func (h *Handler) diagnostics(d *doc, ast []*asp.Statement) []lsp.Diagnostic {
	diags := []lsp.Diagnostic{}
	pkgLabel := core.BuildLabel{
		PackageName: path.Dir(d.Filename),
		Name:        "all",
	}
	asp.WalkAST(ast, func(expr *asp.Expression) bool {
		if expr.Val != nil && expr.Val.String != "" {
			if s := stringLiteral(expr.Val.String); core.LooksLikeABuildLabel(s) {
				if l, err := core.TryParseBuildLabel(s, pkgLabel.PackageName); err == nil {
					if t := h.state.Graph.Target(l); t != nil {
						if !pkgLabel.CanSee(h.state, t) {
							diags = append(diags, lsp.Diagnostic{
								Range: lsp.Range{
									// -1 because asp.Positions are 1-indexed but lsp Positions are 0-indexed.
									// Further fiddling on Column to fix quotes.
									Start: lsp.Position{Line: expr.Pos.Line - 1, Character: expr.Pos.Column},
									End:   lsp.Position{Line: expr.EndPos.Line - 1, Character: expr.EndPos.Column - 1},
								},
								Severity: lsp.Error,
								Source:   diagSource,
								Message:  "Target " + t.Label.String() + " is not visible to this package",
							})
						}
					} else if h.state.Graph.PackageByLabel(l) != nil {
						// Package exists but target doesn't, issue a diagnostic for that.
						diags = append(diags, lsp.Diagnostic{
							Range: lsp.Range{
								Start: lsp.Position{Line: expr.Pos.Line - 1, Character: expr.Pos.Column},
								End:   lsp.Position{Line: expr.EndPos.Line - 1, Character: expr.EndPos.Column - 1},
							},
							Severity: lsp.Error,
							Source:   diagSource,
							Message:  "Target " + s + " does not exist",
						})
					}
				}
			}
			return false
		}
		return true
	})
	return diags
}

func diagnosticsEqual(a, b []lsp.Diagnostic) bool {
	if len(a) != len(b) {
		return false
	}
	for i, d := range a {
		if d != b[i] {
			return false
		}
	}
	return true
}
<commit_msg>Don't report diagnostics for pseudo-labels<commit_after>package lsp

import (
	"context"
	"path"

	"github.com/sourcegraph/go-lsp"

	"github.com/thought-machine/please/src/core"
	"github.com/thought-machine/please/src/parse/asp"
)

// diagSource
const diagSource = "plz tool langserver"

func (h *Handler) diagnose(d *doc) {
	last := []lsp.Diagnostic{}
	for ast := range d.Diagnostics {
		if diags := h.diagnostics(d, ast); !diagnosticsEqual(diags, last) {
			h.Conn.Notify(context.Background(), "textDocument/publishDiagnostics", &lsp.PublishDiagnosticsParams{
				URI:         lsp.DocumentURI("file://" + path.Join(h.root, d.Filename)),
				Diagnostics: diags,
			})
			last = diags
		}
	}
}

func (h *Handler) diagnostics(d *doc, ast []*asp.Statement) []lsp.Diagnostic {
	diags := []lsp.Diagnostic{}
	pkgLabel := core.BuildLabel{
		PackageName: path.Dir(d.Filename),
		Name:        "all",
	}
	asp.WalkAST(ast, func(expr *asp.Expression) bool {
		if expr.Val != nil && expr.Val.String != "" {
			if s := stringLiteral(expr.Val.String); core.LooksLikeABuildLabel(s) {
				if l, err := core.TryParseBuildLabel(s, pkgLabel.PackageName); err == nil {
					if l.IsAllTargets() || l.IsAllSubpackages() {
						// Can't emit any useful info for these.
						// TODO(peterebden): If we know what argument we were in we could emit info
						//                   describing whether this is appropriate or not.
						return false
					} else if t := h.state.Graph.Target(l); t != nil {
						if !pkgLabel.CanSee(h.state, t) {
							diags = append(diags, lsp.Diagnostic{
								Range: lsp.Range{
									// -1 because asp.Positions are 1-indexed but lsp Positions are 0-indexed.
									// Further fiddling on Column to fix quotes.
									Start: lsp.Position{Line: expr.Pos.Line - 1, Character: expr.Pos.Column},
									End:   lsp.Position{Line: expr.EndPos.Line - 1, Character: expr.EndPos.Column - 1},
								},
								Severity: lsp.Error,
								Source:   diagSource,
								Message:  "Target " + t.Label.String() + " is not visible to this package",
							})
						}
					} else if h.state.Graph.PackageByLabel(l) != nil {
						// Package exists but target doesn't, issue a diagnostic for that.
						diags = append(diags, lsp.Diagnostic{
							Range: lsp.Range{
								Start: lsp.Position{Line: expr.Pos.Line - 1, Character: expr.Pos.Column},
								End:   lsp.Position{Line: expr.EndPos.Line - 1, Character: expr.EndPos.Column - 1},
							},
							Severity: lsp.Error,
							Source:   diagSource,
							Message:  "Target " + s + " does not exist",
						})
					}
				}
			}
			return false
		}
		return true
	})
	return diags
}

func diagnosticsEqual(a, b []lsp.Diagnostic) bool {
	if len(a) != len(b) {
		return false
	}
	for i, d := range a {
		if d != b[i] {
			return false
		}
	}
	return true
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"os"

	vlc "github.com/adrg/libvlc-go/v3"
	"github.com/gotk3/gotk3/cairo"
	"github.com/gotk3/gotk3/glib"
	"github.com/gotk3/gotk3/gtk"
)

const appId = "com.github.libvlc-go.gtk3-media-player-example"

func assertErr(err error) {
	if err != nil {
		log.Panic(err)
	}
}

func assertConv(ok bool) {
	if !ok {
		log.Panic("invalid widget conversion")
	}
}

func playerReleaseMedia(player *vlc.Player) {
	player.Stop()
	if media, _ := player.Media(); media != nil {
		media.Release()
	}
}

func main() {
	// Initialize libVLC module.
	err := vlc.Init("--quiet", "--no-xlib")
	assertErr(err)

	// Create a new player.
	player, err := vlc.NewPlayer()
	assertErr(err)

	// Create new GTK application.
	app, err := gtk.ApplicationNew(appId, glib.APPLICATION_FLAGS_NONE)
	assertErr(err)

	app.Connect("activate", func() {
		// Load application layout.
		builder, err := gtk.BuilderNewFromFile("layout.glade")
		assertErr(err)

		// Get application window.
		obj, err := builder.GetObject("appWindow")
		assertErr(err)
		appWin, ok := obj.(*gtk.ApplicationWindow)
		assertConv(ok)

		// Get play button.
		obj, err = builder.GetObject("playButton")
		assertErr(err)
		playButton, ok := obj.(*gtk.Button)
		assertConv(ok)

		// Add builder signal handlers.
		signals := map[string]interface{}{
			"onRealizePlayerArea": func(playerArea *gtk.DrawingArea) {
				// Set window for the player.
				playerWindow, err := playerArea.GetWindow()
				assertErr(err)
				setPlayerWindow(player, playerWindow)
				assertErr(err)
			},
			"onDrawPlayerArea": func(playerArea *gtk.DrawingArea, cr *cairo.Context) {
				cr.SetSourceRGB(0, 0, 0)
				cr.Paint()
			},
			"onActivateOpenFile": func() {
				fileDialog, err := gtk.FileChooserDialogNewWith2Buttons(
					"Choose file...",
					appWin, gtk.FILE_CHOOSER_ACTION_OPEN,
					"Cancel", gtk.RESPONSE_DELETE_EVENT,
					"Open", gtk.RESPONSE_ACCEPT)
				assertErr(err)
				defer fileDialog.Destroy()

				fileFilter, err := gtk.FileFilterNew()
				assertErr(err)
				fileFilter.SetName("Media files")
				fileFilter.AddPattern("*.mp4")
				fileFilter.AddPattern("*.mp3")
				fileDialog.AddFilter(fileFilter)

				if result := fileDialog.Run(); result == gtk.RESPONSE_ACCEPT {
					// Release current media, if any.
					playerReleaseMedia(player)

					// Get selected filename.
					filename := fileDialog.GetFilename()

					// Load media and start playback.
					if _, err := player.LoadMediaFromPath(filename); err != nil {
						log.Printf("Cannot load selected media: %s\n", err)
						return
					}

					player.Play()
					playButton.SetLabel("gtk-media-pause")
				}
			},
			"onActivateQuit": func() {
				app.Quit()
			},
			"onClickPlayButton": func(playButton *gtk.Button) {
				if media, _ := player.Media(); media == nil {
					return
				}

				if player.IsPlaying() {
					player.SetPause(true)
					playButton.SetLabel("gtk-media-play")
				} else {
					player.Play()
					playButton.SetLabel("gtk-media-pause")
				}
			},
			"onClickStopButton": func(stopButton *gtk.Button) {
				player.Stop()
				playButton.SetLabel("gtk-media-play")
			},
		}
		builder.ConnectSignals(signals)

		appWin.ShowAll()
		app.AddWindow(appWin)
	})

	// Cleanup on exit.
	app.Connect("shutdown", func() {
		playerReleaseMedia(player)
		player.Release()
		vlc.Release()
	})

	// Launch the application.
	os.Exit(app.Run(os.Args))
}
<commit_msg>Minor GTK 3 player example improvement<commit_after>package main

import (
	"log"
	"os"

	vlc "github.com/adrg/libvlc-go/v3"
	"github.com/gotk3/gotk3/cairo"
	"github.com/gotk3/gotk3/glib"
	"github.com/gotk3/gotk3/gtk"
)

const appId = "com.github.libvlc-go.gtk3-media-player-example"

func builderGetObject(builder *gtk.Builder, name string) glib.IObject {
	obj, _ := builder.GetObject(name)
	return obj
}

func assertErr(err error) {
	if err != nil {
		log.Panic(err)
	}
}

func assertConv(ok bool) {
	if !ok {
		log.Panic("invalid widget conversion")
	}
}

func playerReleaseMedia(player *vlc.Player) {
	player.Stop()
	if media, _ := player.Media(); media != nil {
		media.Release()
	}
}

func main() {
	// Initialize libVLC module.
	err := vlc.Init("--quiet", "--no-xlib")
	assertErr(err)

	// Create a new player.
	player, err := vlc.NewPlayer()
	assertErr(err)

	// Create new GTK application.
	app, err := gtk.ApplicationNew(appId, glib.APPLICATION_FLAGS_NONE)
	assertErr(err)

	app.Connect("activate", func() {
		// Load application layout.
		builder, err := gtk.BuilderNewFromFile("layout.glade")
		assertErr(err)

		// Get application window.
		appWin, ok := builderGetObject(builder, "appWindow").(*gtk.ApplicationWindow)
		assertConv(ok)

		// Get play button.
		playButton, ok := builderGetObject(builder, "playButton").(*gtk.Button)
		assertConv(ok)

		// Add builder signal handlers.
		signals := map[string]interface{}{
			"onRealizePlayerArea": func(playerArea *gtk.DrawingArea) {
				// Set window for the player.
				playerWindow, err := playerArea.GetWindow()
				assertErr(err)
				setPlayerWindow(player, playerWindow)
				assertErr(err)
			},
			"onDrawPlayerArea": func(playerArea *gtk.DrawingArea, cr *cairo.Context) {
				cr.SetSourceRGB(0, 0, 0)
				cr.Paint()
			},
			"onActivateOpenFile": func() {
				fileDialog, err := gtk.FileChooserDialogNewWith2Buttons(
					"Choose file...",
					appWin, gtk.FILE_CHOOSER_ACTION_OPEN,
					"Cancel", gtk.RESPONSE_DELETE_EVENT,
					"Open", gtk.RESPONSE_ACCEPT)
				assertErr(err)
				defer fileDialog.Destroy()

				fileFilter, err := gtk.FileFilterNew()
				assertErr(err)
				fileFilter.SetName("Media files")
				fileFilter.AddPattern("*.mp4")
				fileFilter.AddPattern("*.mp3")
				fileDialog.AddFilter(fileFilter)

				if result := fileDialog.Run(); result == gtk.RESPONSE_ACCEPT {
					// Release current media, if any.
					playerReleaseMedia(player)

					// Get selected filename.
					filename := fileDialog.GetFilename()

					// Load media and start playback.
					if _, err := player.LoadMediaFromPath(filename); err != nil {
						log.Printf("Cannot load selected media: %s\n", err)
						return
					}

					player.Play()
					playButton.SetLabel("gtk-media-pause")
				}
			},
			"onActivateQuit": func() {
				app.Quit()
			},
			"onClickPlayButton": func(playButton *gtk.Button) {
				if media, _ := player.Media(); media == nil {
					return
				}

				if player.IsPlaying() {
					player.SetPause(true)
					playButton.SetLabel("gtk-media-play")
				} else {
					player.Play()
					playButton.SetLabel("gtk-media-pause")
				}
			},
			"onClickStopButton": func(stopButton *gtk.Button) {
				player.Stop()
				playButton.SetLabel("gtk-media-play")
			},
		}
		builder.ConnectSignals(signals)

		appWin.ShowAll()
		app.AddWindow(appWin)
	})

	// Cleanup on exit.
	app.Connect("shutdown", func() {
		playerReleaseMedia(player)
		player.Release()
		vlc.Release()
	})

	// Launch the application.
	os.Exit(app.Run(os.Args))
}
<|endoftext|>


<commit_before>package lib

import (
	"fmt"
	"github.com/namsral/flag"
	"os"
	"strings"
)

// ReservedSpace -
const ReservedSpace = 450000000 // 450Mb

// Config -
type Config struct {
	Folders        []string `json:"folders"`
	DryRun         bool     `json:"dryRun"`
	NotifyCalc     int      `json:"notifyCalc"`
	NotifyMove     int      `json:"notifyMove"`
	ReservedAmount int64    `json:"reservedAmount"`
	ReservedUnit   string   `json:"reservedUnit"`
	RsyncFlags     []string `json:"rsyncFlags"`
	Version        string   `json:"version"`
}

// NotifyCalc/NotifyMove possible values
// 0 - no notification
// 1 - simple notification
// 2 - detailed notification

// Settings -
type Settings struct {
	Config

	Conf       string
	Port       string
	Log        string
	APIFolders []string
	// ReservedSpace int64
}

// NewSettings -
func NewSettings(version string) (*Settings, error) {
	var config, port, log, folders, rsyncFlags, apiFolders string
	var dryRun bool
	var notifyCalc, notifyMove int

	// /boot/config/plugins/unbalance/
	flag.StringVar(&config, "config", "/boot/config/plugins/unbalance/unbalance.conf", "config location")
	flag.StringVar(&port, "port", "6237", "port to run the server")
	flag.StringVar(&log, "log", "/boot/logs/unbalance.log", "pathname where log file will be written to")
	flag.StringVar(&folders, "folders", "", "folders that will be scanned for media")
	flag.BoolVar(&dryRun, "dryRun", true, "perform a dry-run rather than actual work")
	flag.IntVar(&notifyCalc, "notifyCalc", 0, "notify via email after calculation operation has completed (unraid notifications must be set up first): 0 - No notifications; 1 - Simple notifications; 2 - Detailed notifications")
	flag.IntVar(&notifyMove, "notifyMove", 0, "notify via email after move operation has completed (unraid notifications must be set up first): 0 - No notifications; 1 - Simple notifications; 2 - Detailed notifications")
	flag.StringVar(&rsyncFlags, "rsyncFlags", "", "custom rsync flags")
	flag.StringVar(&apiFolders, "apiFolders", "/var/local/emhttp", "folders to look for api endpoints")

	if found, _ := Exists("/boot/config/plugins/unbalance/unbalance.conf"); found {
		flag.Set("config", "/boot/config/plugins/unbalance/unbalance.conf")
	}

	flag.Parse()

	// fmt.Printf("folders: %s\nconfig: %s\n", folders, config)

	s := &Settings{}

	if folders == "" {
		s.Folders = make([]string, 0)
	} else {
		s.Folders = strings.Split(folders, "|")
	}

	if rsyncFlags == "" {
		s.RsyncFlags = []string{"-avRX", "--partial"}
	} else {
		s.RsyncFlags = strings.Split(rsyncFlags, "|")
	}

	s.DryRun = dryRun
	s.NotifyCalc = notifyCalc
	s.NotifyMove = notifyMove
	s.ReservedAmount = ReservedSpace / 1000 / 1000
	s.ReservedUnit = "Mb"
	s.Version = version

	s.Conf = config
	s.Port = port
	s.Log = log
	s.APIFolders = strings.Split(apiFolders, "|")

	return s, nil
}

// AddFolder -
func (s *Settings) AddFolder(folder string) {
	s.Folders = append(s.Folders, folder)
}

// DeleteFolder -
func (s *Settings) DeleteFolder(folder string) {

	index := -1
	for p, v := range s.Folders {
		if v == folder {
			index = p
			break
		}
	}

	if index == -1 {
		return
	}

	s.Folders = append(s.Folders[:index], s.Folders[index+1:]...)
}

// ToggleDryRun -
func (s *Settings) ToggleDryRun() {
	s.DryRun = !s.DryRun
}

// Save -
func (s *Settings) Save() (err error) {
	tmpFile := s.Conf + ".tmp"

	folders := strings.Join(s.Folders, "|")
	if err = WriteLine(tmpFile, fmt.Sprintf("folders=%s", folders)); err != nil {
		return err
	}

	if err = WriteLine(tmpFile, fmt.Sprintf("dryRun=%t", s.DryRun)); err != nil {
		return err
	}

	if err = WriteLine(tmpFile, fmt.Sprintf("notifyCalc=%d", s.NotifyCalc)); err != nil {
		return err
	}

	if err = WriteLine(tmpFile, fmt.Sprintf("notifyMove=%d", s.NotifyCalc)); err != nil {
		return err
	}

	rsyncFlags := strings.Join(s.RsyncFlags, "|")
	if err = WriteLine(tmpFile, fmt.Sprintf("rsyncFlags=%s", rsyncFlags)); err != nil {
		return err
	}

	os.Rename(tmpFile, s.Conf)

	return
}
<commit_msg>Closes #26<commit_after>package lib

import (
	"fmt"
	"github.com/namsral/flag"
	"os"
	"strings"
)

// ReservedSpace -
const ReservedSpace = 450000000 // 450Mb

// Config -
type Config struct {
	Folders        []string `json:"folders"`
	DryRun         bool     `json:"dryRun"`
	NotifyCalc     int      `json:"notifyCalc"`
	NotifyMove     int      `json:"notifyMove"`
	ReservedAmount int64    `json:"reservedAmount"`
	ReservedUnit   string   `json:"reservedUnit"`
	RsyncFlags     []string `json:"rsyncFlags"`
	Version        string   `json:"version"`
}

// NotifyCalc/NotifyMove possible values
// 0 - no notification
// 1 - simple notification
// 2 - detailed notification

// Settings -
type Settings struct {
	Config

	Conf       string
	Port       string
	Log        string
	APIFolders []string
	// ReservedSpace int64
}

// NewSettings -
func NewSettings(version string) (*Settings, error) {
	var config, port, log, folders, rsyncFlags, apiFolders string
	var dryRun bool
	var notifyCalc, notifyMove int

	// /boot/config/plugins/unbalance/
	flag.StringVar(&config, "config", "/boot/config/plugins/unbalance/unbalance.conf", "config location")
	flag.StringVar(&port, "port", "6237", "port to run the server")
	flag.StringVar(&log, "log", "/boot/logs/unbalance.log", "pathname where log file will be written to")
	flag.StringVar(&folders, "folders", "", "folders that will be scanned for media")
	flag.BoolVar(&dryRun, "dryRun", true, "perform a dry-run rather than actual work")
	flag.IntVar(&notifyCalc, "notifyCalc", 0, "notify via email after calculation operation has completed (unraid notifications must be set up first): 0 - No notifications; 1 - Simple notifications; 2 - Detailed notifications")
	flag.IntVar(&notifyMove, "notifyMove", 0, "notify via email after move operation has completed (unraid notifications must be set up first): 0 - No notifications; 1 - Simple notifications; 2 - Detailed notifications")
	flag.StringVar(&rsyncFlags, "rsyncFlags", "", "custom rsync flags")
	flag.StringVar(&apiFolders, "apiFolders", "/var/local/emhttp", "folders to look for api endpoints")

	if found, _ := Exists("/boot/config/plugins/unbalance/unbalance.conf"); found {
		flag.Set("config", "/boot/config/plugins/unbalance/unbalance.conf")
	}

	flag.Parse()

	// fmt.Printf("folders: %s\nconfig: %s\n", folders, config)

	s := &Settings{}

	if folders == "" {
		s.Folders = make([]string, 0)
	} else {
		s.Folders = strings.Split(folders, "|")
	}

	if rsyncFlags == "" {
		s.RsyncFlags = []string{"-avRX", "--partial"}
	} else {
		s.RsyncFlags = strings.Split(rsyncFlags, "|")
	}

	s.DryRun = dryRun
	s.NotifyCalc = notifyCalc
	s.NotifyMove = notifyMove
	s.ReservedAmount = ReservedSpace / 1000 / 1000
	s.ReservedUnit = "Mb"
	s.Version = version

	s.Conf = config
	s.Port = port
	s.Log = log
	s.APIFolders = strings.Split(apiFolders, "|")

	return s, nil
}

// AddFolder -
func (s *Settings) AddFolder(folder string) {
	s.Folders = append(s.Folders, folder)
}

// DeleteFolder -
func (s *Settings) DeleteFolder(folder string) {

	index := -1
	for p, v := range s.Folders {
		if v == folder {
			index = p
			break
		}
	}

	if index == -1 {
		return
	}

	s.Folders = append(s.Folders[:index], s.Folders[index+1:]...)
}

// ToggleDryRun -
func (s *Settings) ToggleDryRun() {
	s.DryRun = !s.DryRun
}

// Save -
func (s *Settings) Save() (err error) {
	tmpFile := s.Conf + ".tmp"

	folders := strings.Join(s.Folders, "|")
	if err = WriteLine(tmpFile, fmt.Sprintf("folders=%s", folders)); err != nil {
		return err
	}

	if err = WriteLine(tmpFile, fmt.Sprintf("dryRun=%t", s.DryRun)); err != nil {
		return err
	}

	if err = WriteLine(tmpFile, fmt.Sprintf("notifyCalc=%d", s.NotifyCalc)); err != nil {
		return err
	}

	if err = WriteLine(tmpFile, fmt.Sprintf("notifyMove=%d", s.NotifyMove)); err != nil {
		return err
	}

	rsyncFlags := strings.Join(s.RsyncFlags, "|")
	if err = WriteLine(tmpFile, fmt.Sprintf("rsyncFlags=%s", rsyncFlags)); err != nil {
		return err
	}

	os.Rename(tmpFile, s.Conf)

	return
}
<|endoftext|>


<commit_before>// Copyright 2017 The Upspin Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package test

import (
	"crypto/rand"
	"fmt"
	"testing"
	"time"

	"upspin.io/errors"
	"upspin.io/path"
	"upspin.io/test/testenv"
	"upspin.io/upspin"
)

func testSnapshot(t *testing.T, r *testenv.Runner) {
	const (
		base        = ownerName + "/snapshot-test"
		dir         = base + "/dir"
		file        = dir + "/file"
		accessFile  = base + "/Access"
		access      = "*:all" // intentionally permissive.
		snapshotDir = snapshotUser + "/"

		// must be in sync with dir/server/snapshot.go
		snapshotControlFile = snapshotDir + "/TakeSnapshot"
	)

	data := randomString(t, 16)

	r.As(ownerName)
	r.MakeDirectory(base)
	r.Put(accessFile, access)
	r.MakeDirectory(dir)
	r.Put(file, data)
	if r.Failed() {
		t.Fatal(r.Diag())
	}

	// Take the snapshot.
	r.As(snapshotUser)
	r.MakeDirectory(snapshotDir)
	r.Put(snapshotControlFile, "")
	if r.Failed() {
		t.Fatal(r.Diag())
	}

	// If the control file actually exists in snapshotDir, then this
	// DirServer does not support snapshotting.
	r.Get(snapshotControlFile)
	if !r.Failed() {
		if r.Data == "" {
			t.Log("Snapshotting not supported.")
			return
		}
		t.Fatalf("Non-empty snapshot control file: %q.", r.Data)
	}

	// Verify snapshot was taken today and has the correct data in it.
	r.As(ownerName)
	snapPattern := snapshotDir + time.Now().UTC().Format("2006/01/02") + "/"

	// Watch the snapshot root for the new snapshot.
	done := r.DirWatch(upspin.PathName(snapPattern), -1)
	if r.Failed() {
		t.Fatal(r.Diag())
	}
	// There could be many entries, since snapshots are for the root. Keep
	// looking until we find what we want.
	var found upspin.PathName
	for i := 0; i < 2; i++ {
		// We use GetNEvents because we don't have a fixed name to use
		// with r.GotEvent(name). We need two entries, the top directory
		// with the date and the sub directory with the time.
		if !r.GetNEvents(2) {
			t.Fatal(r.Diag())
		}
		entry := r.Events[1].Entry

		// Check  entry contents and name.
		file := path.Join(entry.Name, "snapshot-test", "dir", "file")
		r.Get(file)
		if r.Failed() {
			// TODO: remove once the failure is fixed.
			t.Logf("Failed to Get %q. Attempting to Glob %q", file, entry.Name)
			debugFailure(t, r, entry.Name, file)

			// t.Fatal(r.Diag())
		}
		if r.Data == data {
			found = file
			break
		}
	}
	close(done)
	if found == "" {
		t.Fatalf("Unable to find a snapshot in %s", snapPattern)
	}

	// Ensure no one else can read this snapshotted file, even with a
	// permissive Access file.
	r.As(readerName)
	r.DirLookup(found)
	if !r.Match(errors.E(errors.Private)) {
		t.Fatal(r.Diag())
	}

	// WhichAccess for a snapshotted name returns nothing, even if the
	// Access file exists in the path, which is the case here.
	r.As(ownerName)
	r.DirWhichAccess(found)
	if !r.GotNilEntry() {
		t.Fatal(r.Diag())
	}

	// No one can delete snapshots.
	r.Delete(found)
	if !r.Match(errors.E(errors.Permission)) {
		t.Fatal(r.Diag())
	}

	// No one can overwrite a snapshot.
	r.Put(found, "yo")
	if !r.Match(errors.E(errors.Permission)) {
		t.Fatal(r.Diag())
	}
}

// TODO: remove.
func debugFailure(t *testing.T, r *testenv.Runner, dir, file upspin.PathName) {
	c := r.ClientFor(ownerName)

	entries, err := c.Glob(string(dir) + "/*")
	if err != nil {
		t.Fatalf("Error Globbing: %v", err)
	}
	for i, e := range entries {
		t.Logf("%d: %s", i, e.Name)
	}

	e, err := c.Lookup(file, true)
	if err != nil {
		t.Fatalf("Error in lookup of %q: %v", file, err)
	}
	t.Logf("Entry: %+v", e)
}

func randomString(t *testing.T, size int) string {
	buf := make([]byte, size)
	_, err := rand.Read(buf)
	if err != nil {
		t.Fatal(err)
	}
	return fmt.Sprintf("%x", buf)
}
<commit_msg>test: add more instrumentation to flaky snapshot test<commit_after>// Copyright 2017 The Upspin Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package test

import (
	"crypto/rand"
	"fmt"
	"testing"
	"time"

	"upspin.io/errors"
	"upspin.io/path"
	"upspin.io/test/testenv"
	"upspin.io/upspin"
)

func testSnapshot(t *testing.T, r *testenv.Runner) {
	const (
		base        = ownerName + "/snapshot-test"
		dir         = base + "/dir"
		file        = dir + "/file"
		accessFile  = base + "/Access"
		access      = "*:all" // intentionally permissive.
		snapshotDir = snapshotUser + "/"

		// must be in sync with dir/server/snapshot.go
		snapshotControlFile = snapshotDir + "/TakeSnapshot"
	)

	data := randomString(t, 16)

	r.As(ownerName)
	r.MakeDirectory(base)
	r.Put(accessFile, access)
	r.MakeDirectory(dir)
	r.Put(file, data)
	if r.Failed() {
		t.Fatal(r.Diag())
	}

	// Take the snapshot.
	r.As(snapshotUser)
	r.MakeDirectory(snapshotDir)
	r.Put(snapshotControlFile, "")
	if r.Failed() {
		t.Fatal(r.Diag())
	}

	// If the control file actually exists in snapshotDir, then this
	// DirServer does not support snapshotting.
	r.Get(snapshotControlFile)
	if !r.Failed() {
		if r.Data == "" {
			t.Log("Snapshotting not supported.")
			return
		}
		t.Fatalf("Non-empty snapshot control file: %q.", r.Data)
	}
	t.Log("Snapshot command issued")

	// Verify snapshot was taken today and has the correct data in it.
	r.As(ownerName)
	snapPattern := snapshotDir + time.Now().UTC().Format("2006/01/02") + "/"

	// Watch the snapshot root for the new snapshot.
	done := r.DirWatch(upspin.PathName(snapPattern), -1)
	if r.Failed() {
		t.Fatal(r.Diag())
	}
	// There could be many entries, since snapshots are for the root. Keep
	// looking until we find what we want.
	var found upspin.PathName
	for i := 0; i < 2; i++ {
		// We use GetNEvents because we don't have a fixed name to use
		// with r.GotEvent(name). We need two entries, the top directory
		// with the date and the sub directory with the time.
		if !r.GetNEvents(2) {
			t.Fatal(r.Diag())
		}
		entry := r.Events[1].Entry

		// Check  entry contents and name.
		file := path.Join(entry.Name, "snapshot-test", "dir", "file")
		r.Get(file)
		if r.Failed() {
			// TODO: remove once the failure is fixed.
			t.Logf("Failed to Get %q. Attempting to Glob %q", file, entry.Name)
			debugFailure(t, r, entry.Name, file)

			// t.Fatal(r.Diag())
		}
		if r.Data == data {
			found = file
			break
		}
	}
	close(done)
	if found == "" {
		t.Fatalf("Unable to find a snapshot in %s", snapPattern)
	}

	// Ensure no one else can read this snapshotted file, even with a
	// permissive Access file.
	r.As(readerName)
	r.DirLookup(found)
	if !r.Match(errors.E(errors.Private)) {
		t.Fatal(r.Diag())
	}

	// WhichAccess for a snapshotted name returns nothing, even if the
	// Access file exists in the path, which is the case here.
	r.As(ownerName)
	r.DirWhichAccess(found)
	if !r.GotNilEntry() {
		t.Fatal(r.Diag())
	}

	// No one can delete snapshots.
	r.Delete(found)
	if !r.Match(errors.E(errors.Permission)) {
		t.Fatal(r.Diag())
	}

	// No one can overwrite a snapshot.
	r.Put(found, "yo")
	if !r.Match(errors.E(errors.Permission)) {
		t.Fatal(r.Diag())
	}
}

// TODO: remove.
func debugFailure(t *testing.T, r *testenv.Runner, dir, file upspin.PathName) {
	c := r.ClientFor(ownerName)

	entries, err := c.Glob(string(dir) + "/*")
	if err != nil {
		t.Fatalf("Error Globbing: %v", err)
	}
	for i, e := range entries {
		t.Logf("%d: %s", i, e.Name)
	}

	e, err := c.Lookup(file, true)
	if err != nil {
		t.Errorf("Error in lookup of %q: %v", file, err)
	}
	t.Logf("Entry: %+v", e)
}

func randomString(t *testing.T, size int) string {
	buf := make([]byte, size)
	_, err := rand.Read(buf)
	if err != nil {
		t.Fatal(err)
	}
	return fmt.Sprintf("%x", buf)
}
<|endoftext|>


<commit_before>package sgsimulator

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

	"github.com/gorilla/mux"
)

type SGSimulator struct{}

type BulkDocsResponse struct {
	Name    string
	Hobbies []string
}

func HomeHandler(w http.ResponseWriter, req *http.Request) {
	log.Printf("HomeHandler called with req: %+v", req)
	w.Write([]byte("Sync Gateway Simulator\n"))
}

func DoNothingHandler(w http.ResponseWriter, req *http.Request) {
	log.Printf("DoNothingHandler called with req: %+v", req)
	w.Write([]byte("Sync Gateway Simulator DB\n"))
}

func BulkDocsHandler(w http.ResponseWriter, req *http.Request) {
	log.Printf("BulkDocsHandler called with req: %+v", req)

	bulkDocsResponseSlice := []map[string]string{}
	bulkDocResponse := map[string]string{
		"id":  "1",
		"rev": "1-34243",
	}
	bulkDocsResponseSlice = append(bulkDocsResponseSlice, bulkDocResponse)
	js, err := json.Marshal(bulkDocsResponseSlice)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.Write(js)

	w.Write([]byte("Sync Gateway Simulator DB\n"))
}

func NewSGSimulator() *SGSimulator {
	return &SGSimulator{}
}

func (sg *SGSimulator) Run() {

	// TODO: parameterize via CLI
	dbName := "db"
	port := 8000

	r := mux.NewRouter()
	r.HandleFunc("/", DoNothingHandler)
	dbRouter := r.PathPrefix(fmt.Sprintf("/%v", dbName)).Subrouter()
	dbRouter.Path("/_user/").HandlerFunc(DoNothingHandler)
	dbRouter.Path("/_bulk_docs").HandlerFunc(BulkDocsHandler)

	http.Handle("/", r)

	srv := &http.Server{
		Handler:      r,
		Addr:         fmt.Sprintf("127.0.0.1:%d", port),
		WriteTimeout: 15 * time.Second,
		ReadTimeout:  15 * time.Second,
	}

	log.Printf("Listening on %v", srv.Addr)

	log.Fatal(srv.ListenAndServe())

}
<commit_msg>Handles POSTing single docs<commit_after>package sgsimulator

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

	"github.com/gorilla/mux"
)

type SGSimulator struct{}

type BulkDocsResponse struct {
	Name    string
	Hobbies []string
}

func HomeHandler(w http.ResponseWriter, req *http.Request) {
	log.Printf("HomeHandler called with req: %+v", req)
	w.Write([]byte("Sync Gateway Simulator\n"))
}

func DoNothingHandler(w http.ResponseWriter, req *http.Request) {
	log.Printf("DoNothingHandler called with req: %+v", req)
	w.Write([]byte("Sync Gateway Simulator DB\n"))
}

func BulkDocsHandler(w http.ResponseWriter, req *http.Request) {
	log.Printf("BulkDocsHandler called with req: %+v", req)

	bulkDocsResponseSlice := []map[string]string{}
	bulkDocResponse := map[string]string{
		"id":  "1",
		"rev": "1-34243",
	}
	bulkDocsResponseSlice = append(bulkDocsResponseSlice, bulkDocResponse)
	js, err := json.Marshal(bulkDocsResponseSlice)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.Write(js)

	w.Write([]byte("Sync Gateway Simulator DB\n"))
}

func NewSGSimulator() *SGSimulator {
	return &SGSimulator{}
}

func (sg *SGSimulator) Run() {

	// TODO: parameterize via CLI
	dbName := "db"
	port := 8000

	r := mux.NewRouter()
	r.HandleFunc("/", DoNothingHandler)
	dbRouter := r.PathPrefix(fmt.Sprintf("/%v", dbName)).Subrouter()
	dbRouter.Path("/").HandlerFunc(DoNothingHandler)
	dbRouter.Path("/_user/").HandlerFunc(DoNothingHandler)
	dbRouter.Path("/_bulk_docs").HandlerFunc(BulkDocsHandler)

	http.Handle("/", r)

	srv := &http.Server{
		Handler:      r,
		Addr:         fmt.Sprintf("127.0.0.1:%d", port),
		WriteTimeout: 15 * time.Second,
		ReadTimeout:  15 * time.Second,
	}

	log.Printf("Listening on %v", srv.Addr)

	log.Fatal(srv.ListenAndServe())

}
<|endoftext|>


<commit_before>package eagain

import (
	"io"

	"golang.org/x/sys/unix"

	"github.com/lxc/lxd/shared"
)

// Reader represents an io.Reader that handles EAGAIN
type Reader struct {
	Reader io.Reader
}

// Read behaves like io.Reader.Read but will retry on EAGAIN
func (er Reader) Read(p []byte) (int, error) {
again:
	n, err := er.Reader.Read(p)
	if err == nil {
		return n, nil
	}

	// keep retrying on EAGAIN
	errno, ok := shared.GetErrno(err)
	if ok && (errno == unix.EAGAIN || errno == unix.EINTR) {
		goto again
	}

	return n, err
}

// Writer represents an io.Writer that handles EAGAIN
type Writer struct {
	Writer io.Writer
}

// Write behaves like io.Writer.Write but will retry on EAGAIN
func (ew Writer) Write(p []byte) (int, error) {
again:
	n, err := ew.Writer.Write(p)
	if err == nil {
		return n, nil
	}

	// keep retrying on EAGAIN
	errno, ok := shared.GetErrno(err)
	if ok && (errno == unix.EAGAIN || errno == unix.EINTR) {
		goto again
	}

	return n, err
}
<commit_msg>shared/eagain: Restrict to Linux<commit_after>// +build linux

package eagain

import (
	"io"

	"golang.org/x/sys/unix"

	"github.com/lxc/lxd/shared"
)

// Reader represents an io.Reader that handles EAGAIN
type Reader struct {
	Reader io.Reader
}

// Read behaves like io.Reader.Read but will retry on EAGAIN
func (er Reader) Read(p []byte) (int, error) {
again:
	n, err := er.Reader.Read(p)
	if err == nil {
		return n, nil
	}

	// keep retrying on EAGAIN
	errno, ok := shared.GetErrno(err)
	if ok && (errno == unix.EAGAIN || errno == unix.EINTR) {
		goto again
	}

	return n, err
}

// Writer represents an io.Writer that handles EAGAIN
type Writer struct {
	Writer io.Writer
}

// Write behaves like io.Writer.Write but will retry on EAGAIN
func (ew Writer) Write(p []byte) (int, error) {
again:
	n, err := ew.Writer.Write(p)
	if err == nil {
		return n, nil
	}

	// keep retrying on EAGAIN
	errno, ok := shared.GetErrno(err)
	if ok && (errno == unix.EAGAIN || errno == unix.EINTR) {
		goto again
	}

	return n, err
}
<|endoftext|>


<commit_before>package main

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

	dep "github.com/hashicorp/consul-template/dependency"
)

// now is function that represents the current time in UTC. This is here
// primarily for the tests to override times.
var now = func() time.Time { return time.Now().UTC() }

// datacentersFunc returns or accumulates datacenter dependencies.
func datacentersFunc(brain *Brain,
	used, missing map[string]dep.Dependency) func(...string) ([]string, error) {
	return func(s ...string) ([]string, error) {
		result := make([]string, 0)

		d, err := dep.ParseDatacenters(s...)
		if err != nil {
			return result, err
		}

		addDependency(used, d)

		if value, ok := brain.Recall(d); ok {
			return value.([]string), nil
		}

		addDependency(missing, d)

		return result, nil
	}
}

// fileFunc returns or accumulates file dependencies.
func fileFunc(brain *Brain,
	used, missing map[string]dep.Dependency) func(string) (string, error) {
	return func(s string) (string, error) {
		if len(s) == 0 {
			return "", nil
		}

		d, err := dep.ParseFile(s)
		if err != nil {
			return "", err
		}

		addDependency(used, d)

		if value, ok := brain.Recall(d); ok {
			if value == nil {
				return "", nil
			} else {
				return value.(string), nil
			}
		}

		addDependency(missing, d)

		return "", nil
	}
}

// keyFunc returns or accumulates key dependencies.
func keyFunc(brain *Brain,
	used, missing map[string]dep.Dependency) func(string) (string, error) {
	return func(s string) (string, error) {
		if len(s) == 0 {
			return "", nil
		}

		d, err := dep.ParseStoreKey(s)
		if err != nil {
			return "", err
		}

		addDependency(used, d)

		if value, ok := brain.Recall(d); ok {
			if value == nil {
				return "", nil
			} else {
				return value.(string), nil
			}
		}

		addDependency(missing, d)

		return "", nil
	}
}

// lsFunc returns or accumulates keyPrefix dependencies.
func lsFunc(brain *Brain,
	used, missing map[string]dep.Dependency) func(string) ([]*dep.KeyPair, error) {
	return func(s string) ([]*dep.KeyPair, error) {
		result := make([]*dep.KeyPair, 0)

		if len(s) == 0 {
			return result, nil
		}

		d, err := dep.ParseStoreKeyPrefix(s)
		if err != nil {
			return result, err
		}

		addDependency(used, d)

		// Only return non-empty top-level keys
		if value, ok := brain.Recall(d); ok {
			for _, pair := range value.([]*dep.KeyPair) {
				if pair.Key != "" && !strings.Contains(pair.Key, "/") {
					result = append(result, pair)
				}
			}
			return result, nil
		}

		addDependency(missing, d)

		return result, nil
	}
}

// nodesFunc returns or accumulates catalog node dependencies.
func nodesFunc(brain *Brain,
	used, missing map[string]dep.Dependency) func(...string) ([]*dep.Node, error) {
	return func(s ...string) ([]*dep.Node, error) {
		result := make([]*dep.Node, 0)

		d, err := dep.ParseCatalogNodes(s...)
		if err != nil {
			return nil, err
		}

		addDependency(used, d)

		if value, ok := brain.Recall(d); ok {
			return value.([]*dep.Node), nil
		}

		addDependency(missing, d)

		return result, nil
	}
}

// serviceFunc returns or accumulates health service dependencies.
func serviceFunc(brain *Brain,
	used, missing map[string]dep.Dependency) func(...string) ([]*dep.HealthService, error) {
	return func(s ...string) ([]*dep.HealthService, error) {
		result := make([]*dep.HealthService, 0)

		if len(s) == 0 || s[0] == "" {
			return result, nil
		}

		d, err := dep.ParseHealthServices(s...)
		if err != nil {
			return nil, err
		}

		addDependency(used, d)

		if value, ok := brain.Recall(d); ok {
			return value.([]*dep.HealthService), nil
		}

		addDependency(missing, d)

		return result, nil
	}
}

// servicesFunc returns or accumulates catalog services dependencies.
func servicesFunc(brain *Brain,
	used, missing map[string]dep.Dependency) func(...string) ([]*dep.CatalogService, error) {
	return func(s ...string) ([]*dep.CatalogService, error) {
		result := make([]*dep.CatalogService, 0)

		d, err := dep.ParseCatalogServices(s...)
		if err != nil {
			return nil, err
		}

		addDependency(used, d)

		if value, ok := brain.Recall(d); ok {
			return value.([]*dep.CatalogService), nil
		}

		addDependency(missing, d)

		return result, nil
	}
}

// treeFunc returns or accumulates keyPrefix dependencies.
func treeFunc(brain *Brain,
	used, missing map[string]dep.Dependency) func(string) ([]*dep.KeyPair, error) {
	return func(s string) ([]*dep.KeyPair, error) {
		result := make([]*dep.KeyPair, 0)

		if len(s) == 0 {
			return result, nil
		}

		d, err := dep.ParseStoreKeyPrefix(s)
		if err != nil {
			return result, err
		}

		addDependency(used, d)

		// Only return non-empty top-level keys
		if value, ok := brain.Recall(d); ok {
			for _, pair := range value.([]*dep.KeyPair) {
				parts := strings.Split(pair.Key, "/")
				if parts[len(parts)-1] != "" {
					result = append(result, pair)
				}
			}
			return result, nil
		}

		addDependency(missing, d)

		return result, nil
	}

}

// byKey accepts a slice of KV pairs and returns a map of the top-level
// key to all its subkeys. For example:
//
//		elasticsearch/a //=> "1"
//		elasticsearch/b //=> "2"
//		redis/a/b //=> "3"
//
// Passing the result from Consul through byTag would yield:
//
// 		map[string]map[string]string{
//	  	"elasticsearch": &dep.KeyPair{"a": "1"}, &dep.KeyPair{"b": "2"},
//			"redis": &dep.KeyPair{"a/b": "3"}
//		}
//
// Note that the top-most key is stripped from the Key value. Keys that have no
// prefix after stripping are removed from the list.
func byKey(pairs []*dep.KeyPair) (map[string][]*dep.KeyPair, error) {
	m := make(map[string][]*dep.KeyPair)
	for _, pair := range pairs {
		parts := strings.Split(pair.Key, "/")
		top := parts[0]
		key := strings.Join(parts[1:], "/")

		if key == "" {
			// Do not add a key if it has no prefix after stripping.
			continue
		}

		if _, ok := m[top]; !ok {
			m[top] = make([]*dep.KeyPair, 0, 1)
		}
		pair.Key = key
		m[top] = append(m[top], pair)
	}
	return m, nil
}

// byTag is a template func that takes the provided services and
// produces a map based on Service tags.
//
// The map key is a string representing the service tag. The map value is a
// slice of Services which have the tag assigned.
func byTag(in []*dep.HealthService) (map[string][]*dep.HealthService, error) {
	m := make(map[string][]*dep.HealthService)
	for _, s := range in {
		for _, t := range s.Tags {
			m[t] = append(m[t], s)
		}
	}
	return m, nil
}

// env returns the value of the environment variable set
func env(s string) (string, error) {
	return os.Getenv(s), nil
}

// loop accepts varying parameters and differs its behavior. If given one
// parameter, loop will return a goroutine that begins at 0 and loops until the
// given int, increasing the index by 1 each iteration. If given two parameters,
// loop will return a goroutine that begins at the first parameter and loops
// up to but not including the second parameter.
//
//    // Prints 0 1 2 3 4
// 		for _, i := range loop(5) {
// 			print(i)
// 		}
//
//    // Prints 5 6 7
// 		for _, i := range loop(5, 8) {
// 			print(i)
// 		}
//
func loop(ints ...int) (<-chan int, error) {
	var start, stop int
	switch len(ints) {
	case 1:
		start, stop = 0, ints[0]
	case 2:
		start, stop = ints[0], ints[1]
	default:
		return nil, fmt.Errorf("loop: wrong number of arguments, expected 1 or 2"+
			", but got %d", len(ints))
	}

	ch := make(chan int)

	go func() {
		for i := start; i < stop; i++ {
			ch <- i
		}
		close(ch)
	}()

	return ch, nil
}

// parseJSON returns a structure for valid JSON
func parseJSON(s string) (interface{}, error) {
	if s == "" {
		return make([]interface{}, 0), nil
	}

	var data interface{}
	if err := json.Unmarshal([]byte(s), &data); err != nil {
		return nil, err
	}
	return data, nil
}

// replaceAll replaces all occurrences of a value in a string with the given
// replacement value.
func replaceAll(f, t, s string) (string, error) {
	return strings.Replace(s, f, t, -1), nil
}

// regexReplaceAll replaces all occurrences of a regular expression with
// the given replacement value.
func regexReplaceAll(re, pl, s string) (string, error) {
	compiled, err := regexp.Compile(re)
	if err != nil {
		return "", err
	}
	return compiled.ReplaceAllString(s, pl), nil
}

// timestamp returns the current UNIX timestamp in UTC. If an argument is
// specified, it will be used to format the timestamp.
func timestamp(s ...string) (string, error) {
	switch len(s) {
	case 0:
		return now().Format(time.RFC3339), nil
	case 1:
		return now().Format(s[0]), nil
	default:
		return "", fmt.Errorf("timestamp: wrong number of arguments, expected 0 or 1"+
			", but got %d", len(s))
	}
}

// toLower converts the given string (usually by a pipe) to lowercase.
func toLower(s string) (string, error) {
	return strings.ToLower(s), nil
}

// toTitle converts the given string (usually by a pipe) to titlecase.
func toTitle(s string) (string, error) {
	return strings.Title(s), nil
}

// toUpper converts the given string (usually by a pipe) to uppercase.
func toUpper(s string) (string, error) {
	return strings.ToUpper(s), nil
}

// addDependency adds the given Dependency to the map.
func addDependency(m map[string]dep.Dependency, d dep.Dependency) {
	if _, ok := m[d.HashCode()]; !ok {
		m[d.HashCode()] = d
	}
}
<commit_msg>checkpoint<commit_after>package main

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

	dep "github.com/hashicorp/consul-template/dependency"
	"github.com/mitchellh/copystructure"
)

// now is function that represents the current time in UTC. This is here
// primarily for the tests to override times.
var now = func() time.Time { return time.Now().UTC() }

// datacentersFunc returns or accumulates datacenter dependencies.
func datacentersFunc(brain *Brain,
	used, missing map[string]dep.Dependency) func(...string) ([]string, error) {
	return func(s ...string) ([]string, error) {
		result := make([]string, 0)

		d, err := dep.ParseDatacenters(s...)
		if err != nil {
			return result, err
		}

		addDependency(used, d)

		if value, ok := brain.Recall(d); ok {
			return value.([]string), nil
		}

		addDependency(missing, d)

		return result, nil
	}
}

// fileFunc returns or accumulates file dependencies.
func fileFunc(brain *Brain,
	used, missing map[string]dep.Dependency) func(string) (string, error) {
	return func(s string) (string, error) {
		if len(s) == 0 {
			return "", nil
		}

		d, err := dep.ParseFile(s)
		if err != nil {
			return "", err
		}

		addDependency(used, d)

		if value, ok := brain.Recall(d); ok {
			if value == nil {
				return "", nil
			} else {
				return value.(string), nil
			}
		}

		addDependency(missing, d)

		return "", nil
	}
}

// keyFunc returns or accumulates key dependencies.
func keyFunc(brain *Brain,
	used, missing map[string]dep.Dependency) func(string) (string, error) {
	return func(s string) (string, error) {
		if len(s) == 0 {
			return "", nil
		}

		d, err := dep.ParseStoreKey(s)
		if err != nil {
			return "", err
		}

		addDependency(used, d)

		if value, ok := brain.Recall(d); ok {
			if value == nil {
				return "", nil
			} else {
				return value.(string), nil
			}
		}

		addDependency(missing, d)

		return "", nil
	}
}

// lsFunc returns or accumulates keyPrefix dependencies.
func lsFunc(brain *Brain,
	used, missing map[string]dep.Dependency) func(string) ([]*dep.KeyPair, error) {
	return func(s string) ([]*dep.KeyPair, error) {
		result := make([]*dep.KeyPair, 0)

		if len(s) == 0 {
			return result, nil
		}

		d, err := dep.ParseStoreKeyPrefix(s)
		if err != nil {
			return result, err
		}

		addDependency(used, d)

		// Only return non-empty top-level keys
		if value, ok := brain.Recall(d); ok {
			for _, pair := range value.([]*dep.KeyPair) {
				if pair.Key != "" && !strings.Contains(pair.Key, "/") {
					result = append(result, pair)
				}
			}
			return result, nil
		}

		addDependency(missing, d)

		return result, nil
	}
}

// nodesFunc returns or accumulates catalog node dependencies.
func nodesFunc(brain *Brain,
	used, missing map[string]dep.Dependency) func(...string) ([]*dep.Node, error) {
	return func(s ...string) ([]*dep.Node, error) {
		result := make([]*dep.Node, 0)

		d, err := dep.ParseCatalogNodes(s...)
		if err != nil {
			return nil, err
		}

		addDependency(used, d)

		if value, ok := brain.Recall(d); ok {
			return value.([]*dep.Node), nil
		}

		addDependency(missing, d)

		return result, nil
	}
}

// serviceFunc returns or accumulates health service dependencies.
func serviceFunc(brain *Brain,
	used, missing map[string]dep.Dependency) func(...string) ([]*dep.HealthService, error) {
	return func(s ...string) ([]*dep.HealthService, error) {
		result := make([]*dep.HealthService, 0)

		if len(s) == 0 || s[0] == "" {
			return result, nil
		}

		d, err := dep.ParseHealthServices(s...)
		if err != nil {
			return nil, err
		}

		addDependency(used, d)

		if value, ok := brain.Recall(d); ok {
			return value.([]*dep.HealthService), nil
		}

		addDependency(missing, d)

		return result, nil
	}
}

// servicesFunc returns or accumulates catalog services dependencies.
func servicesFunc(brain *Brain,
	used, missing map[string]dep.Dependency) func(...string) ([]*dep.CatalogService, error) {
	return func(s ...string) ([]*dep.CatalogService, error) {
		result := make([]*dep.CatalogService, 0)

		d, err := dep.ParseCatalogServices(s...)
		if err != nil {
			return nil, err
		}

		addDependency(used, d)

		if value, ok := brain.Recall(d); ok {
			return value.([]*dep.CatalogService), nil
		}

		addDependency(missing, d)

		return result, nil
	}
}

// treeFunc returns or accumulates keyPrefix dependencies.
func treeFunc(brain *Brain,
	used, missing map[string]dep.Dependency) func(string) ([]*dep.KeyPair, error) {
	return func(s string) ([]*dep.KeyPair, error) {
		result := make([]*dep.KeyPair, 0)

		if len(s) == 0 {
			return result, nil
		}

		d, err := dep.ParseStoreKeyPrefix(s)
		if err != nil {
			return result, err
		}

		addDependency(used, d)

		// Only return non-empty top-level keys
		if value, ok := brain.Recall(d); ok {
			for _, pair := range value.([]*dep.KeyPair) {
				parts := strings.Split(pair.Key, "/")
				if parts[len(parts)-1] != "" {
					result = append(result, pair)
				}
			}
			return result, nil
		}

		addDependency(missing, d)

		return result, nil
	}

}

// byKey accepts a slice of KV pairs and returns a map of the top-level
// key to all its subkeys. For example:
//
//		elasticsearch/a //=> "1"
//		elasticsearch/b //=> "2"
//		redis/a/b //=> "3"
//
// Passing the result from Consul through byTag would yield:
//
// 		map[string]map[string]string{
//	  	"elasticsearch": &dep.KeyPair{"a": "1"}, &dep.KeyPair{"b": "2"},
//			"redis": &dep.KeyPair{"a/b": "3"}
//		}
//
// Note that the top-most key is stripped from the Key value. Keys that have no
// prefix after stripping are removed from the list.
func byKey(pairs []*dep.KeyPair) (map[string][]*dep.KeyPair, error) {
	m := make(map[string][]*dep.KeyPair)
	for _, pair := range pairs {
		parts := strings.Split(pair.Key, "/")
		top := parts[0]
		key := strings.Join(parts[1:], "/")

		if key == "" {
			// Do not add a key if it has no prefix after stripping.
			continue
		}

		if _, ok := m[top]; !ok {
			m[top] = make([]*dep.KeyPair, 0, 1)
		}

		dup, err := copystructure.Copy(pair)
		if err != nil {
			return nil, err
		}
		newPair := dup.(*dep.KeyPair)

		newPair.Key = key
		m[top] = append(m[top], newPair)
	}

	return m, nil
}

//
//		elasticsearch/a => "1"
//		elasticsearch/b => "2"
//		elasticsearch/c => "2"
//		redis/a/b => "3"
//		redis/a/c => "3"
//
//
// {
// 	"elasticsearch": {
// 		"a": 1,
// 		"b": 2,
// 		"c": 3,
// 	},
// 	"redis": {
// 		"a": {
// 			"b": 3
// 			"c": 3
// 		}
// 	}
// }
//

func by(pairs []*dep.KeyPair) (map[string]interface{}, error) {
	m := make(map[string]interface{})

	for _, pair := range pairs {
		parts := strings.Split(pair.Key, "/")

		// Create the deep map
		var ref interface{}
		// ref := m
		for i := 0; i < len(parts)-1; i++ {
			typed := ref.(map[string]interface{})
			part := parts[i]
			if _, ok := typed[part]; !ok {
				typed[part] = make(map[string]interface{})
			}
			ref = typed[part]
		}

		part := parts[len(parts)-1]
		ref.(map[string]interface{})[part] = &dep.KeyPair{
			Path:        pair.Path,
			Key:         part,
			Value:       pair.Value,
			CreateIndex: pair.CreateIndex,
			ModifyIndex: pair.ModifyIndex,
			LockIndex:   pair.LockIndex,
			Flags:       pair.Flags,
			Session:     pair.Session,
		}
	}

	println(fmt.Sprintf("%#v", m))

	return m, nil
}

// byTag is a template func that takes the provided services and
// produces a map based on Service tags.
//
// The map key is a string representing the service tag. The map value is a
// slice of Services which have the tag assigned.
func byTag(in []*dep.HealthService) (map[string][]*dep.HealthService, error) {
	m := make(map[string][]*dep.HealthService)
	for _, s := range in {
		for _, t := range s.Tags {
			m[t] = append(m[t], s)
		}
	}
	return m, nil
}

// env returns the value of the environment variable set
func env(s string) (string, error) {
	return os.Getenv(s), nil
}

// loop accepts varying parameters and differs its behavior. If given one
// parameter, loop will return a goroutine that begins at 0 and loops until the
// given int, increasing the index by 1 each iteration. If given two parameters,
// loop will return a goroutine that begins at the first parameter and loops
// up to but not including the second parameter.
//
//    // Prints 0 1 2 3 4
// 		for _, i := range loop(5) {
// 			print(i)
// 		}
//
//    // Prints 5 6 7
// 		for _, i := range loop(5, 8) {
// 			print(i)
// 		}
//
func loop(ints ...int) (<-chan int, error) {
	var start, stop int
	switch len(ints) {
	case 1:
		start, stop = 0, ints[0]
	case 2:
		start, stop = ints[0], ints[1]
	default:
		return nil, fmt.Errorf("loop: wrong number of arguments, expected 1 or 2"+
			", but got %d", len(ints))
	}

	ch := make(chan int)

	go func() {
		for i := start; i < stop; i++ {
			ch <- i
		}
		close(ch)
	}()

	return ch, nil
}

// parseJSON returns a structure for valid JSON
func parseJSON(s string) (interface{}, error) {
	if s == "" {
		return make([]interface{}, 0), nil
	}

	var data interface{}
	if err := json.Unmarshal([]byte(s), &data); err != nil {
		return nil, err
	}
	return data, nil
}

// replaceAll replaces all occurrences of a value in a string with the given
// replacement value.
func replaceAll(f, t, s string) (string, error) {
	return strings.Replace(s, f, t, -1), nil
}

// regexReplaceAll replaces all occurrences of a regular expression with
// the given replacement value.
func regexReplaceAll(re, pl, s string) (string, error) {
	compiled, err := regexp.Compile(re)
	if err != nil {
		return "", err
	}
	return compiled.ReplaceAllString(s, pl), nil
}

// timestamp returns the current UNIX timestamp in UTC. If an argument is
// specified, it will be used to format the timestamp.
func timestamp(s ...string) (string, error) {
	switch len(s) {
	case 0:
		return now().Format(time.RFC3339), nil
	case 1:
		return now().Format(s[0]), nil
	default:
		return "", fmt.Errorf("timestamp: wrong number of arguments, expected 0 or 1"+
			", but got %d", len(s))
	}
}

// toLower converts the given string (usually by a pipe) to lowercase.
func toLower(s string) (string, error) {
	return strings.ToLower(s), nil
}

// toTitle converts the given string (usually by a pipe) to titlecase.
func toTitle(s string) (string, error) {
	return strings.Title(s), nil
}

// toUpper converts the given string (usually by a pipe) to uppercase.
func toUpper(s string) (string, error) {
	return strings.ToUpper(s), nil
}

// addDependency adds the given Dependency to the map.
func addDependency(m map[string]dep.Dependency, d dep.Dependency) {
	if _, ok := m[d.HashCode()]; !ok {
		m[d.HashCode()] = d
	}
}
<|endoftext|>


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

import (
	"testing"

	"github.com/GoogleCloudPlatform/buildpacks/internal/acceptance"
)

func init() {
	acceptance.DefineFlags()
}

func TestAcceptancePython(t *testing.T) {
	builder, cleanup := acceptance.CreateBuilder(t)
	t.Cleanup(cleanup)

	testCases := []acceptance.Test{
		{
			Name:    "entrypoint from procfile web",
			App:     "python/simple",
			MustUse: []string{pythonRuntime, pythonPIP, entrypoint},
		},
		{
			Name:       "entrypoint from procfile custom",
			App:        "python/simple",
			Path:       "/custom",
			Entrypoint: "custom", // Must match the non-web process in Procfile.
			MustUse:    []string{pythonRuntime, pythonPIP, entrypoint},
		},
		{
			Name:    "entrypoint from env",
			App:     "python/simple",
			Path:    "/custom",
			Env:     []string{"GOOGLE_ENTRYPOINT=gunicorn -b :8080 custom:app"},
			MustUse: []string{pythonRuntime, pythonPIP, entrypoint},
		},
		{
			Name:    "entrypoint with env var",
			App:     "python/simple",
			Path:    "/env?want=bar",
			Env:     []string{"GOOGLE_ENTRYPOINT=FOO=bar gunicorn -b :8080 main:app"},
			MustUse: []string{pythonRuntime, pythonPIP, entrypoint},
		},
		{
			Name:    "runtime version from env",
			App:     "python/version",
			Path:    "/version?want=3.8.0",
			Env:     []string{"GOOGLE_RUNTIME_VERSION=3.8.0"},
			MustUse: []string{pythonRuntime, pythonPIP, entrypoint},
		},
		{
			Name:    "runtime version from .python-version",
			App:     "python/version",
			Path:    "/version?want=3.8.1",
			MustUse: []string{pythonRuntime, pythonPIP, entrypoint},
		},
		{
			Name:       "selected via GOOGLE_RUNTIME",
			App:        "override",
			Env:        []string{"GOOGLE_RUNTIME=python", "GOOGLE_ENTRYPOINT=gunicorn -b :8080 main:app"},
			MustUse:    []string{pythonRuntime},
			MustNotUse: []string{goRuntime, javaRuntime, nodeRuntime},
		},
		{
			Name:    "python with client-side scripts correctly builds as a python app",
			App:     "python/scripts",
			Env:     []string{"GOOGLE_ENTRYPOINT=gunicorn -b :8080 main:app"},
			MustUse: []string{pythonRuntime, pythonPIP, entrypoint},
		},
	}
	// Tests for all published versions of Python.
	// Unlike with the other languages, we control the versions published to GCS.
	for _, v := range []string{
		"3.7.0",
		"3.7.1",
		"3.7.2",
		"3.7.3",
		"3.7.4",
		"3.7.5",
		"3.7.6",
		"3.7.7",
		"3.7.8",
		"3.7.9",
		"3.7.10",
		"3.8.0",
		"3.8.1",
		"3.8.2",
		"3.8.3",
		"3.8.4",
		"3.8.5",
		"3.8.6",
		"3.8.7",
		"3.8.8",
		"3.8.9",
		"3.8.10",
		"3.9.0",
		"3.9.1",
		"3.9.2",
		// 3.9.3 is not currently available.
		"3.9.4",
		"3.9.5",
	} {
		testCases = append(testCases, acceptance.Test{
			Name:    "runtime version " + v,
			App:     "python/version",
			Path:    "/version?want=" + v,
			Env:     []string{"GOOGLE_RUNTIME_VERSION=" + v},
			MustUse: []string{pythonRuntime, pythonPIP, entrypoint},
		})
	}
	for _, tc := range testCases {
		tc := tc
		t.Run(tc.Name, func(t *testing.T) {
			t.Parallel()

			acceptance.TestApp(t, builder, tc)
		})
	}
}

func TestFailuresPython(t *testing.T) {
	builder, cleanup := acceptance.CreateBuilder(t)
	t.Cleanup(cleanup)

	testCases := []acceptance.FailureTest{
		{
			Name:      "bad runtime version",
			App:       "python/simple",
			Env:       []string{"GOOGLE_RUNTIME_VERSION=BAD_NEWS_BEARS", "GOOGLE_ENTRYPOINT=gunicorn -b :8080 main:app"},
			MustMatch: "Runtime version BAD_NEWS_BEARS does not exist",
		},
		{
			Name:      "python-version empty",
			App:       "python/empty_version",
			MustMatch: ".python-version exists but does not specify a version",
		},
		{
			Name:      "missing entrypoint",
			App:       "python/missing_entrypoint",
			MustMatch: `for Python, an entrypoint must be manually set, either with "GOOGLE_ENTRYPOINT" env var or by creating a "Procfile" file`,
		},
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.Name, func(t *testing.T) {
			t.Parallel()

			acceptance.TestBuildFailure(t, builder, tc)
		})
	}
}
<commit_msg>Sync python runtime buildpack acceptance tests with most up to date published versions.<commit_after>// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package acceptance

import (
	"testing"

	"github.com/GoogleCloudPlatform/buildpacks/internal/acceptance"
)

func init() {
	acceptance.DefineFlags()
}

func TestAcceptancePython(t *testing.T) {
	builder, cleanup := acceptance.CreateBuilder(t)
	t.Cleanup(cleanup)

	testCases := []acceptance.Test{
		{
			Name:    "entrypoint from procfile web",
			App:     "python/simple",
			MustUse: []string{pythonRuntime, pythonPIP, entrypoint},
		},
		{
			Name:       "entrypoint from procfile custom",
			App:        "python/simple",
			Path:       "/custom",
			Entrypoint: "custom", // Must match the non-web process in Procfile.
			MustUse:    []string{pythonRuntime, pythonPIP, entrypoint},
		},
		{
			Name:    "entrypoint from env",
			App:     "python/simple",
			Path:    "/custom",
			Env:     []string{"GOOGLE_ENTRYPOINT=gunicorn -b :8080 custom:app"},
			MustUse: []string{pythonRuntime, pythonPIP, entrypoint},
		},
		{
			Name:    "entrypoint with env var",
			App:     "python/simple",
			Path:    "/env?want=bar",
			Env:     []string{"GOOGLE_ENTRYPOINT=FOO=bar gunicorn -b :8080 main:app"},
			MustUse: []string{pythonRuntime, pythonPIP, entrypoint},
		},
		{
			Name:    "runtime version from env",
			App:     "python/version",
			Path:    "/version?want=3.8.0",
			Env:     []string{"GOOGLE_RUNTIME_VERSION=3.8.0"},
			MustUse: []string{pythonRuntime, pythonPIP, entrypoint},
		},
		{
			Name:    "runtime version from .python-version",
			App:     "python/version",
			Path:    "/version?want=3.8.1",
			MustUse: []string{pythonRuntime, pythonPIP, entrypoint},
		},
		{
			Name:       "selected via GOOGLE_RUNTIME",
			App:        "override",
			Env:        []string{"GOOGLE_RUNTIME=python", "GOOGLE_ENTRYPOINT=gunicorn -b :8080 main:app"},
			MustUse:    []string{pythonRuntime},
			MustNotUse: []string{goRuntime, javaRuntime, nodeRuntime},
		},
		{
			Name:    "python with client-side scripts correctly builds as a python app",
			App:     "python/scripts",
			Env:     []string{"GOOGLE_ENTRYPOINT=gunicorn -b :8080 main:app"},
			MustUse: []string{pythonRuntime, pythonPIP, entrypoint},
		},
	}
	// Tests for two most recent published patch versions of Python.
	// Unlike with the other languages, we control the versions published to GCS.
	for _, v := range []string{
		"3.7.11",
		"3.7.12",
		"3.8.11",
		"3.8.12",
		"3.9.6",
		"3.9.7",
		"3.9.8",
	} {
		testCases = append(testCases, acceptance.Test{
			Name:    "runtime version " + v,
			App:     "python/version",
			Path:    "/version?want=" + v,
			Env:     []string{"GOOGLE_RUNTIME_VERSION=" + v},
			MustUse: []string{pythonRuntime, pythonPIP, entrypoint},
		})
	}
	for _, tc := range testCases {
		tc := tc
		t.Run(tc.Name, func(t *testing.T) {
			t.Parallel()

			acceptance.TestApp(t, builder, tc)
		})
	}
}

func TestFailuresPython(t *testing.T) {
	builder, cleanup := acceptance.CreateBuilder(t)
	t.Cleanup(cleanup)

	testCases := []acceptance.FailureTest{
		{
			Name:      "bad runtime version",
			App:       "python/simple",
			Env:       []string{"GOOGLE_RUNTIME_VERSION=BAD_NEWS_BEARS", "GOOGLE_ENTRYPOINT=gunicorn -b :8080 main:app"},
			MustMatch: "Runtime version BAD_NEWS_BEARS does not exist",
		},
		{
			Name:      "python-version empty",
			App:       "python/empty_version",
			MustMatch: ".python-version exists but does not specify a version",
		},
		{
			Name:      "missing entrypoint",
			App:       "python/missing_entrypoint",
			MustMatch: `for Python, an entrypoint must be manually set, either with "GOOGLE_ENTRYPOINT" env var or by creating a "Procfile" file`,
		},
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.Name, func(t *testing.T) {
			t.Parallel()

			acceptance.TestBuildFailure(t, builder, tc)
		})
	}
}
<|endoftext|>