<commit_before>package couchdb

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strings"

	"github.com/cozy/cozy-stack/pkg/config"
	"github.com/cozy/cozy-stack/pkg/realtime"
	"github.com/labstack/echo"
)

// Proxy generate a httputil.ReverseProxy which forwards the request to the
// correct route.
func Proxy(db Database, doctype, path string) *httputil.ReverseProxy {
	couchURL := config.CouchURL()
	couchAuth := config.GetConfig().CouchDB.Auth

	director := func(req *http.Request) {
		req.URL.Scheme = couchURL.Scheme
		req.URL.Host = couchURL.Host
		req.Header.Del(echo.HeaderAuthorization) // drop stack auth
		req.Header.Del(echo.HeaderCookie)
		req.URL.RawPath = "/" + makeDBName(db, doctype) + "/" + path
		req.URL.Path, _ = url.PathUnescape(req.URL.RawPath)
		if couchAuth != nil {
			if p, ok := couchAuth.Password(); ok {
				req.SetBasicAuth(couchAuth.Username(), p)
			}
		}
	}

	return &httputil.ReverseProxy{
		Director: director,
	}
}

// ProxyBulkDocs generates a httputil.ReverseProxy to forward the couchdb
// request on the _bulk_docs endpoint. This endpoint is specific since it will
// mutate many document in database, the stack has to read the response from
// couch to emit the correct realtime events.
func ProxyBulkDocs(db Database, doctype string, req *http.Request) (*httputil.ReverseProxy, *http.Request, error) {
	body, err := ioutil.ReadAll(req.Body)
	if err != nil {
		return nil, nil, err
	}

	var reqValue struct {
		Docs     []JSONDoc `json:"docs"`
		NewEdits *bool     `json:"new_edits"`
	}

	if err = json.Unmarshal(body, &reqValue); err != nil {
		return nil, nil, echo.NewHTTPError(http.StatusBadRequest,
			"request body is not valid JSON")
	}

	docs := make(map[string]JSONDoc)
	for _, d := range reqValue.Docs {
		docs[d.ID()] = d
	}

	// reset body to proxy
	req.Body = ioutil.NopCloser(bytes.NewReader(body))

	p := Proxy(db, doctype, "/_bulk_docs")
	p.Transport = &bulkTransport{
		RoundTripper: http.DefaultTransport,
		OnResponseRead: func(data []byte) {
			type respValue struct {
				ID    string `json:"id"`
				Rev   string `json:"rev"`
				OK    bool   `json:"ok"`
				Error string `json:"error"`
			}

			// When using the 'new_edits' flag (like pouchdb), the couchdb response
			// does not contain any value. We only rely on the request data and
			// expect no error.
			if reqValue.NewEdits != nil && !*reqValue.NewEdits {
				for _, doc := range reqValue.Docs {
					rev := doc.Rev()
					var event string
					if strings.HasPrefix(rev, "1-") {
						event = realtime.EventCreate
					} else {
						event = realtime.EventUpdate
					}
					rtevent(db, event, doc, nil)
				}
			} else {
				var respValues []*respValue
				if err = json.Unmarshal(data, &respValues); err != nil {
					return
				}

				docs := make(map[string]JSONDoc)
				for _, doc := range reqValue.Docs {
					docs[doc.ID()] = doc
				}

				for _, r := range respValues {
					if r.Error != "" || !r.OK {
						continue
					}
					doc, ok := docs[r.ID]
					if !ok {
						continue
					}
					var event string
					if doc.Rev() == "" {
						event = realtime.EventCreate
					} else {
						event = realtime.EventUpdate
					}
					doc.SetRev(r.Rev)
					rtevent(db, event, doc, nil)
				}
			}
		},
	}

	return p, req, nil
}

type bulkTransport struct {
	http.RoundTripper
	OnResponseRead func([]byte)
}

func (t *bulkTransport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
	resp, err = t.RoundTripper.RoundTrip(req)
	if err != nil {
		return nil, err
	}
	defer func() {
		if errc := resp.Body.Close(); err == nil && errc != nil {
			err = errc
		}
	}()
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode == http.StatusCreated {
		go t.OnResponseRead(b)
	}
	resp.Body = ioutil.NopCloser(bytes.NewReader(b))
	return resp, nil
}
<commit_msg>feat: event delete is correctly broadcasted<commit_after>package couchdb

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strings"

	"github.com/cozy/cozy-stack/pkg/config"
	"github.com/cozy/cozy-stack/pkg/realtime"
	"github.com/labstack/echo"
)

// Proxy generate a httputil.ReverseProxy which forwards the request to the
// correct route.
func Proxy(db Database, doctype, path string) *httputil.ReverseProxy {
	couchURL := config.CouchURL()
	couchAuth := config.GetConfig().CouchDB.Auth

	director := func(req *http.Request) {
		req.URL.Scheme = couchURL.Scheme
		req.URL.Host = couchURL.Host
		req.Header.Del(echo.HeaderAuthorization) // drop stack auth
		req.Header.Del(echo.HeaderCookie)
		req.URL.RawPath = "/" + makeDBName(db, doctype) + "/" + path
		req.URL.Path, _ = url.PathUnescape(req.URL.RawPath)
		if couchAuth != nil {
			if p, ok := couchAuth.Password(); ok {
				req.SetBasicAuth(couchAuth.Username(), p)
			}
		}
	}

	return &httputil.ReverseProxy{
		Director: director,
	}
}

// ProxyBulkDocs generates a httputil.ReverseProxy to forward the couchdb
// request on the _bulk_docs endpoint. This endpoint is specific since it will
// mutate many document in database, the stack has to read the response from
// couch to emit the correct realtime events.
func ProxyBulkDocs(db Database, doctype string, req *http.Request) (*httputil.ReverseProxy, *http.Request, error) {
	body, err := ioutil.ReadAll(req.Body)
	if err != nil {
		return nil, nil, err
	}

	var reqValue struct {
		Docs     []JSONDoc `json:"docs"`
		NewEdits *bool     `json:"new_edits"`
	}

	if err = json.Unmarshal(body, &reqValue); err != nil {
		return nil, nil, echo.NewHTTPError(http.StatusBadRequest,
			"request body is not valid JSON")
	}

	docs := make(map[string]JSONDoc)
	for _, d := range reqValue.Docs {
		docs[d.ID()] = d
	}

	// reset body to proxy
	req.Body = ioutil.NopCloser(bytes.NewReader(body))

	p := Proxy(db, doctype, "/_bulk_docs")
	p.Transport = &bulkTransport{
		RoundTripper: http.DefaultTransport,
		OnResponseRead: func(data []byte) {
			type respValue struct {
				ID    string `json:"id"`
				Rev   string `json:"rev"`
				OK    bool   `json:"ok"`
				Error string `json:"error"`
			}

			// When using the 'new_edits' flag (like pouchdb), the couchdb response
			// does not contain any value. We only rely on the request data and
			// expect no error.
			if reqValue.NewEdits != nil && !*reqValue.NewEdits {
				for _, doc := range reqValue.Docs {
					rev := doc.Rev()
					var event string
					if strings.HasPrefix(rev, "1-") {
						event = realtime.EventCreate
					} else {
						event = realtime.EventUpdate
					}
					rtevent(db, event, doc, nil)
				}
			} else {
				var respValues []*respValue
				if err = json.Unmarshal(data, &respValues); err != nil {
					return
				}

				docs := make(map[string]JSONDoc)
				for _, doc := range reqValue.Docs {
					docs[doc.ID()] = doc
				}

				for _, r := range respValues {
					if r.Error != "" || !r.OK {
						continue
					}
					doc, ok := docs[r.ID]
					if !ok {
						continue
					}
					var event string
					if doc.Rev() == "" {
						event = realtime.EventCreate
					} else if doc.Get("_deleted") == true {
						event = realtime.EventDelete
					} else {
						event = realtime.EventUpdate
					}
					doc.SetRev(r.Rev)
					rtevent(db, event, doc, nil)
				}
			}
		},
	}

	return p, req, nil
}

type bulkTransport struct {
	http.RoundTripper
	OnResponseRead func([]byte)
}

func (t *bulkTransport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
	resp, err = t.RoundTripper.RoundTrip(req)
	if err != nil {
		return nil, err
	}
	defer func() {
		if errc := resp.Body.Close(); err == nil && errc != nil {
			err = errc
		}
	}()
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode == http.StatusCreated {
		go t.OnResponseRead(b)
	}
	resp.Body = ioutil.NopCloser(bytes.NewReader(b))
	return resp, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2014 The Kubernetes Authors.

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

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

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

package labels

import (
	"fmt"
	"sort"
	"strings"
)

// Labels allows you to present labels independently from their storage.
type Labels interface {
	// Has returns whether the provided label exists.
	Has(label string) (exists bool)

	// Get returns the value for the provided label.
	Get(label string) (value string)
}

// Set is a map of label:value. It implements Labels.
type Set map[string]string

// String returns all labels listed as a human readable string.
// Conveniently, exactly the format that ParseSelector takes.
func (ls Set) String() string {
	selector := make([]string, 0, len(ls))
	for key, value := range ls {
		selector = append(selector, key+"="+value)
	}
	// Sort for determinism.
	sort.StringSlice(selector).Sort()
	return strings.Join(selector, ",")
}

// Has returns whether the provided label exists in the map.
func (ls Set) Has(label string) bool {
	_, exists := ls[label]
	return exists
}

// Get returns the value in the map for the provided label.
func (ls Set) Get(label string) string {
	return ls[label]
}

// AsSelector converts labels into a selectors. It does not
// perform any validation, which means the server will reject
// the request if the Set contains invalid values.
func (ls Set) AsSelector() Selector {
	return SelectorFromSet(ls)
}

// AsValidatedSelector converts labels into a selectors.
// The Set is validated client-side, which allows to catch errors early.
func (ls Set) AsValidatedSelector() (Selector, error) {
	return ValidatedSelectorFromSet(ls)
}

// AsSelectorPreValidated converts labels into a selector, but
// assumes that labels are already validated and thus doesn't
// perform any validation.
// According to our measurements this is significantly faster
// in codepaths that matter at high scale.
func (ls Set) AsSelectorPreValidated() Selector {
	return SelectorFromValidatedSet(ls)
}

// FormatLabels convert label map into plain string
func FormatLabels(labelMap map[string]string) string {
	l := Set(labelMap).String()
	if l == "" {
		l = "<none>"
	}
	return l
}

// Conflicts takes 2 maps and returns true if there a key match between
// the maps but the value doesn't match, and returns false in other cases
func Conflicts(labels1, labels2 Set) bool {
	small := labels1
	big := labels2
	if len(labels2) < len(labels1) {
		small = labels2
		big = labels1
	}

	for k, v := range small {
		if val, match := big[k]; match {
			if val != v {
				return true
			}
		}
	}

	return false
}

// Merge combines given maps, and does not check for any conflicts
// between the maps. In case of conflicts, second map (labels2) wins
func Merge(labels1, labels2 Set) Set {
	mergedMap := Set{}

	for k, v := range labels1 {
		mergedMap[k] = v
	}
	for k, v := range labels2 {
		mergedMap[k] = v
	}
	return mergedMap
}

// Equals returns true if the given maps are equal
func Equals(labels1, labels2 Set) bool {
	if len(labels1) != len(labels2) {
		return false
	}

	for k, v := range labels1 {
		value, ok := labels2[k]
		if !ok {
			return false
		}
		if value != v {
			return false
		}
	}
	return true
}

// AreLabelsInWhiteList verifies if the provided label list
// is in the provided whitelist and returns true, otherwise false.
func AreLabelsInWhiteList(labels, whitelist Set) bool {
	if len(whitelist) == 0 {
		return true
	}

	for k, v := range labels {
		value, ok := whitelist[k]
		if !ok {
			return false
		}
		if value != v {
			return false
		}
	}
	return true
}

// ConvertSelectorToLabelsMap converts selector string to labels map
// and validates keys and values
func ConvertSelectorToLabelsMap(selector string) (Set, error) {
	labelsMap := Set{}

	if len(selector) == 0 {
		return labelsMap, nil
	}

	labels := strings.Split(selector, ",")
	for _, label := range labels {
		l := strings.Split(label, "=")
		if len(l) != 2 {
			return labelsMap, fmt.Errorf("invalid selector: %s", l)
		}
		key := strings.TrimSpace(l[0])
		if err := validateLabelKey(key); err != nil {
			return labelsMap, err
		}
		value := strings.TrimSpace(l[1])
		if err := validateLabelValue(key, value); err != nil {
			return labelsMap, err
		}
		labelsMap[key] = value
	}
	return labelsMap, nil
}
<commit_msg>Replace AreLabelsInWhiteList with isSubset<commit_after>/*
Copyright 2014 The Kubernetes Authors.

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

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

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

package labels

import (
	"fmt"
	"sort"
	"strings"
)

// Labels allows you to present labels independently from their storage.
type Labels interface {
	// Has returns whether the provided label exists.
	Has(label string) (exists bool)

	// Get returns the value for the provided label.
	Get(label string) (value string)
}

// Set is a map of label:value. It implements Labels.
type Set map[string]string

// String returns all labels listed as a human readable string.
// Conveniently, exactly the format that ParseSelector takes.
func (ls Set) String() string {
	selector := make([]string, 0, len(ls))
	for key, value := range ls {
		selector = append(selector, key+"="+value)
	}
	// Sort for determinism.
	sort.StringSlice(selector).Sort()
	return strings.Join(selector, ",")
}

// Has returns whether the provided label exists in the map.
func (ls Set) Has(label string) bool {
	_, exists := ls[label]
	return exists
}

// Get returns the value in the map for the provided label.
func (ls Set) Get(label string) string {
	return ls[label]
}

// AsSelector converts labels into a selectors. It does not
// perform any validation, which means the server will reject
// the request if the Set contains invalid values.
func (ls Set) AsSelector() Selector {
	return SelectorFromSet(ls)
}

// AsValidatedSelector converts labels into a selectors.
// The Set is validated client-side, which allows to catch errors early.
func (ls Set) AsValidatedSelector() (Selector, error) {
	return ValidatedSelectorFromSet(ls)
}

// AsSelectorPreValidated converts labels into a selector, but
// assumes that labels are already validated and thus doesn't
// perform any validation.
// According to our measurements this is significantly faster
// in codepaths that matter at high scale.
func (ls Set) AsSelectorPreValidated() Selector {
	return SelectorFromValidatedSet(ls)
}

// FormatLabels convert label map into plain string
func FormatLabels(labelMap map[string]string) string {
	l := Set(labelMap).String()
	if l == "" {
		l = "<none>"
	}
	return l
}

// Conflicts takes 2 maps and returns true if there a key match between
// the maps but the value doesn't match, and returns false in other cases
func Conflicts(labels1, labels2 Set) bool {
	small := labels1
	big := labels2
	if len(labels2) < len(labels1) {
		small = labels2
		big = labels1
	}

	for k, v := range small {
		if val, match := big[k]; match {
			if val != v {
				return true
			}
		}
	}

	return false
}

// Merge combines given maps, and does not check for any conflicts
// between the maps. In case of conflicts, second map (labels2) wins
func Merge(labels1, labels2 Set) Set {
	mergedMap := Set{}

	for k, v := range labels1 {
		mergedMap[k] = v
	}
	for k, v := range labels2 {
		mergedMap[k] = v
	}
	return mergedMap
}

// Equals returns true if the given maps are equal
func Equals(labels1, labels2 Set) bool {
	if len(labels1) != len(labels2) {
		return false
	}

	for k, v := range labels1 {
		value, ok := labels2[k]
		if !ok {
			return false
		}
		if value != v {
			return false
		}
	}
	return true
}

// ConvertSelectorToLabelsMap converts selector string to labels map
// and validates keys and values
func ConvertSelectorToLabelsMap(selector string) (Set, error) {
	labelsMap := Set{}

	if len(selector) == 0 {
		return labelsMap, nil
	}

	labels := strings.Split(selector, ",")
	for _, label := range labels {
		l := strings.Split(label, "=")
		if len(l) != 2 {
			return labelsMap, fmt.Errorf("invalid selector: %s", l)
		}
		key := strings.TrimSpace(l[0])
		if err := validateLabelKey(key); err != nil {
			return labelsMap, err
		}
		value := strings.TrimSpace(l[1])
		if err := validateLabelValue(key, value); err != nil {
			return labelsMap, err
		}
		labelsMap[key] = value
	}
	return labelsMap, nil
}
<|endoftext|>


<commit_before>package nginx

import (
	"github.com/dynport/urknall"
	"github.com/dynport/urknall/cmd"
	"github.com/dynport/urknall/utils"
)

func New(version string) *Package {
	return &Package{Version: version}
}

type Package struct {
	Version            string `urknall:"default=1.4.4"`
	HeadersMoreVersion string `urknall:"default=0.24"`
	SyslogPatchVersion string `urknall:"default=1.3.14"`
}

func (pkg *Package) Package(r *urknall.Runlist) {
	srcDir := "/opt/src/nginx-" + pkg.Version
	syslogPatchPath := "/tmp/nginx_syslog_patch"
	fileName := "syslog_{{ .SyslogPatchVersion }}.patch"
	r.Add(
		cmd.InstallPackages("unzip", "libpcre3", "libpcre3-dev", "libssl-dev", "libpcrecpp0", "zlib1g-dev", "libgd2-xpm-dev"),
		cmd.DownloadToFile(pkg.url(), "/opt/src", "root", 0644),
		cmd.Mkdir(syslogPatchPath, "root", 0755),
		cmd.DownloadToFile("https://raw.github.com/yaoweibin/nginx_syslog_patch/master/config", syslogPatchPath+"/config", "root", 0644),
		cmd.DownloadToFile("https://raw.github.com/yaoweibin/nginx_syslog_patch/master/"+fileName, syslogPatchPath+"/"+fileName, "root", 0644),
		cmd.And(
			"rm -Rf "+srcDir,
			"mkdir -p /opt/src",
			"cd /opt/src",
			"tar xfz "+pkg.fileName(),
		),
		cmd.And(
			"cd /opt/src/nginx-{{ .Version }}",
			"patch -p1 < "+syslogPatchPath+"/"+fileName,
		),
		cmd.DownloadToFile("https://github.com/agentzh/headers-more-nginx-module/archive/v{{ .HeadersMoreVersion }}.tar.gz", "/opt/src/headers-more-nginx-module-{{ .HeadersMoreVersion }}.tar.gz", "root", 0644),
		cmd.And(
			"cd /opt/src",
			"tar xvfz headers-more-nginx-module-{{ .HeadersMoreVersion }}.tar.gz",
		),
		cmd.And(
			"cd /opt/src/nginx-{{ .Version }}",
			"./configure --with-http_ssl_module --with-http_gzip_static_module --with-http_stub_status_module --with-http_spdy_module --add-module=/tmp/nginx_syslog_patch --add-module=/opt/src/headers-more-nginx-module-{{ .HeadersMoreVersion }} --prefix=/opt/nginx-{{ .Version }}",
			"make",
			"make install",
		),
		cmd.WriteFile("/etc/init/nginx.conf", utils.MustRenderTemplate(upstartScript, pkg), "root", 0644),
	)
}

func (pkg *Package) WriteConfigCommand(b []byte) cmd.Command {
	return cmd.WriteFile(pkg.InstallPath(), string(b), "root", 0644)
}

const upstartScript = `# nginx

description "nginx http daemon"
author "George Shammas <georgyo@gmail.com>"

start on (filesystem and net-device-up IFACE=lo)
stop on runlevel [!2345]

env DAEMON={{ .InstallPath }}/sbin/nginx
env PID=/var/run/nginx.pid

expect fork
respawn
respawn limit 10 5
#oom never

pre-start script
        $DAEMON -t
        if [ $? -ne 0 ]
                then exit $?
        fi
end script

exec $DAEMON
`

func (pkg *Package) url() string {
	return "http://nginx.org/download/" + pkg.fileName()
}

func (pkg *Package) InstallPath() string {
	return "/opt/nginx-" + pkg.Version
}

func (pkg *Package) fileName() string {
	return pkg.name() + ".tar.gz"
}

func (pkg *Package) name() string {
	return "nginx-" + pkg.Version
}
<commit_msg>install curl instead of unzip (we use tar.gz archives now)<commit_after>package nginx

import (
	"github.com/dynport/urknall"
	"github.com/dynport/urknall/cmd"
	"github.com/dynport/urknall/utils"
)

func New(version string) *Package {
	return &Package{Version: version}
}

type Package struct {
	Version            string `urknall:"default=1.4.4"`
	HeadersMoreVersion string `urknall:"default=0.24"`
	SyslogPatchVersion string `urknall:"default=1.3.14"`
}

func (pkg *Package) Package(r *urknall.Runlist) {
	srcDir := "/opt/src/nginx-" + pkg.Version
	syslogPatchPath := "/tmp/nginx_syslog_patch"
	fileName := "syslog_{{ .SyslogPatchVersion }}.patch"
	r.Add(
		cmd.InstallPackages("curl", "libpcre3", "libpcre3-dev", "libssl-dev", "libpcrecpp0", "zlib1g-dev", "libgd2-xpm-dev"),
		cmd.DownloadToFile(pkg.url(), "/opt/src", "root", 0644),
		cmd.Mkdir(syslogPatchPath, "root", 0755),
		cmd.DownloadToFile("https://raw.github.com/yaoweibin/nginx_syslog_patch/master/config", syslogPatchPath+"/config", "root", 0644),
		cmd.DownloadToFile("https://raw.github.com/yaoweibin/nginx_syslog_patch/master/"+fileName, syslogPatchPath+"/"+fileName, "root", 0644),
		cmd.And(
			"rm -Rf "+srcDir,
			"mkdir -p /opt/src",
			"cd /opt/src",
			"tar xfz "+pkg.fileName(),
		),
		cmd.And(
			"cd /opt/src/nginx-{{ .Version }}",
			"patch -p1 < "+syslogPatchPath+"/"+fileName,
		),
		cmd.DownloadToFile("https://github.com/agentzh/headers-more-nginx-module/archive/v{{ .HeadersMoreVersion }}.tar.gz", "/opt/src/headers-more-nginx-module-{{ .HeadersMoreVersion }}.tar.gz", "root", 0644),
		cmd.And(
			"cd /opt/src",
			"tar xvfz headers-more-nginx-module-{{ .HeadersMoreVersion }}.tar.gz",
		),
		cmd.And(
			"cd /opt/src/nginx-{{ .Version }}",
			"./configure --with-http_ssl_module --with-http_gzip_static_module --with-http_stub_status_module --with-http_spdy_module --add-module=/tmp/nginx_syslog_patch --add-module=/opt/src/headers-more-nginx-module-{{ .HeadersMoreVersion }} --prefix=/opt/nginx-{{ .Version }}",
			"make",
			"make install",
		),
		cmd.WriteFile("/etc/init/nginx.conf", utils.MustRenderTemplate(upstartScript, pkg), "root", 0644),
	)
}

func (pkg *Package) WriteConfigCommand(b []byte) cmd.Command {
	return cmd.WriteFile(pkg.InstallPath(), string(b), "root", 0644)
}

const upstartScript = `# nginx

description "nginx http daemon"
author "George Shammas <georgyo@gmail.com>"

start on (filesystem and net-device-up IFACE=lo)
stop on runlevel [!2345]

env DAEMON={{ .InstallPath }}/sbin/nginx
env PID=/var/run/nginx.pid

expect fork
respawn
respawn limit 10 5
#oom never

pre-start script
        $DAEMON -t
        if [ $? -ne 0 ]
                then exit $?
        fi
end script

exec $DAEMON
`

func (pkg *Package) url() string {
	return "http://nginx.org/download/" + pkg.fileName()
}

func (pkg *Package) InstallPath() string {
	return "/opt/nginx-" + pkg.Version
}

func (pkg *Package) fileName() string {
	return pkg.name() + ".tar.gz"
}

func (pkg *Package) name() string {
	return "nginx-" + pkg.Version
}
<|endoftext|>


<commit_before>// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Cilium

package option

import (
	"fmt"
	"sort"
	"strings"

	"github.com/cilium/cilium/api/v1/models"
	"github.com/cilium/cilium/pkg/lock"
)

// VerifyFunc validates option key with value and may return an error if the
// option should not be applied
type VerifyFunc func(key string, value string) error

// ParseFunc parses the option value and may return an error if the option
// cannot be parsed or applied.
type ParseFunc func(value string) (OptionSetting, error)

// FormatFunc formats the specified value as textual representation option.
type FormatFunc func(value OptionSetting) string

// Option is the structure used to specify the semantics of a configurable
// boolean option
type Option struct {
	// Define is the name of the #define used for BPF programs
	Define string
	// Description is a short human readable description
	Description string
	// Immutable marks an option which is read-only
	Immutable bool
	// Requires is a list of required options, such options will be
	// automatically enabled as required.
	Requires []string
	// Parse is called to parse the option. If not specified, defaults to
	// NormalizeBool().
	Parse ParseFunc
	// FormatFunc is called to format the value for an option. If not
	// specified, defaults to formatting 0 as "Disabled" and other values
	// as "Enabled".
	Format FormatFunc
	// Verify is called prior to applying the option
	Verify VerifyFunc
}

// OptionSetting specifies the different choices each Option has.
type OptionSetting int

const (
	OptionDisabled OptionSetting = iota
	OptionEnabled
)

// RequiresOption returns true if the option requires the specified option `name`.
func (o Option) RequiresOption(name string) bool {
	for _, o := range o.Requires {
		if o == name {
			return true
		}
	}

	return false
}

type OptionLibrary map[string]*Option

func (l OptionLibrary) Lookup(name string) (string, *Option) {
	nameLower := strings.ToLower(name)

	for k := range l {
		if strings.ToLower(k) == nameLower {
			return k, l[k]
		}
	}

	return "", nil
}

func (l OptionLibrary) Define(name string) string {
	if _, ok := l[name]; ok {
		return l[name].Define
	}

	return name
}

func NormalizeBool(value string) (OptionSetting, error) {
	switch strings.ToLower(value) {
	case "true", "on", "enable", "enabled", "1":
		return OptionEnabled, nil
	case "false", "off", "disable", "disabled", "0":
		return OptionDisabled, nil
	default:
		return OptionDisabled, fmt.Errorf("invalid option value %s", value)
	}
}

// ValidateConfigurationMap validates a given configuration map based on the
// option library
func (l *OptionLibrary) ValidateConfigurationMap(n models.ConfigurationMap) (OptionMap, error) {
	o := make(OptionMap)
	for k, v := range n {
		_, newVal, err := ParseKeyValue(l, k, v)
		if err != nil {
			return nil, err
		}

		if err := l.Validate(k, v); err != nil {
			return nil, err
		}
		o[k] = newVal
	}

	return o, nil
}

func (l OptionLibrary) Validate(name string, value string) error {
	key, spec := l.Lookup(name)
	if key == "" {
		return fmt.Errorf("unknown option %s", name)
	}

	if spec.Immutable {
		return fmt.Errorf("specified option is immutable (read-only)")
	}

	if spec.Verify != nil {
		return spec.Verify(key, value)
	}

	return nil
}

type OptionMap map[string]OptionSetting

func (om OptionMap) DeepCopy() OptionMap {
	cpy := make(OptionMap, len(om))
	for k, v := range om {
		cpy[k] = v
	}
	return cpy
}

// IntOptions member functions with external access do not require
// locking by the caller, while functions with internal access presume
// the caller to have taken care of any locking needed.
type IntOptions struct {
	optsMU  lock.RWMutex   // Protects all variables from this structure below this line
	Opts    OptionMap      `json:"map"`
	Library *OptionLibrary `json:"-"`
}

// GetImmutableModel returns the set of immutable options as a ConfigurationMap API model.
func (o *IntOptions) GetImmutableModel() *models.ConfigurationMap {
	immutableCfg := make(models.ConfigurationMap)
	return &immutableCfg
}

// GetMutableModel returns the set of mutable options as a ConfigurationMap API model.
func (o *IntOptions) GetMutableModel() *models.ConfigurationMap {
	mutableCfg := make(models.ConfigurationMap)
	o.optsMU.RLock()
	for k, v := range o.Opts {
		_, config := o.Library.Lookup(k)

		// It's possible that an option has since been removed and thus has
		// no corresponding configuration; need to check if configuration is
		// nil accordingly.
		if config != nil {
			if config.Format == nil {
				if v == OptionDisabled {
					mutableCfg[k] = "Disabled"
				} else {
					mutableCfg[k] = "Enabled"
				}
			} else {
				mutableCfg[k] = config.Format(v)
			}
		}
	}
	o.optsMU.RUnlock()

	return &mutableCfg
}

func (o *IntOptions) DeepCopy() *IntOptions {
	o.optsMU.RLock()
	cpy := &IntOptions{
		Opts:    o.Opts.DeepCopy(),
		Library: o.Library,
	}
	o.optsMU.RUnlock()
	return cpy
}

func NewIntOptions(lib *OptionLibrary) *IntOptions {
	return &IntOptions{
		Opts:    OptionMap{},
		Library: lib,
	}
}

func (o *IntOptions) getValue(key string) OptionSetting {
	value, exists := o.Opts[key]
	if !exists {
		return OptionDisabled
	}
	return value
}

func (o *IntOptions) GetValue(key string) OptionSetting {
	o.optsMU.RLock()
	v := o.getValue(key)
	o.optsMU.RUnlock()
	return v
}

func (o *IntOptions) IsEnabled(key string) bool {
	return o.GetValue(key) != OptionDisabled
}

// SetValidated sets the option `key` to the specified value. The caller is
// expected to have validated the input to this function.
func (o *IntOptions) SetValidated(key string, value OptionSetting) {
	o.optsMU.Lock()
	o.Opts[key] = value
	o.optsMU.Unlock()
}

// SetBool sets the specified option to Enabled.
func (o *IntOptions) SetBool(key string, value bool) {
	intValue := OptionDisabled
	if value {
		intValue = OptionEnabled
	}
	o.optsMU.Lock()
	o.Opts[key] = intValue
	o.optsMU.Unlock()
}

func (o *IntOptions) Delete(key string) {
	o.optsMU.Lock()
	delete(o.Opts, key)
	o.optsMU.Unlock()
}

func (o *IntOptions) SetIfUnset(key string, value OptionSetting) {
	o.optsMU.Lock()
	if _, exists := o.Opts[key]; !exists {
		o.Opts[key] = value
	}
	o.optsMU.Unlock()
}

func (o *IntOptions) InheritDefault(parent *IntOptions, key string) {
	o.optsMU.RLock()
	o.Opts[key] = parent.GetValue(key)
	o.optsMU.RUnlock()
}

func ParseOption(arg string, lib *OptionLibrary) (string, OptionSetting, error) {
	result := OptionEnabled

	if arg[0] == '!' {
		result = OptionDisabled
		arg = arg[1:]
	}

	optionSplit := strings.SplitN(arg, "=", 2)
	arg = optionSplit[0]
	if len(optionSplit) > 1 {
		if result == OptionDisabled {
			return "", OptionDisabled, fmt.Errorf("invalid boolean format")
		}

		return ParseKeyValue(lib, arg, optionSplit[1])
	}

	return "", OptionDisabled, fmt.Errorf("invalid option format")
}

func ParseKeyValue(lib *OptionLibrary, arg, value string) (string, OptionSetting, error) {
	var result OptionSetting

	key, spec := lib.Lookup(arg)
	if key == "" {
		return "", OptionDisabled, fmt.Errorf("unknown option %q", arg)
	}

	var err error
	if spec.Parse != nil {
		result, err = spec.Parse(value)
	} else {
		result, err = NormalizeBool(value)
	}
	if err != nil {
		return "", OptionDisabled, err
	}

	if spec.Immutable {
		return "", OptionDisabled, fmt.Errorf("specified option is immutable (read-only)")
	}

	return key, result, nil
}

// getFmtOpt returns #define name if option exists and is set to true in endpoint's Opts
// map or #undef name if option does not exist or exists but is set to false
func (o *IntOptions) getFmtOpt(name string) string {
	define := o.Library.Define(name)
	if define == "" {
		return ""
	}

	value := o.getValue(name)
	if value != OptionDisabled {
		return fmt.Sprintf("#define %s %d", o.Library.Define(name), value)
	}
	return "#undef " + o.Library.Define(name)
}

func (o *IntOptions) GetFmtList() string {
	txt := ""

	o.optsMU.RLock()
	opts := []string{}
	for k := range o.Opts {
		opts = append(opts, k)
	}
	sort.Strings(opts)

	for _, k := range opts {
		def := o.getFmtOpt(k)
		if def != "" {
			txt += def + "\n"
		}
	}
	o.optsMU.RUnlock()

	return txt
}

func (o *IntOptions) Dump() {
	if o == nil {
		return
	}

	o.optsMU.RLock()
	opts := []string{}
	for k := range o.Opts {
		opts = append(opts, k)
	}
	sort.Strings(opts)

	for _, k := range opts {
		var text string
		_, option := o.Library.Lookup(k)
		if option == nil || option.Format == nil {
			if o.Opts[k] == OptionDisabled {
				text = "Disabled"
			} else {
				text = "Enabled"
			}
		} else {
			text = option.Format(o.Opts[k])
		}

		fmt.Printf("%-24s %s\n", k, text)
	}
	o.optsMU.RUnlock()
}

// Validate validates a given configuration map based on the option library
func (o *IntOptions) Validate(n models.ConfigurationMap) error {
	o.optsMU.RLock()
	defer o.optsMU.RUnlock()
	for k, v := range n {
		_, newVal, err := ParseKeyValue(o.Library, k, v)
		if err != nil {
			return err
		}

		// Ignore validation if value is identical
		if oldVal, ok := o.Opts[k]; ok && oldVal == newVal {
			continue
		}

		if err := o.Library.Validate(k, v); err != nil {
			return err
		}
	}

	return nil
}

// ChangedFunc is called by `Apply()` for each option changed
type ChangedFunc func(key string, value OptionSetting, data interface{})

// enable enables the option `name` with all its dependencies
func (o *IntOptions) enable(name string) {
	if o.Library != nil {
		if _, opt := o.Library.Lookup(name); opt != nil {
			for _, dependency := range opt.Requires {
				o.enable(dependency)
			}
		}
	}

	o.Opts[name] = OptionEnabled
}

// set enables the option `name` with all its dependencies, and sets the
// integer level of the option to `value`.
func (o *IntOptions) set(name string, value OptionSetting) {
	o.enable(name)
	o.Opts[name] = value
}

// disable disables the option `name`. All options which depend on the option
// to be disabled will be disabled. Options which have previously been enabled
// as a dependency will not be automatically disabled.
func (o *IntOptions) disable(name string) {
	o.Opts[name] = OptionDisabled

	if o.Library != nil {
		// Disable all options which have a dependency on the option
		// that was just disabled
		for key, opt := range *o.Library {
			if opt.RequiresOption(name) && o.Opts[key] != OptionDisabled {
				o.disable(key)
			}
		}
	}
}

type changedOptions struct {
	key   string
	value OptionSetting
}

// ApplyValidated takes a configuration map and applies the changes. For an
// option which is changed, the `ChangedFunc` function is called with the
// `data` argument passed in as well. Returns the number of options changed if
// any.
//
// The caller is expected to have validated the configuration options prior to
// calling this function.
func (o *IntOptions) ApplyValidated(n OptionMap, changed ChangedFunc, data interface{}) int {
	changes := make([]changedOptions, 0, len(n))

	o.optsMU.Lock()
	for k, optVal := range n {
		val, ok := o.Opts[k]

		if optVal == OptionDisabled {
			/* Only disable if enabled already */
			if ok && val != OptionDisabled {
				o.disable(k)
				changes = append(changes, changedOptions{key: k, value: optVal})
			}
		} else {
			/* Only enable if not enabled already */
			if !ok || val == OptionDisabled {
				o.set(k, optVal)
				changes = append(changes, changedOptions{key: k, value: optVal})
			}
		}
	}
	o.optsMU.Unlock()

	for _, change := range changes {
		changed(change.key, change.value, data)
	}

	return len(changes)
}
<commit_msg>option: allocate array in advance<commit_after>// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Cilium

package option

import (
	"fmt"
	"sort"
	"strings"

	"github.com/cilium/cilium/api/v1/models"
	"github.com/cilium/cilium/pkg/lock"
)

// VerifyFunc validates option key with value and may return an error if the
// option should not be applied
type VerifyFunc func(key string, value string) error

// ParseFunc parses the option value and may return an error if the option
// cannot be parsed or applied.
type ParseFunc func(value string) (OptionSetting, error)

// FormatFunc formats the specified value as textual representation option.
type FormatFunc func(value OptionSetting) string

// Option is the structure used to specify the semantics of a configurable
// boolean option
type Option struct {
	// Define is the name of the #define used for BPF programs
	Define string
	// Description is a short human readable description
	Description string
	// Immutable marks an option which is read-only
	Immutable bool
	// Requires is a list of required options, such options will be
	// automatically enabled as required.
	Requires []string
	// Parse is called to parse the option. If not specified, defaults to
	// NormalizeBool().
	Parse ParseFunc
	// FormatFunc is called to format the value for an option. If not
	// specified, defaults to formatting 0 as "Disabled" and other values
	// as "Enabled".
	Format FormatFunc
	// Verify is called prior to applying the option
	Verify VerifyFunc
}

// OptionSetting specifies the different choices each Option has.
type OptionSetting int

const (
	OptionDisabled OptionSetting = iota
	OptionEnabled
)

// RequiresOption returns true if the option requires the specified option `name`.
func (o Option) RequiresOption(name string) bool {
	for _, o := range o.Requires {
		if o == name {
			return true
		}
	}

	return false
}

type OptionLibrary map[string]*Option

func (l OptionLibrary) Lookup(name string) (string, *Option) {
	nameLower := strings.ToLower(name)

	for k := range l {
		if strings.ToLower(k) == nameLower {
			return k, l[k]
		}
	}

	return "", nil
}

func (l OptionLibrary) Define(name string) string {
	if _, ok := l[name]; ok {
		return l[name].Define
	}

	return name
}

func NormalizeBool(value string) (OptionSetting, error) {
	switch strings.ToLower(value) {
	case "true", "on", "enable", "enabled", "1":
		return OptionEnabled, nil
	case "false", "off", "disable", "disabled", "0":
		return OptionDisabled, nil
	default:
		return OptionDisabled, fmt.Errorf("invalid option value %s", value)
	}
}

// ValidateConfigurationMap validates a given configuration map based on the
// option library
func (l *OptionLibrary) ValidateConfigurationMap(n models.ConfigurationMap) (OptionMap, error) {
	o := make(OptionMap)
	for k, v := range n {
		_, newVal, err := ParseKeyValue(l, k, v)
		if err != nil {
			return nil, err
		}

		if err := l.Validate(k, v); err != nil {
			return nil, err
		}
		o[k] = newVal
	}

	return o, nil
}

func (l OptionLibrary) Validate(name string, value string) error {
	key, spec := l.Lookup(name)
	if key == "" {
		return fmt.Errorf("unknown option %s", name)
	}

	if spec.Immutable {
		return fmt.Errorf("specified option is immutable (read-only)")
	}

	if spec.Verify != nil {
		return spec.Verify(key, value)
	}

	return nil
}

type OptionMap map[string]OptionSetting

func (om OptionMap) DeepCopy() OptionMap {
	cpy := make(OptionMap, len(om))
	for k, v := range om {
		cpy[k] = v
	}
	return cpy
}

// IntOptions member functions with external access do not require
// locking by the caller, while functions with internal access presume
// the caller to have taken care of any locking needed.
type IntOptions struct {
	optsMU  lock.RWMutex   // Protects all variables from this structure below this line
	Opts    OptionMap      `json:"map"`
	Library *OptionLibrary `json:"-"`
}

// GetImmutableModel returns the set of immutable options as a ConfigurationMap API model.
func (o *IntOptions) GetImmutableModel() *models.ConfigurationMap {
	immutableCfg := make(models.ConfigurationMap)
	return &immutableCfg
}

// GetMutableModel returns the set of mutable options as a ConfigurationMap API model.
func (o *IntOptions) GetMutableModel() *models.ConfigurationMap {
	mutableCfg := make(models.ConfigurationMap)
	o.optsMU.RLock()
	for k, v := range o.Opts {
		_, config := o.Library.Lookup(k)

		// It's possible that an option has since been removed and thus has
		// no corresponding configuration; need to check if configuration is
		// nil accordingly.
		if config != nil {
			if config.Format == nil {
				if v == OptionDisabled {
					mutableCfg[k] = "Disabled"
				} else {
					mutableCfg[k] = "Enabled"
				}
			} else {
				mutableCfg[k] = config.Format(v)
			}
		}
	}
	o.optsMU.RUnlock()

	return &mutableCfg
}

func (o *IntOptions) DeepCopy() *IntOptions {
	o.optsMU.RLock()
	cpy := &IntOptions{
		Opts:    o.Opts.DeepCopy(),
		Library: o.Library,
	}
	o.optsMU.RUnlock()
	return cpy
}

func NewIntOptions(lib *OptionLibrary) *IntOptions {
	return &IntOptions{
		Opts:    OptionMap{},
		Library: lib,
	}
}

func (o *IntOptions) getValue(key string) OptionSetting {
	value, exists := o.Opts[key]
	if !exists {
		return OptionDisabled
	}
	return value
}

func (o *IntOptions) GetValue(key string) OptionSetting {
	o.optsMU.RLock()
	v := o.getValue(key)
	o.optsMU.RUnlock()
	return v
}

func (o *IntOptions) IsEnabled(key string) bool {
	return o.GetValue(key) != OptionDisabled
}

// SetValidated sets the option `key` to the specified value. The caller is
// expected to have validated the input to this function.
func (o *IntOptions) SetValidated(key string, value OptionSetting) {
	o.optsMU.Lock()
	o.Opts[key] = value
	o.optsMU.Unlock()
}

// SetBool sets the specified option to Enabled.
func (o *IntOptions) SetBool(key string, value bool) {
	intValue := OptionDisabled
	if value {
		intValue = OptionEnabled
	}
	o.optsMU.Lock()
	o.Opts[key] = intValue
	o.optsMU.Unlock()
}

func (o *IntOptions) Delete(key string) {
	o.optsMU.Lock()
	delete(o.Opts, key)
	o.optsMU.Unlock()
}

func (o *IntOptions) SetIfUnset(key string, value OptionSetting) {
	o.optsMU.Lock()
	if _, exists := o.Opts[key]; !exists {
		o.Opts[key] = value
	}
	o.optsMU.Unlock()
}

func (o *IntOptions) InheritDefault(parent *IntOptions, key string) {
	o.optsMU.RLock()
	o.Opts[key] = parent.GetValue(key)
	o.optsMU.RUnlock()
}

func ParseOption(arg string, lib *OptionLibrary) (string, OptionSetting, error) {
	result := OptionEnabled

	if arg[0] == '!' {
		result = OptionDisabled
		arg = arg[1:]
	}

	optionSplit := strings.SplitN(arg, "=", 2)
	arg = optionSplit[0]
	if len(optionSplit) > 1 {
		if result == OptionDisabled {
			return "", OptionDisabled, fmt.Errorf("invalid boolean format")
		}

		return ParseKeyValue(lib, arg, optionSplit[1])
	}

	return "", OptionDisabled, fmt.Errorf("invalid option format")
}

func ParseKeyValue(lib *OptionLibrary, arg, value string) (string, OptionSetting, error) {
	var result OptionSetting

	key, spec := lib.Lookup(arg)
	if key == "" {
		return "", OptionDisabled, fmt.Errorf("unknown option %q", arg)
	}

	var err error
	if spec.Parse != nil {
		result, err = spec.Parse(value)
	} else {
		result, err = NormalizeBool(value)
	}
	if err != nil {
		return "", OptionDisabled, err
	}

	if spec.Immutable {
		return "", OptionDisabled, fmt.Errorf("specified option is immutable (read-only)")
	}

	return key, result, nil
}

// getFmtOpt returns #define name if option exists and is set to true in endpoint's Opts
// map or #undef name if option does not exist or exists but is set to false
func (o *IntOptions) getFmtOpt(name string) string {
	define := o.Library.Define(name)
	if define == "" {
		return ""
	}

	value := o.getValue(name)
	if value != OptionDisabled {
		return fmt.Sprintf("#define %s %d", o.Library.Define(name), value)
	}
	return "#undef " + o.Library.Define(name)
}

func (o *IntOptions) GetFmtList() string {
	txt := ""

	o.optsMU.RLock()
	opts := make([]string, 0, len(o.Opts))
	for k := range o.Opts {
		opts = append(opts, k)
	}
	sort.Strings(opts)

	for _, k := range opts {
		def := o.getFmtOpt(k)
		if def != "" {
			txt += def + "\n"
		}
	}
	o.optsMU.RUnlock()

	return txt
}

func (o *IntOptions) Dump() {
	if o == nil {
		return
	}

	o.optsMU.RLock()
	opts := make([]string, 0, len(o.Opts))
	for k := range o.Opts {
		opts = append(opts, k)
	}
	sort.Strings(opts)

	for _, k := range opts {
		var text string
		_, option := o.Library.Lookup(k)
		if option == nil || option.Format == nil {
			if o.Opts[k] == OptionDisabled {
				text = "Disabled"
			} else {
				text = "Enabled"
			}
		} else {
			text = option.Format(o.Opts[k])
		}

		fmt.Printf("%-24s %s\n", k, text)
	}
	o.optsMU.RUnlock()
}

// Validate validates a given configuration map based on the option library
func (o *IntOptions) Validate(n models.ConfigurationMap) error {
	o.optsMU.RLock()
	defer o.optsMU.RUnlock()
	for k, v := range n {
		_, newVal, err := ParseKeyValue(o.Library, k, v)
		if err != nil {
			return err
		}

		// Ignore validation if value is identical
		if oldVal, ok := o.Opts[k]; ok && oldVal == newVal {
			continue
		}

		if err := o.Library.Validate(k, v); err != nil {
			return err
		}
	}

	return nil
}

// ChangedFunc is called by `Apply()` for each option changed
type ChangedFunc func(key string, value OptionSetting, data interface{})

// enable enables the option `name` with all its dependencies
func (o *IntOptions) enable(name string) {
	if o.Library != nil {
		if _, opt := o.Library.Lookup(name); opt != nil {
			for _, dependency := range opt.Requires {
				o.enable(dependency)
			}
		}
	}

	o.Opts[name] = OptionEnabled
}

// set enables the option `name` with all its dependencies, and sets the
// integer level of the option to `value`.
func (o *IntOptions) set(name string, value OptionSetting) {
	o.enable(name)
	o.Opts[name] = value
}

// disable disables the option `name`. All options which depend on the option
// to be disabled will be disabled. Options which have previously been enabled
// as a dependency will not be automatically disabled.
func (o *IntOptions) disable(name string) {
	o.Opts[name] = OptionDisabled

	if o.Library != nil {
		// Disable all options which have a dependency on the option
		// that was just disabled
		for key, opt := range *o.Library {
			if opt.RequiresOption(name) && o.Opts[key] != OptionDisabled {
				o.disable(key)
			}
		}
	}
}

type changedOptions struct {
	key   string
	value OptionSetting
}

// ApplyValidated takes a configuration map and applies the changes. For an
// option which is changed, the `ChangedFunc` function is called with the
// `data` argument passed in as well. Returns the number of options changed if
// any.
//
// The caller is expected to have validated the configuration options prior to
// calling this function.
func (o *IntOptions) ApplyValidated(n OptionMap, changed ChangedFunc, data interface{}) int {
	changes := make([]changedOptions, 0, len(n))

	o.optsMU.Lock()
	for k, optVal := range n {
		val, ok := o.Opts[k]

		if optVal == OptionDisabled {
			/* Only disable if enabled already */
			if ok && val != OptionDisabled {
				o.disable(k)
				changes = append(changes, changedOptions{key: k, value: optVal})
			}
		} else {
			/* Only enable if not enabled already */
			if !ok || val == OptionDisabled {
				o.set(k, optVal)
				changes = append(changes, changedOptions{key: k, value: optVal})
			}
		}
	}
	o.optsMU.Unlock()

	for _, change := range changes {
		changed(change.key, change.value, data)
	}

	return len(changes)
}
<|endoftext|>


<commit_before>package api

import (
	"fmt"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
)

func TestAPI_ConfigEntry_DiscoveryChain(t *testing.T) {
	t.Parallel()
	c, s := makeClient(t)
	defer s.Stop()

	config_entries := c.ConfigEntries()

	verifyResolver := func(t *testing.T, initial ConfigEntry) {
		t.Helper()
		require.IsType(t, &ServiceResolverConfigEntry{}, initial)
		testEntry := initial.(*ServiceResolverConfigEntry)

		// set it
		_, wm, err := config_entries.Set(testEntry, nil)
		require.NoError(t, err)
		require.NotNil(t, wm)
		require.NotEqual(t, 0, wm.RequestTime)

		// get it
		entry, qm, err := config_entries.Get(ServiceResolver, testEntry.Name, nil)
		require.NoError(t, err)
		require.NotNil(t, qm)
		require.NotEqual(t, 0, qm.RequestTime)

		// generic verification
		require.Equal(t, testEntry.Meta, entry.GetMeta())

		// verify it
		readResolver, ok := entry.(*ServiceResolverConfigEntry)
		require.True(t, ok)
		readResolver.ModifyIndex = 0 // reset for Equals()
		readResolver.CreateIndex = 0 // reset for Equals()

		require.Equal(t, testEntry, readResolver)

		// TODO(rb): cas?
		// TODO(rb): list?
	}

	verifySplitter := func(t *testing.T, initial ConfigEntry) {
		t.Helper()
		require.IsType(t, &ServiceSplitterConfigEntry{}, initial)
		testEntry := initial.(*ServiceSplitterConfigEntry)

		// set it
		_, wm, err := config_entries.Set(testEntry, nil)
		require.NoError(t, err)
		require.NotNil(t, wm)
		require.NotEqual(t, 0, wm.RequestTime)

		// get it
		entry, qm, err := config_entries.Get(ServiceSplitter, testEntry.Name, nil)
		require.NoError(t, err)
		require.NotNil(t, qm)
		require.NotEqual(t, 0, qm.RequestTime)

		// generic verification
		require.Equal(t, testEntry.Meta, entry.GetMeta())

		// verify it
		readSplitter, ok := entry.(*ServiceSplitterConfigEntry)
		require.True(t, ok)
		readSplitter.ModifyIndex = 0 // reset for Equals()
		readSplitter.CreateIndex = 0 // reset for Equals()

		require.Equal(t, testEntry, readSplitter)

		// TODO(rb): cas?
		// TODO(rb): list?
	}

	verifyRouter := func(t *testing.T, initial ConfigEntry) {
		t.Helper()
		require.IsType(t, &ServiceRouterConfigEntry{}, initial)
		testEntry := initial.(*ServiceRouterConfigEntry)

		// set it
		_, wm, err := config_entries.Set(testEntry, nil)
		require.NoError(t, err)
		require.NotNil(t, wm)
		require.NotEqual(t, 0, wm.RequestTime)

		// get it
		entry, qm, err := config_entries.Get(ServiceRouter, testEntry.Name, nil)
		require.NoError(t, err)
		require.NotNil(t, qm)
		require.NotEqual(t, 0, qm.RequestTime)

		// generic verification
		require.Equal(t, testEntry.Meta, entry.GetMeta())

		// verify it
		readRouter, ok := entry.(*ServiceRouterConfigEntry)
		require.True(t, ok)
		readRouter.ModifyIndex = 0 // reset for Equals()
		readRouter.CreateIndex = 0 // reset for Equals()

		require.Equal(t, testEntry, readRouter)

		// TODO(rb): cas?
		// TODO(rb): list?
	}

	// First set the necessary protocols to allow advanced routing features.
	for _, service := range []string{
		"test-failover",
		"test-redirect",
		"alternate",
		"test-split",
		"test-route",
	} {
		serviceDefaults := &ServiceConfigEntry{
			Kind:     ServiceDefaults,
			Name:     service,
			Protocol: "http",
		}
		_, _, err := config_entries.Set(serviceDefaults, nil)
		require.NoError(t, err)
	}

	// NOTE: Due to service graph validation, these have to happen in a specific order.
	for _, tc := range []struct {
		name   string
		entry  ConfigEntry
		verify func(t *testing.T, initial ConfigEntry)
	}{
		{
			name: "failover",
			entry: &ServiceResolverConfigEntry{
				Kind:          ServiceResolver,
				Name:          "test-failover",
				Partition:     defaultPartition,
				Namespace:     defaultNamespace,
				DefaultSubset: "v1",
				Subsets: map[string]ServiceResolverSubset{
					"v1": {
						Filter: "Service.Meta.version == v1",
					},
					"v2": {
						Filter: "Service.Meta.version == v2",
					},
				},
				Failover: map[string]ServiceResolverFailover{
					"*": {
						Datacenters: []string{"dc2"},
					},
					"v1": {
						Service:   "alternate",
						Namespace: defaultNamespace,
					},
				},
				ConnectTimeout: 5 * time.Second,
				Meta: map[string]string{
					"foo": "bar",
					"gir": "zim",
				},
			},
			verify: verifyResolver,
		},
		{
			name: "redirect",
			entry: &ServiceResolverConfigEntry{
				Kind:      ServiceResolver,
				Name:      "test-redirect",
				Partition: defaultPartition,
				Namespace: defaultNamespace,
				Redirect: &ServiceResolverRedirect{
					Service:       "test-failover",
					ServiceSubset: "v2",
					Namespace:     defaultNamespace,
					Datacenter:    "d",
				},
			},
			verify: verifyResolver,
		},
		{
			name: "mega splitter", // use one mega object to avoid multiple trips
			entry: &ServiceSplitterConfigEntry{
				Kind:      ServiceSplitter,
				Name:      "test-split",
				Partition: defaultPartition,
				Namespace: defaultNamespace,
				Splits: []ServiceSplit{
					{
						Weight:        90,
						Service:       "test-failover",
						ServiceSubset: "v1",
						Namespace:     defaultNamespace,
						RequestHeaders: &HTTPHeaderModifiers{
							Set: map[string]string{
								"x-foo": "bar",
							},
						},
						ResponseHeaders: &HTTPHeaderModifiers{
							Remove: []string{"x-foo"},
						},
					},
					{
						Weight:    10,
						Service:   "test-redirect",
						Namespace: defaultNamespace,
					},
				},
				Meta: map[string]string{
					"foo": "bar",
					"gir": "zim",
				},
			},
			verify: verifySplitter,
		},
		{
			name: "mega router", // use one mega object to avoid multiple trips
			entry: &ServiceRouterConfigEntry{
				Kind:      ServiceRouter,
				Name:      "test-route",
				Partition: defaultPartition,
				Namespace: defaultNamespace,
				Routes: []ServiceRoute{
					{
						Match: &ServiceRouteMatch{
							HTTP: &ServiceRouteHTTPMatch{
								PathPrefix: "/prefix",
								Header: []ServiceRouteHTTPMatchHeader{
									{Name: "x-debug", Exact: "1"},
								},
								QueryParam: []ServiceRouteHTTPMatchQueryParam{
									{Name: "debug", Exact: "1"},
								},
							},
						},
						Destination: &ServiceRouteDestination{
							Service:               "test-failover",
							ServiceSubset:         "v2",
							Namespace:             defaultNamespace,
							PrefixRewrite:         "/",
							RequestTimeout:        5 * time.Second,
							NumRetries:            5,
							RetryOnConnectFailure: true,
							RetryOnStatusCodes:    []uint32{500, 503, 401},
							RequestHeaders: &HTTPHeaderModifiers{
								Set: map[string]string{
									"x-foo": "bar",
								},
							},
							ResponseHeaders: &HTTPHeaderModifiers{
								Remove: []string{"x-foo"},
							},
						},
					},
				},
				Meta: map[string]string{
					"foo": "bar",
					"gir": "zim",
				},
			},
			verify: verifyRouter,
		},
	} {
		tc := tc
		name := fmt.Sprintf("%s:%s: %s", tc.entry.GetKind(), tc.entry.GetName(), tc.name)
		ok := t.Run(name, func(t *testing.T) {
			tc.verify(t, tc.entry)
		})
		require.True(t, ok, "subtest %q failed so aborting remainder", name)
	}
}

func TestAPI_ConfigEntry_ServiceResolver_LoadBalancer(t *testing.T) {
	t.Parallel()
	c, s := makeClient(t)
	defer s.Stop()

	config_entries := c.ConfigEntries()

	verifyResolver := func(t *testing.T, initial ConfigEntry) {
		t.Helper()
		require.IsType(t, &ServiceResolverConfigEntry{}, initial)
		testEntry := initial.(*ServiceResolverConfigEntry)

		// set it
		_, wm, err := config_entries.Set(testEntry, nil)
		require.NoError(t, err)
		require.NotNil(t, wm)
		require.NotEqual(t, 0, wm.RequestTime)

		// get it
		entry, qm, err := config_entries.Get(ServiceResolver, testEntry.Name, nil)
		require.NoError(t, err)
		require.NotNil(t, qm)
		require.NotEqual(t, 0, qm.RequestTime)

		// verify it
		readResolver, ok := entry.(*ServiceResolverConfigEntry)
		require.True(t, ok)
		readResolver.ModifyIndex = 0 // reset for Equals()
		readResolver.CreateIndex = 0 // reset for Equals()

		require.Equal(t, testEntry, readResolver)
	}

	// First set the necessary protocols to allow advanced routing features.
	for _, service := range []string{
		"test-least-req",
		"test-ring-hash",
	} {
		serviceDefaults := &ServiceConfigEntry{
			Kind:     ServiceDefaults,
			Name:     service,
			Protocol: "http",
		}
		_, _, err := config_entries.Set(serviceDefaults, nil)
		require.NoError(t, err)
	}

	// NOTE: Due to service graph validation, these have to happen in a specific order.
	for _, tc := range []struct {
		name   string
		entry  ConfigEntry
		verify func(t *testing.T, initial ConfigEntry)
	}{
		{
			name: "least-req",
			entry: &ServiceResolverConfigEntry{
				Kind:      ServiceResolver,
				Name:      "test-least-req",
				Partition: defaultPartition,
				Namespace: defaultNamespace,
				LoadBalancer: &LoadBalancer{
					Policy:             "least_request",
					LeastRequestConfig: &LeastRequestConfig{ChoiceCount: 10},
				},
			},
			verify: verifyResolver,
		},
		{
			name: "ring-hash-with-policies",
			entry: &ServiceResolverConfigEntry{
				Kind:      ServiceResolver,
				Name:      "test-ring-hash",
				Namespace: defaultNamespace,
				Partition: defaultPartition,
				LoadBalancer: &LoadBalancer{
					Policy: "ring_hash",
					RingHashConfig: &RingHashConfig{
						MinimumRingSize: 1024 * 2,
						MaximumRingSize: 1024 * 4,
					},
					HashPolicies: []HashPolicy{
						{
							Field:      "header",
							FieldValue: "my-session-header",
							Terminal:   true,
						},
						{
							Field:      "cookie",
							FieldValue: "oreo",
							CookieConfig: &CookieConfig{
								Path: "/tray",
								TTL:  20 * time.Millisecond,
							},
						},
						{
							Field:      "cookie",
							FieldValue: "sugar",
							CookieConfig: &CookieConfig{
								Session: true,
								Path:    "/tin",
							},
						},
						{
							SourceIP: true,
						},
					},
				},
			},
			verify: verifyResolver,
		},
	} {
		tc := tc
		name := fmt.Sprintf("%s:%s: %s", tc.entry.GetKind(), tc.entry.GetName(), tc.name)
		ok := t.Run(name, func(t *testing.T) {
			tc.verify(t, tc.entry)
		})
		require.True(t, ok, "subtest %q failed so aborting remainder", name)
	}
}
<commit_msg>fix test failures<commit_after>package api

import (
	"fmt"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
)

func TestAPI_ConfigEntry_DiscoveryChain(t *testing.T) {
	t.Parallel()
	c, s := makeClient(t)
	defer s.Stop()

	config_entries := c.ConfigEntries()

	verifyResolver := func(t *testing.T, initial ConfigEntry) {
		t.Helper()
		require.IsType(t, &ServiceResolverConfigEntry{}, initial)
		testEntry := initial.(*ServiceResolverConfigEntry)

		// set it
		_, wm, err := config_entries.Set(testEntry, nil)
		require.NoError(t, err)
		require.NotNil(t, wm)
		require.NotEqual(t, 0, wm.RequestTime)

		// get it
		entry, qm, err := config_entries.Get(ServiceResolver, testEntry.Name, nil)
		require.NoError(t, err)
		require.NotNil(t, qm)
		require.NotEqual(t, 0, qm.RequestTime)

		// generic verification
		require.Equal(t, testEntry.Meta, entry.GetMeta())

		// verify it
		readResolver, ok := entry.(*ServiceResolverConfigEntry)
		require.True(t, ok)
		readResolver.ModifyIndex = 0 // reset for Equals()
		readResolver.CreateIndex = 0 // reset for Equals()

		require.Equal(t, testEntry, readResolver)

		// TODO(rb): cas?
		// TODO(rb): list?
	}

	verifySplitter := func(t *testing.T, initial ConfigEntry) {
		t.Helper()
		require.IsType(t, &ServiceSplitterConfigEntry{}, initial)
		testEntry := initial.(*ServiceSplitterConfigEntry)

		// set it
		_, wm, err := config_entries.Set(testEntry, nil)
		require.NoError(t, err)
		require.NotNil(t, wm)
		require.NotEqual(t, 0, wm.RequestTime)

		// get it
		entry, qm, err := config_entries.Get(ServiceSplitter, testEntry.Name, nil)
		require.NoError(t, err)
		require.NotNil(t, qm)
		require.NotEqual(t, 0, qm.RequestTime)

		// generic verification
		require.Equal(t, testEntry.Meta, entry.GetMeta())

		// verify it
		readSplitter, ok := entry.(*ServiceSplitterConfigEntry)
		require.True(t, ok)
		readSplitter.ModifyIndex = 0 // reset for Equals()
		readSplitter.CreateIndex = 0 // reset for Equals()

		require.Equal(t, testEntry, readSplitter)

		// TODO(rb): cas?
		// TODO(rb): list?
	}

	verifyRouter := func(t *testing.T, initial ConfigEntry) {
		t.Helper()
		require.IsType(t, &ServiceRouterConfigEntry{}, initial)
		testEntry := initial.(*ServiceRouterConfigEntry)

		// set it
		_, wm, err := config_entries.Set(testEntry, nil)
		require.NoError(t, err)
		require.NotNil(t, wm)
		require.NotEqual(t, 0, wm.RequestTime)

		// get it
		entry, qm, err := config_entries.Get(ServiceRouter, testEntry.Name, nil)
		require.NoError(t, err)
		require.NotNil(t, qm)
		require.NotEqual(t, 0, qm.RequestTime)

		// generic verification
		require.Equal(t, testEntry.Meta, entry.GetMeta())

		// verify it
		readRouter, ok := entry.(*ServiceRouterConfigEntry)
		require.True(t, ok)
		readRouter.ModifyIndex = 0 // reset for Equals()
		readRouter.CreateIndex = 0 // reset for Equals()

		require.Equal(t, testEntry, readRouter)

		// TODO(rb): cas?
		// TODO(rb): list?
	}

	// First set the necessary protocols to allow advanced routing features.
	for _, service := range []string{
		"test-failover",
		"test-redirect",
		"alternate",
		"test-split",
		"test-route",
	} {
		serviceDefaults := &ServiceConfigEntry{
			Kind:     ServiceDefaults,
			Name:     service,
			Protocol: "http",
		}
		_, _, err := config_entries.Set(serviceDefaults, nil)
		require.NoError(t, err)
	}

	// NOTE: Due to service graph validation, these have to happen in a specific order.
	for _, tc := range []struct {
		name   string
		entry  ConfigEntry
		verify func(t *testing.T, initial ConfigEntry)
	}{
		{
			name: "failover",
			entry: &ServiceResolverConfigEntry{
				Kind:          ServiceResolver,
				Name:          "test-failover",
				Partition:     defaultPartition,
				Namespace:     defaultNamespace,
				DefaultSubset: "v1",
				Subsets: map[string]ServiceResolverSubset{
					"v1": {
						Filter: "Service.Meta.version == v1",
					},
					"v2": {
						Filter: "Service.Meta.version == v2",
					},
				},
				Failover: map[string]ServiceResolverFailover{
					"*": {
						Datacenters: []string{"dc2"},
					},
					"v1": {
						Service:   "alternate",
						Namespace: defaultNamespace,
					},
				},
				ConnectTimeout: 5 * time.Second,
				Meta: map[string]string{
					"foo": "bar",
					"gir": "zim",
				},
			},
			verify: verifyResolver,
		},
		{
			name: "redirect",
			entry: &ServiceResolverConfigEntry{
				Kind:      ServiceResolver,
				Name:      "test-redirect",
				Partition: defaultPartition,
				Namespace: defaultNamespace,
				Redirect: &ServiceResolverRedirect{
					Service:       "test-failover",
					ServiceSubset: "v2",
					Namespace:     defaultNamespace,
					Datacenter:    "d",
				},
			},
			verify: verifyResolver,
		},
		{
			name: "mega splitter", // use one mega object to avoid multiple trips
			entry: &ServiceSplitterConfigEntry{
				Kind:      ServiceSplitter,
				Name:      "test-split",
				Partition: defaultPartition,
				Namespace: defaultNamespace,
				Splits: []ServiceSplit{
					{
						Weight:        90,
						Service:       "test-failover",
						ServiceSubset: "v1",
						Namespace:     defaultNamespace,
						RequestHeaders: &HTTPHeaderModifiers{
							Set: map[string]string{
								"x-foo": "bar",
							},
						},
						ResponseHeaders: &HTTPHeaderModifiers{
							Remove: []string{"x-foo"},
						},
					},
					{
						Weight:    10,
						Service:   "test-redirect",
						Namespace: defaultNamespace,
					},
				},
				Meta: map[string]string{
					"foo": "bar",
					"gir": "zim",
				},
			},
			verify: verifySplitter,
		},
		{
			name: "mega router", // use one mega object to avoid multiple trips
			entry: &ServiceRouterConfigEntry{
				Kind:      ServiceRouter,
				Name:      "test-route",
				Partition: defaultPartition,
				Namespace: defaultNamespace,
				Routes: []ServiceRoute{
					{
						Match: &ServiceRouteMatch{
							HTTP: &ServiceRouteHTTPMatch{
								PathPrefix: "/prefix",
								Header: []ServiceRouteHTTPMatchHeader{
									{Name: "x-debug", Exact: "1"},
								},
								QueryParam: []ServiceRouteHTTPMatchQueryParam{
									{Name: "debug", Exact: "1"},
								},
							},
						},
						Destination: &ServiceRouteDestination{
							Service:               "test-failover",
							ServiceSubset:         "v2",
							Namespace:             defaultNamespace,
							Partition:             defaultPartition,
							PrefixRewrite:         "/",
							RequestTimeout:        5 * time.Second,
							NumRetries:            5,
							RetryOnConnectFailure: true,
							RetryOnStatusCodes:    []uint32{500, 503, 401},
							RequestHeaders: &HTTPHeaderModifiers{
								Set: map[string]string{
									"x-foo": "bar",
								},
							},
							ResponseHeaders: &HTTPHeaderModifiers{
								Remove: []string{"x-foo"},
							},
						},
					},
				},
				Meta: map[string]string{
					"foo": "bar",
					"gir": "zim",
				},
			},
			verify: verifyRouter,
		},
	} {
		tc := tc
		name := fmt.Sprintf("%s:%s: %s", tc.entry.GetKind(), tc.entry.GetName(), tc.name)
		ok := t.Run(name, func(t *testing.T) {
			tc.verify(t, tc.entry)
		})
		require.True(t, ok, "subtest %q failed so aborting remainder", name)
	}
}

func TestAPI_ConfigEntry_ServiceResolver_LoadBalancer(t *testing.T) {
	t.Parallel()
	c, s := makeClient(t)
	defer s.Stop()

	config_entries := c.ConfigEntries()

	verifyResolver := func(t *testing.T, initial ConfigEntry) {
		t.Helper()
		require.IsType(t, &ServiceResolverConfigEntry{}, initial)
		testEntry := initial.(*ServiceResolverConfigEntry)

		// set it
		_, wm, err := config_entries.Set(testEntry, nil)
		require.NoError(t, err)
		require.NotNil(t, wm)
		require.NotEqual(t, 0, wm.RequestTime)

		// get it
		entry, qm, err := config_entries.Get(ServiceResolver, testEntry.Name, nil)
		require.NoError(t, err)
		require.NotNil(t, qm)
		require.NotEqual(t, 0, qm.RequestTime)

		// verify it
		readResolver, ok := entry.(*ServiceResolverConfigEntry)
		require.True(t, ok)
		readResolver.ModifyIndex = 0 // reset for Equals()
		readResolver.CreateIndex = 0 // reset for Equals()

		require.Equal(t, testEntry, readResolver)
	}

	// First set the necessary protocols to allow advanced routing features.
	for _, service := range []string{
		"test-least-req",
		"test-ring-hash",
	} {
		serviceDefaults := &ServiceConfigEntry{
			Kind:     ServiceDefaults,
			Name:     service,
			Protocol: "http",
		}
		_, _, err := config_entries.Set(serviceDefaults, nil)
		require.NoError(t, err)
	}

	// NOTE: Due to service graph validation, these have to happen in a specific order.
	for _, tc := range []struct {
		name   string
		entry  ConfigEntry
		verify func(t *testing.T, initial ConfigEntry)
	}{
		{
			name: "least-req",
			entry: &ServiceResolverConfigEntry{
				Kind:      ServiceResolver,
				Name:      "test-least-req",
				Partition: defaultPartition,
				Namespace: defaultNamespace,
				LoadBalancer: &LoadBalancer{
					Policy:             "least_request",
					LeastRequestConfig: &LeastRequestConfig{ChoiceCount: 10},
				},
			},
			verify: verifyResolver,
		},
		{
			name: "ring-hash-with-policies",
			entry: &ServiceResolverConfigEntry{
				Kind:      ServiceResolver,
				Name:      "test-ring-hash",
				Namespace: defaultNamespace,
				Partition: defaultPartition,
				LoadBalancer: &LoadBalancer{
					Policy: "ring_hash",
					RingHashConfig: &RingHashConfig{
						MinimumRingSize: 1024 * 2,
						MaximumRingSize: 1024 * 4,
					},
					HashPolicies: []HashPolicy{
						{
							Field:      "header",
							FieldValue: "my-session-header",
							Terminal:   true,
						},
						{
							Field:      "cookie",
							FieldValue: "oreo",
							CookieConfig: &CookieConfig{
								Path: "/tray",
								TTL:  20 * time.Millisecond,
							},
						},
						{
							Field:      "cookie",
							FieldValue: "sugar",
							CookieConfig: &CookieConfig{
								Session: true,
								Path:    "/tin",
							},
						},
						{
							SourceIP: true,
						},
					},
				},
			},
			verify: verifyResolver,
		},
	} {
		tc := tc
		name := fmt.Sprintf("%s:%s: %s", tc.entry.GetKind(), tc.entry.GetName(), tc.name)
		ok := t.Run(name, func(t *testing.T) {
			tc.verify(t, tc.entry)
		})
		require.True(t, ok, "subtest %q failed so aborting remainder", name)
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2012 The Camlistore Authors.

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

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

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

package server

import (
	"encoding/json"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"os"
	"reflect"
	"strconv"
	"strings"

	"camlistore.org/pkg/auth"
	"camlistore.org/pkg/blobserver"
	"camlistore.org/pkg/httputil"
	"camlistore.org/pkg/jsonconfig"
	"camlistore.org/pkg/osutil"
)

// SetupHandler handles serving the wizard setup page.
type SetupHandler struct {
	config jsonconfig.Obj
}

func init() {
	blobserver.RegisterHandlerConstructor("setup", newSetupFromConfig)
}

func newSetupFromConfig(ld blobserver.Loader, conf jsonconfig.Obj) (h http.Handler, err error) {
	wizard := &SetupHandler{config: conf}
	return wizard, nil
}

func printWizard(i interface{}) (s string) {
	switch ei := i.(type) {
	case []string:
		for _, v := range ei {
			s += printWizard(v) + ","
		}
		s = strings.TrimRight(s, ",")
	case []interface{}:
		for _, v := range ei {
			s += printWizard(v) + ","
		}
		s = strings.TrimRight(s, ",")
	default:
		return fmt.Sprintf("%v", i)
	}
	return s
}

// Flatten all published entities as lists and move them at the root
// of the conf, to have them displayed individually by the template
func flattenPublish(config jsonconfig.Obj) error {
	gallery := []string{}
	blog := []string{}
	config["gallery"] = gallery
	config["blog"] = blog
	published, ok := config["publish"]
	if !ok {
		delete(config, "publish")
		return nil
	}
	pubObj, ok := published.(map[string]interface{})
	if !ok {
		return fmt.Errorf("Was expecting a map[string]interface{} for \"publish\", got %T", published)
	}
	for k, v := range pubObj {
		pub, ok := v.(map[string]interface{})
		if !ok {
			return fmt.Errorf("Was expecting a map[string]interface{} for %s, got %T", k, pub)
		}
		template, rootPermanode, style := "", "", ""
		for pk, pv := range pub {
			val, ok := pv.(string)
			if !ok {
				return fmt.Errorf("Was expecting a string for %s, got %T", pk, pv)
			}
			switch pk {
			case "template":
				template = val
			case "rootPermanode":
				rootPermanode = val
			case "style":
				style = val
			default:
				return fmt.Errorf("Unknown key %q in %s", pk, k)
			}
		}
		if template == "" || rootPermanode == "" {
			return fmt.Errorf("missing \"template\" key or \"rootPermanode\" key in %s", k)
		}
		obj := []string{k, rootPermanode, style}
		config[template] = obj
	}

	delete(config, "publish")
	return nil
}

func sendWizard(rw http.ResponseWriter, req *http.Request, hasChanged bool) {
	config, err := jsonconfig.ReadFile(osutil.UserServerConfigPath())
	if err != nil {
		httputil.ServerError(rw, req, err)
		return
	}

	err = flattenPublish(config)
	if err != nil {
		httputil.ServerError(rw, req, err)
		return
	}

	funcMap := template.FuncMap{
		"printWizard": printWizard,
	}

	body := `<form id="WizardForm" action="setup" method="post" enctype="multipart/form-data">`
	body += `{{range $k,$v := .}}{{printf "%v" $k}} <input type="text" size="30" name ="{{printf "%v" $k}}" value="{{printWizard $v}}"><br />{{end}}`
	body += `<input type="submit" form="WizardForm" value="Save"></form>`

	if hasChanged {
		body += `<p> Configuration succesfully rewritten </p>`
	}

	tmpl, err := template.New("wizard").Funcs(funcMap).Parse(topWizard + body + bottomWizard)
	if err != nil {
		httputil.ServerError(rw, req, err)
		return
	}
	err = tmpl.Execute(rw, config)
	if err != nil {
		httputil.ServerError(rw, req, err)
		return
	}
}

func rewriteConfig(config *jsonconfig.Obj, configfile string) error {
	b, err := json.MarshalIndent(*config, "", "	")
	if err != nil {
		return err
	}
	s := string(b)
	f, err := os.Create(configfile)
	if err != nil {
		return err
	}
	defer f.Close()
	_, err = f.WriteString(s)
	return err
}

func handleSetupChange(rw http.ResponseWriter, req *http.Request) {
	hilevelConf, err := jsonconfig.ReadFile(osutil.UserServerConfigPath())
	if err != nil {
		httputil.ServerError(rw, req, err)
		return
	}

	hasChanged := false
	var el interface{}
	publish := jsonconfig.Obj{}
	for k, v := range req.Form {
		if _, ok := hilevelConf[k]; !ok {
			if k != "gallery" && k != "blog" {
				continue
			}
		}

		switch k {
		case "https":
			b, err := strconv.ParseBool(v[0])
			if err != nil {
				httputil.ServerError(rw, req, fmt.Errorf("https field expects a boolean value"))
			}
			el = b
		case "replicateTo":
			// TODO(mpl): figure out why it is always seen as different from the conf
			el = []interface{}{}
			if len(v[0]) > 0 {
				els := []string{}
				vals := strings.Split(v[0], ",")
				els = append(els, vals...)
				el = els
			}
		// TODO(mpl): "handler,rootPermanode[,style]" for each published entity for now.
		// we will need something more readable later probably
		case "gallery", "blog":
			if len(v[0]) > 0 {
				pub := strings.Split(v[0], ",")
				if len(pub) < 2 || len(pub) > 3 {
					// no need to fail loudly for now as we'll probably change this format
					continue
				}
				handler := jsonconfig.Obj{}
				handler["template"] = k
				handler["rootPermanode"] = pub[1]
				if len(pub) > 2 {
					handler["style"] = pub[2]
				}
				publish[pub[0]] = handler
			}
			continue
		default:
			el = v[0]
		}
		if reflect.DeepEqual(hilevelConf[k], el) {
			continue
		}
		hasChanged = true
		hilevelConf[k] = el
	}
	// "publish" wasn't checked yet
	if !reflect.DeepEqual(hilevelConf["publish"], publish) {
		hilevelConf["publish"] = publish
		hasChanged = true
	}

	if hasChanged {
		err = rewriteConfig(&hilevelConf, osutil.UserServerConfigPath())
		if err != nil {
			httputil.ServerError(rw, req, err)
			return
		}
	}
	sendWizard(rw, req, hasChanged)
	return
}

func (sh *SetupHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	if !auth.LocalhostAuthorized(req) {
		fmt.Fprintf(rw,
			"Setup only allowed from localhost"+
				"
Back
"+
				"\n")
		return
	}
	if req.Method == "POST" {
		err := req.ParseMultipartForm(10e6)
		if err != nil {
			httputil.ServerError(rw, req, err)
			return
		}
		if len(req.Form) > 0 {
			handleSetupChange(rw, req)
			return
		}
		if strings.Contains(req.URL.Path, "restartCamli") {
			err = osutil.RestartProcess()
			if err != nil {
				log.Fatal("Failed to restart: " + err.Error())
			}
		}
	}

	sendWizard(rw, req, false)
}
<commit_msg>wizard: placeholder for gallery field<commit_after>/*
Copyright 2012 The Camlistore Authors.

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

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

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

package server

import (
	"encoding/json"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"os"
	"reflect"
	"strconv"
	"strings"

	"camlistore.org/pkg/auth"
	"camlistore.org/pkg/blobserver"
	"camlistore.org/pkg/httputil"
	"camlistore.org/pkg/jsonconfig"
	"camlistore.org/pkg/osutil"
)

// SetupHandler handles serving the wizard setup page.
type SetupHandler struct {
	config jsonconfig.Obj
}

func init() {
	blobserver.RegisterHandlerConstructor("setup", newSetupFromConfig)
}

func newSetupFromConfig(ld blobserver.Loader, conf jsonconfig.Obj) (h http.Handler, err error) {
	wizard := &SetupHandler{config: conf}
	return wizard, nil
}

func printWizard(i interface{}) (s string) {
	switch ei := i.(type) {
	case []string:
		for _, v := range ei {
			s += printWizard(v) + ","
		}
		s = strings.TrimRight(s, ",")
	case []interface{}:
		for _, v := range ei {
			s += printWizard(v) + ","
		}
		s = strings.TrimRight(s, ",")
	default:
		return fmt.Sprintf("%v", i)
	}
	return s
}

// Flatten all published entities as lists and move them at the root
// of the conf, to have them displayed individually by the template
func flattenPublish(config jsonconfig.Obj) error {
	gallery := []string{}
	blog := []string{}
	config["gallery"] = gallery
	config["blog"] = blog
	published, ok := config["publish"]
	if !ok {
		delete(config, "publish")
		return nil
	}
	pubObj, ok := published.(map[string]interface{})
	if !ok {
		return fmt.Errorf("Was expecting a map[string]interface{} for \"publish\", got %T", published)
	}
	for k, v := range pubObj {
		pub, ok := v.(map[string]interface{})
		if !ok {
			return fmt.Errorf("Was expecting a map[string]interface{} for %s, got %T", k, pub)
		}
		template, rootPermanode, style := "", "", ""
		for pk, pv := range pub {
			val, ok := pv.(string)
			if !ok {
				return fmt.Errorf("Was expecting a string for %s, got %T", pk, pv)
			}
			switch pk {
			case "template":
				template = val
			case "rootPermanode":
				rootPermanode = val
			case "style":
				style = val
			default:
				return fmt.Errorf("Unknown key %q in %s", pk, k)
			}
		}
		if template == "" || rootPermanode == "" {
			return fmt.Errorf("missing \"template\" key or \"rootPermanode\" key in %s", k)
		}
		obj := []string{k, rootPermanode, style}
		config[template] = obj
	}

	delete(config, "publish")
	return nil
}

func sendWizard(rw http.ResponseWriter, req *http.Request, hasChanged bool) {
	config, err := jsonconfig.ReadFile(osutil.UserServerConfigPath())
	if err != nil {
		httputil.ServerError(rw, req, err)
		return
	}

	err = flattenPublish(config)
	if err != nil {
		httputil.ServerError(rw, req, err)
		return
	}

	funcMap := template.FuncMap{
		"printWizard":    printWizard,
		"inputIsGallery": func(inputName string) bool { return inputName == "gallery" },
	}

	body := `<form id="WizardForm" action="setup" method="post" enctype="multipart/form-data">`
	body += `{{range $k,$v := .}}{{printf "%v" $k}} <input type="text" size="30" name ="{{printf "%v" $k}}" value="{{printWizard $v}}" {{if inputIsGallery $k}}placeholder="/pics/,sha1-xxxx,pics.css"{{end}}>
{{end}}`
	body += `<input type="submit" form="WizardForm" value="Save"></form>`

	if hasChanged {
		body += `<p> Configuration succesfully rewritten </p>`
	}

	tmpl, err := template.New("wizard").Funcs(funcMap).Parse(topWizard + body + bottomWizard)
	if err != nil {
		httputil.ServerError(rw, req, err)
		return
	}
	err = tmpl.Execute(rw, config)
	if err != nil {
		httputil.ServerError(rw, req, err)
		return
	}
}

func rewriteConfig(config *jsonconfig.Obj, configfile string) error {
	b, err := json.MarshalIndent(*config, "", "	")
	if err != nil {
		return err
	}
	s := string(b)
	f, err := os.Create(configfile)
	if err != nil {
		return err
	}
	defer f.Close()
	_, err = f.WriteString(s)
	return err
}

func handleSetupChange(rw http.ResponseWriter, req *http.Request) {
	hilevelConf, err := jsonconfig.ReadFile(osutil.UserServerConfigPath())
	if err != nil {
		httputil.ServerError(rw, req, err)
		return
	}

	hasChanged := false
	var el interface{}
	publish := jsonconfig.Obj{}
	for k, v := range req.Form {
		if _, ok := hilevelConf[k]; !ok {
			if k != "gallery" && k != "blog" {
				continue
			}
		}

		switch k {
		case "https":
			b, err := strconv.ParseBool(v[0])
			if err != nil {
				httputil.ServerError(rw, req, fmt.Errorf("https field expects a boolean value"))
			}
			el = b
		case "replicateTo":
			// TODO(mpl): figure out why it is always seen as different from the conf
			el = []interface{}{}
			if len(v[0]) > 0 {
				els := []string{}
				vals := strings.Split(v[0], ",")
				els = append(els, vals...)
				el = els
			}
		// TODO(mpl): "handler,rootPermanode[,style]" for each published entity for now.
		// we will need something more readable later probably
		case "gallery", "blog":
			if len(v[0]) > 0 {
				pub := strings.Split(v[0], ",")
				if len(pub) < 2 || len(pub) > 3 {
					// no need to fail loudly for now as we'll probably change this format
					continue
				}
				handler := jsonconfig.Obj{}
				handler["template"] = k
				handler["rootPermanode"] = pub[1]
				if len(pub) > 2 {
					handler["style"] = pub[2]
				}
				publish[pub[0]] = handler
			}
			continue
		default:
			el = v[0]
		}
		if reflect.DeepEqual(hilevelConf[k], el) {
			continue
		}
		hasChanged = true
		hilevelConf[k] = el
	}
	// "publish" wasn't checked yet
	if !reflect.DeepEqual(hilevelConf["publish"], publish) {
		hilevelConf["publish"] = publish
		hasChanged = true
	}

	if hasChanged {
		err = rewriteConfig(&hilevelConf, osutil.UserServerConfigPath())
		if err != nil {
			httputil.ServerError(rw, req, err)
			return
		}
	}
	sendWizard(rw, req, hasChanged)
	return
}

func (sh *SetupHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	if !auth.LocalhostAuthorized(req) {
		fmt.Fprintf(rw,
			"Setup only allowed from localhost"+
				"
Back
"+
				"\n")
		return
	}
	if req.Method == "POST" {
		err := req.ParseMultipartForm(10e6)
		if err != nil {
			httputil.ServerError(rw, req, err)
			return
		}
		if len(req.Form) > 0 {
			handleSetupChange(rw, req)
			return
		}
		if strings.Contains(req.URL.Path, "restartCamli") {
			err = osutil.RestartProcess()
			if err != nil {
				log.Fatal("Failed to restart: " + err.Error())
			}
		}
	}

	sendWizard(rw, req, false)
}
<|endoftext|>