<commit_before>package guardian

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"strconv"
	"strings"

	"github.com/gentlemanautomaton/serviceresolver"
	"github.com/scjalliance/resourceful/environment"
	"github.com/scjalliance/resourceful/guardian/transport"
)

type serviceSelection struct {
	Service int
	Addr    int
}

// Client coordinates resource leasing with a resourceful guardian server.
type Client struct {
	service  string
	services []serviceresolver.Service
	endpoint string
}

// NewClient creates a new guardian client that will resolve services for the
// given service name.
//
// TODO:
// Query the health of multiple servers in parallel, probably with a
// 50ms delay between each, in order to proactively locate a functional
// endpoint.
//
// TODO:
// Break service resolution out into its own thing that plugs into a client.
func NewClient(service string) (*Client, error) {
	ctx := context.TODO()

	services, err := serviceresolver.DefaultResolver.Resolve(ctx, service)
	if err != nil {
		return nil, fmt.Errorf("client: %v", err)
	}
	if len(services) == 0 {
		return nil, errors.New("Unable to detect host domain")
	}
	log.Print(services)

	return &Client{
		service:  service,
		services: services,
	}, nil
}

// Acquire will attempt to acquire a lease for the given resource and consumer.
func (c *Client) Acquire(resource, consumer string, env environment.Environment) (response transport.AcquireResponse, err error) {
	err = c.query("acquire", resource, consumer, env, &response)
	return
}

// Release will attempt to remove the lease for the given resource and consumer.
func (c *Client) Release(resource, consumer string) (response transport.ReleaseResponse, err error) {
	err = c.query("release", resource, consumer, nil, &response)
	return
}

// query works through the services list in-order looking for a
// service endpoint that can successfully service the query.
func (c *Client) query(path string, resource, consumer string, env environment.Environment, response interface{}) (err error) {
	var failed []error

	// Try to use the same endpoint as last time if we've already selected one
	if len(c.endpoint) > 0 {
		err = post(c.endpoint+path, resource, consumer, env, response)
		if err == nil {
			return
		}
		failed = append(failed, err)
	}

	// Failover or initial selection
	for _, service := range c.services {
		for _, addr := range service.Addrs {
			endpoint := "http://" + strings.TrimRight(addr.Target, ".") + ":" + strconv.Itoa((int)(addr.Port)) + "/"
			err = post(endpoint+path, resource, consumer, env, response)
			if err == nil {
				c.endpoint = endpoint
				return
			}
			failed = append(failed, err)
		}
	}

	f := len(failed)
	switch {
	case f == 1:
		err = fmt.Errorf("%s failed: %v", path, failed[0])
	case f > 1:
		err = fmt.Errorf("%s failed: attempts to connect to %d servers failed, the last error was: %v", path, f, failed[f-1])
	default:
		err = fmt.Errorf("%s failed: no servers available", path)
	}

	return
}

func post(address, resource, consumer string, env environment.Environment, response interface{}) (err error) {
	v := url.Values{}
	if resource != "" {
		v.Set("resource", resource)
	}
	if consumer != "" {
		v.Set("consumer", consumer)
	}
	if env != nil {
		for key, value := range env {
			v.Set(key, value)
		}
	}
	r, err := http.PostForm(address, v)
	if err != nil {
		return
	}

	defer r.Body.Close()

	if r.StatusCode != 200 {
		err = fmt.Errorf("http status: %v", r.Status)
		return
	}

	err = json.NewDecoder(r.Body).Decode(response)
	if err != nil {
		log.Print(err)
	}

	return
}
<commit_msg>guardian: Removed leftover logging entry<commit_after>package guardian

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"strconv"
	"strings"

	"github.com/gentlemanautomaton/serviceresolver"
	"github.com/scjalliance/resourceful/environment"
	"github.com/scjalliance/resourceful/guardian/transport"
)

type serviceSelection struct {
	Service int
	Addr    int
}

// Client coordinates resource leasing with a resourceful guardian server.
type Client struct {
	service  string
	services []serviceresolver.Service
	endpoint string
}

// NewClient creates a new guardian client that will resolve services for the
// given service name.
//
// TODO:
// Query the health of multiple servers in parallel, probably with a
// 50ms delay between each, in order to proactively locate a functional
// endpoint.
//
// TODO:
// Break service resolution out into its own thing that plugs into a client.
func NewClient(service string) (*Client, error) {
	ctx := context.TODO()

	services, err := serviceresolver.DefaultResolver.Resolve(ctx, service)
	if err != nil {
		return nil, fmt.Errorf("client: %v", err)
	}
	if len(services) == 0 {
		return nil, errors.New("Unable to detect host domain")
	}

	return &Client{
		service:  service,
		services: services,
	}, nil
}

// Acquire will attempt to acquire a lease for the given resource and consumer.
func (c *Client) Acquire(resource, consumer string, env environment.Environment) (response transport.AcquireResponse, err error) {
	err = c.query("acquire", resource, consumer, env, &response)
	return
}

// Release will attempt to remove the lease for the given resource and consumer.
func (c *Client) Release(resource, consumer string) (response transport.ReleaseResponse, err error) {
	err = c.query("release", resource, consumer, nil, &response)
	return
}

// query works through the services list in-order looking for a
// service endpoint that can successfully service the query.
func (c *Client) query(path string, resource, consumer string, env environment.Environment, response interface{}) (err error) {
	var failed []error

	// Try to use the same endpoint as last time if we've already selected one
	if len(c.endpoint) > 0 {
		err = post(c.endpoint+path, resource, consumer, env, response)
		if err == nil {
			return
		}
		failed = append(failed, err)
	}

	// Failover or initial selection
	for _, service := range c.services {
		for _, addr := range service.Addrs {
			endpoint := "http://" + strings.TrimRight(addr.Target, ".") + ":" + strconv.Itoa((int)(addr.Port)) + "/"
			err = post(endpoint+path, resource, consumer, env, response)
			if err == nil {
				c.endpoint = endpoint
				return
			}
			failed = append(failed, err)
		}
	}

	f := len(failed)
	switch {
	case f == 1:
		err = fmt.Errorf("%s failed: %v", path, failed[0])
	case f > 1:
		err = fmt.Errorf("%s failed: attempts to connect to %d servers failed, the last error was: %v", path, f, failed[f-1])
	default:
		err = fmt.Errorf("%s failed: no servers available", path)
	}

	return
}

func post(address, resource, consumer string, env environment.Environment, response interface{}) (err error) {
	v := url.Values{}
	if resource != "" {
		v.Set("resource", resource)
	}
	if consumer != "" {
		v.Set("consumer", consumer)
	}
	if env != nil {
		for key, value := range env {
			v.Set(key, value)
		}
	}
	r, err := http.PostForm(address, v)
	if err != nil {
		return
	}

	defer r.Body.Close()

	if r.StatusCode != 200 {
		err = fmt.Errorf("http status: %v", r.Status)
		return
	}

	err = json.NewDecoder(r.Body).Decode(response)
	if err != nil {
		log.Print(err)
	}

	return
}
<|endoftext|>


<commit_before>package spec

import (
	"strings"
	"testing"
)

type G func(string, func(), ...Option)

func (g G) Pend(text string, f func(), _ ...Option) {
	g(text, f, func(c *config) { c.pend = true })
}

type S func(string, func(), ...Option)

func (s S) Before(f func()) {
	s("", f, func(c *config) { c.before = true })
}

func (s S) After(f func()) {
	s("", f, func(c *config) { c.after = true })
}

func (s S) Pend(text string, f func(), _ ...Option) {
	s(text, f, func(c *config) { c.pend = true })
}

func Run(t *testing.T, f func(*testing.T, G, S), opts ...Option) bool {
	success := true

	for _, s := range parse(f, opts...) {
		s := s
		name := strings.Join(s.name, "/")
		success = success && t.Run(name, func(t *testing.T) {
			switch {
			case s.pend:
				t.SkipNow()
			case s.parallel:
				t.Parallel()
			}
			var (
				spec          func()
				before, after []func()
			)
			f(t, func(_ string, f func(), _ ...Option) {
				switch {
				case len(s.groups) == 0:
				case s.groups[0] > 0:
					s.groups[0]--
				default:
					s.groups = s.groups[1:]
					f()
				}
			}, func(text string, f func(), opts ...Option) {
				cfg := options(opts).apply()
				switch {
				case cfg.before:
					before = append(before, f)
				case cfg.after:
					after = append([]func(){f}, after...)
				case spec != nil || len(s.groups) > 0:
				case s.index > 0:
					s.index--
				default:
					spec = f
				}
			})

			if spec == nil {
				t.Fatalf("Failed to parse: %s", name)
			}

			run(before...)
			defer run(after...)
			run(spec)
		})
	}

	return success
}

func run(fs ...func()) {
	for _, f := range fs {
		f()
	}
}

type specInfo struct {
	name     []string
	pend     bool
	parallel bool
	groups   []uint64
	index    uint64
}

func parse(f func(*testing.T, G, S), opts ...Option) []specInfo {
	type groupInfo struct {
		text       string
		pend       bool
		parallel   bool
		groupIndex uint64
		specIndex  uint64
	}

	var (
		parallel   = options(opts).apply().parallel
		specs      []specInfo
		groups     []groupInfo
		groupIndex uint64 // does this really work?
		specIndex  uint64
	)

	f(nil, func(text string, f func(), opts ...Option) {
		cfg := options(opts).apply()
		groups = append(groups, groupInfo{
			text, cfg.pend,cfg.parallel || parallel,
			groupIndex, specIndex,
		})
		groupIndex, specIndex = 0, 0
		defer func() {
			groupIndex = groups[len(groups)-1].groupIndex + 1
			specIndex = groups[len(groups)-1].specIndex
			groups = groups[:len(groups)-1]
		}()
		f()
	}, func(text string, _ func(), opts ...Option) {
		cfg := options(opts).apply()
		if cfg.before || cfg.after {
			return
		}
		spec := specInfo{pend: cfg.pend, parallel: cfg.parallel || parallel, index: specIndex}
		for _, group := range groups {
			spec.name = append(spec.name, group.text)
			spec.groups = append(spec.groups, group.groupIndex)
			spec.pend = spec.pend || group.pend
			spec.parallel = spec.parallel || group.parallel
		}
		spec.name = append(spec.name, text)
		specs = append(specs, spec)
		specIndex++
	})
	return specs
}

type Option func(*config)

func Parallel() Option {
	return func(c *config) {
		c.parallel = true
	}
}

type config struct {
	pend     bool
	parallel bool
	before   bool
	after    bool
}

type options []Option

func (o options) apply() *config {
	cfg := &config{}
	for _, opt := range o {
		opt(cfg)
	}
	return cfg
}
<commit_msg>Add support for focusing specs<commit_after>package spec

import (
	"strings"
	"testing"
)

type G func(string, func(), ...Option)

func (g G) Pend(text string, f func(), _ ...Option) {
	g(text, f, func(c *config) { c.pend = true })
}

func (g G) Focus(text string, f func(), opts ...Option) {
	g(text, f, append(opts, func(c *config) { c.focus = true })...)
}

type S func(string, func(), ...Option)

func (s S) Before(f func()) {
	s("", f, func(c *config) { c.before = true })
}

func (s S) After(f func()) {
	s("", f, func(c *config) { c.after = true })
}

func (s S) Pend(text string, f func(), _ ...Option) {
	s(text, f, func(c *config) { c.pend = true })
}

func (s S) Focus(text string, f func(), opts ...Option) {
	s(text, f, append(opts, func(c *config) { c.focus = true })...)
}

func Run(t *testing.T, f func(*testing.T, G, S), opts ...Option) bool {
	success := true
	specs, focused := parse(f, opts...)

	for _, s := range specs {
		s := s
		name := strings.Join(s.name, "/")
		success = success && t.Run(name, func(t *testing.T) {
			switch {
			case s.pend, focused && !s.focus:
				t.SkipNow()
			case s.parallel:
				t.Parallel()
			}
			var (
				spec          func()
				before, after []func()
			)
			f(t, func(_ string, f func(), _ ...Option) {
				switch {
				case len(s.groups) == 0:
				case s.groups[0] > 0:
					s.groups[0]--
				default:
					s.groups = s.groups[1:]
					f()
				}
			}, func(text string, f func(), opts ...Option) {
				cfg := options(opts).apply()
				switch {
				case cfg.before:
					before = append(before, f)
				case cfg.after:
					after = append([]func(){f}, after...)
				case spec != nil || len(s.groups) > 0:
				case s.index > 0:
					s.index--
				default:
					spec = f
				}
			})

			if spec == nil {
				t.Fatalf("Failed to parse: %s", name)
			}

			run(before...)
			defer run(after...)
			run(spec)
		})
	}

	return success
}

func run(fs ...func()) {
	for _, f := range fs {
		f()
	}
}

type specInfo struct {
	name     []string
	pend     bool
	focus    bool
	parallel bool
	groups   []uint64
	index    uint64
}

func parse(f func(*testing.T, G, S), opts ...Option) (specs []specInfo, focused bool) {
	type groupInfo struct {
		text       string
		pend       bool
		focus      bool
		parallel   bool
		groupIndex uint64
		specIndex  uint64
	}

	var (
		global     = options(opts).apply()
		groups     []groupInfo
		groupIndex uint64 // does this really work?
		specIndex  uint64
	)

	f(nil, func(text string, f func(), opts ...Option) {
		cfg := options(opts).apply()
		groups = append(groups, groupInfo{
			text:       text,
			pend:       cfg.pend,
			focus:      cfg.focus,
			parallel:   cfg.parallel,
			groupIndex: groupIndex,
			specIndex:  specIndex,
		})
		groupIndex, specIndex = 0, 0
		defer func() {
			groupIndex = groups[len(groups)-1].groupIndex + 1
			specIndex = groups[len(groups)-1].specIndex
			groups = groups[:len(groups)-1]
		}()
		f()
	}, func(text string, _ func(), opts ...Option) {
		cfg := options(opts).apply()
		if cfg.before || cfg.after {
			return
		}
		spec := specInfo{
			pend:     cfg.pend,
			focus:    cfg.focus,
			parallel: cfg.parallel || global.parallel,
			index:    specIndex,
		}
		for _, group := range groups {
			spec.name = append(spec.name, group.text)
			spec.groups = append(spec.groups, group.groupIndex)
			spec.pend = spec.pend || group.pend
			spec.focus = spec.focus || group.focus
			spec.parallel = spec.parallel || group.parallel
		}
		spec.name = append(spec.name, text)
		specs = append(specs, spec)
		specIndex++
		focused = focused || spec.focus && !spec.pend
	})
	return specs, focused
}

type Option func(*config)

func Parallel() Option {
	return func(c *config) {
		c.parallel = true
	}
}

type config struct {
	pend     bool
	focus    bool
	parallel bool
	before   bool
	after    bool
}

type options []Option

func (o options) apply() *config {
	cfg := &config{}
	for _, opt := range o {
		opt(cfg)
	}
	return cfg
}
<|endoftext|>


<commit_before>/*
   Nging is a toolbox for webmasters
   Copyright (C) 2018-present  Wenhui Shen <swh@admpub.com>

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as published
   by the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

package listeners

import (
	"github.com/admpub/nging/application/library/fileupdater"
	"github.com/admpub/nging/application/library/fileupdater/listener"
)

func New() fileupdater.Listener {
	return &Listeners{
		options: map[string]*fileupdater.Options{},
	}
}

type Listeners struct {
	options   map[string]*fileupdater.Options
	tableName string
}

func (a *Listeners) SetTableName(tableName string) fileupdater.Listener {
	a.tableName = tableName
	return a
}

func (a *Listeners) Listen() {
	for _field, _option := range a.options {
		listener.NewWithOptions(_option).ListenDefault()
		delete(a.options, _field)
	}
}

func (a *Listeners) BuildOptions(fieldName string, setters ...fileupdater.OptionSetter) *fileupdater.Options {
	options := &fileupdater.Options{
		FieldName: fieldName,
		TableName: a.tableName,
	}
	for _, setter := range setters {
		setter(options)
	}
	if options.Callback == nil {
		options.Callback = fileupdater.GenCallbackDefault(fieldName, options.FieldValue)
	}
	if len(options.TableName) == 0 {
		panic(`liseners.options: TableName is empty`)
	}
	return options
}

func (a *Listeners) ListenByOptions(fieldName string, setters ...fileupdater.OptionSetter) {
	options := a.BuildOptions(fieldName, setters...)
	listener.NewWithOptions(options).ListenDefault()
}

func (a *Listeners) Add(fieldName string, setters ...fileupdater.OptionSetter) fileupdater.Listener {
	options := a.BuildOptions(fieldName, setters...)
	a.options[fieldName] = options
	return a
}

func (a *Listeners) Delete(fieldNames ...string) fileupdater.Listener {
	for _, fieldName := range fieldNames {
		if _, ok := a.options[fieldName]; ok {
			delete(a.options, fieldName)
		}
	}
	return a
}
<commit_msg>update<commit_after>/*
   Nging is a toolbox for webmasters
   Copyright (C) 2018-present  Wenhui Shen <swh@admpub.com>

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as published
   by the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

package listeners

import (
	"github.com/admpub/nging/application/library/fileupdater"
	"github.com/admpub/nging/application/library/fileupdater/listener"
)

func New(table ...string) fileupdater.Listener {
	l := &Listeners{
		options: map[string]*fileupdater.Options{},
	}
	if len(table) > 0 {
		l.SetTableName(table[0])
	}
	return l
}

type Listeners struct {
	options   map[string]*fileupdater.Options
	tableName string
}

func (a *Listeners) SetTableName(tableName string) fileupdater.Listener {
	a.tableName = tableName
	return a
}

func (a *Listeners) Listen() {
	for _field, _option := range a.options {
		listener.NewWithOptions(_option).ListenDefault()
		delete(a.options, _field)
	}
}

func (a *Listeners) BuildOptions(fieldName string, setters ...fileupdater.OptionSetter) *fileupdater.Options {
	options := &fileupdater.Options{
		FieldName: fieldName,
		TableName: a.tableName,
	}
	for _, setter := range setters {
		setter(options)
	}
	if options.Callback == nil {
		options.Callback = fileupdater.GenCallbackDefault(fieldName, options.FieldValue)
	}
	if len(options.TableName) == 0 {
		panic(`liseners.options: TableName is empty`)
	}
	return options
}

func (a *Listeners) ListenByOptions(fieldName string, setters ...fileupdater.OptionSetter) {
	options := a.BuildOptions(fieldName, setters...)
	listener.NewWithOptions(options).ListenDefault()
}

func (a *Listeners) Add(fieldName string, setters ...fileupdater.OptionSetter) fileupdater.Listener {
	options := a.BuildOptions(fieldName, setters...)
	a.options[fieldName] = options
	return a
}

func (a *Listeners) Delete(fieldNames ...string) fileupdater.Listener {
	for _, fieldName := range fieldNames {
		if _, ok := a.options[fieldName]; ok {
			delete(a.options, fieldName)
		}
	}
	return a
}
<|endoftext|>


<commit_before>// Copyright 2020 The LUCI 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 main is the main entry point for the app.
package main

import (
	"fmt"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strings"

	"go.chromium.org/luci/common/data/rand/mathrand"
	"go.chromium.org/luci/common/logging"
	"go.chromium.org/luci/common/proto/access"
	"go.chromium.org/luci/server"
	"go.chromium.org/luci/server/gaeemulation"
	"go.chromium.org/luci/server/module"
	"go.chromium.org/luci/server/router"

	"go.chromium.org/luci/buildbucket/appengine/rpc"
	buildbucketpb "go.chromium.org/luci/buildbucket/proto"
)

func isBeefy(req *http.Request) bool {
	return strings.Contains(req.Host, "beefy")
}

func isDev(req *http.Request) bool {
	return strings.HasSuffix(req.Host, "-dev.appspot.com")
}

func main() {
	mods := []module.Module{
		gaeemulation.NewModuleFromFlags(),
	}

	server.Main(nil, mods, func(srv *server.Server) error {
		// Proxy buildbucket.v2.Builds pRPC requests back to the Python
		// service in order to achieve a programmatic traffic split.
		// Because of the way dispatch routes work, requests are proxied
		// to a copy of the Python service hosted at a different path.
		// TODO(crbug/1042991): Remove the proxy once the go service handles all traffic.
		pythonURL, err := url.Parse(fmt.Sprintf("https://default-dot-%s.appspot.com/python", srv.Options.CloudProject))
		if err != nil {
			panic(err)
		}
		beefyURL, err := url.Parse(fmt.Sprintf("https://beefy-dot-%s.appspot.com/python", srv.Options.CloudProject))
		if err != nil {
			panic(err)
		}
		prx := httputil.NewSingleHostReverseProxy(pythonURL)
		prx.Director = func(req *http.Request) {
			target := pythonURL
			if isBeefy(req) {
				target = beefyURL
			}
			// According to net.Request documentation, setting Host is unnecessary
			// because URL.Host is supposed to be used for outbound requests.
			// However, on GAE, it seems that req.Host is incorrectly used.
			req.Host = target.Host
			req.URL.Scheme = target.Scheme
			req.URL.Host = target.Host
			req.URL.Path = fmt.Sprintf("%s%s", target.Path, req.URL.Path)
		}

		access.RegisterAccessServer(srv.PRPC, &access.UnimplementedAccessServer{})
		buildbucketpb.RegisterBuildsServer(srv.PRPC, rpc.New())
		// TODO(crbug/1082369): Remove this workaround once field masks can be decoded.
		srv.PRPC.HackFixFieldMasksForJSON = true
		srv.PRPC.RegisterOverride("buildbucket.v2.Builds", "GetBuild", func(ctx *router.Context) bool {
			// Proxy prod requests back to Python.
			pct := 0
			if isDev(ctx.Request) {
				// Dev has a lower volume of traffic and is less critical.
				pct = 50
			}
			if mathrand.Intn(ctx.Context, 100) < pct {
				return false
			}
			target := pythonURL
			if isBeefy(ctx.Request) {
				target = beefyURL
			}
			logging.Debugf(ctx.Context, "proxying request to %s", target)
			prx.ServeHTTP(ctx.Writer, ctx.Request)
			return true
		})
		return nil
	})
}
<commit_msg>[buildbucket] Add mechanism for requests to determine their own routing<commit_after>// Copyright 2020 The LUCI 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 main is the main entry point for the app.
package main

import (
	"fmt"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strings"

	"go.chromium.org/luci/common/data/rand/mathrand"
	"go.chromium.org/luci/common/logging"
	"go.chromium.org/luci/common/proto/access"
	"go.chromium.org/luci/server"
	"go.chromium.org/luci/server/gaeemulation"
	"go.chromium.org/luci/server/module"
	"go.chromium.org/luci/server/router"

	"go.chromium.org/luci/buildbucket/appengine/rpc"
	buildbucketpb "go.chromium.org/luci/buildbucket/proto"
)

// isBeefy returns whether the request was intended for the beefy service.
func isBeefy(req *http.Request) bool {
	return strings.Contains(req.Host, "beefy")
}

// isDev returns whether the request was intended for the dev instance.
func isDev(req *http.Request) bool {
	return strings.HasSuffix(req.Host, "-dev.appspot.com")
}

func main() {
	mods := []module.Module{
		gaeemulation.NewModuleFromFlags(),
	}

	server.Main(nil, mods, func(srv *server.Server) error {
		// Proxy buildbucket.v2.Builds pRPC requests back to the Python
		// service in order to achieve a programmatic traffic split.
		// Because of the way dispatch routes work, requests are proxied
		// to a copy of the Python service hosted at a different path.
		// TODO(crbug/1042991): Remove the proxy once the go service handles all traffic.
		pythonURL, err := url.Parse(fmt.Sprintf("https://default-dot-%s.appspot.com/python", srv.Options.CloudProject))
		if err != nil {
			panic(err)
		}
		beefyURL, err := url.Parse(fmt.Sprintf("https://beefy-dot-%s.appspot.com/python", srv.Options.CloudProject))
		if err != nil {
			panic(err)
		}
		prx := httputil.NewSingleHostReverseProxy(pythonURL)
		prx.Director = func(req *http.Request) {
			target := pythonURL
			if isBeefy(req) {
				target = beefyURL
			}
			// According to net.Request documentation, setting Host is unnecessary
			// because URL.Host is supposed to be used for outbound requests.
			// However, on GAE, it seems that req.Host is incorrectly used.
			req.Host = target.Host
			req.URL.Scheme = target.Scheme
			req.URL.Host = target.Host
			req.URL.Path = fmt.Sprintf("%s%s", target.Path, req.URL.Path)
		}

		access.RegisterAccessServer(srv.PRPC, &access.UnimplementedAccessServer{})
		buildbucketpb.RegisterBuildsServer(srv.PRPC, rpc.New())
		// TODO(crbug/1082369): Remove this workaround once field masks can be decoded.
		srv.PRPC.HackFixFieldMasksForJSON = true
		srv.PRPC.RegisterOverride("buildbucket.v2.Builds", "GetBuild", func(ctx *router.Context) bool {
			// Proxy prod requests back to Python.
			pct := 0
			if isDev(ctx.Request) {
				// Dev has a lower volume of traffic and is less critical.
				pct = 50
			}
			switch val := ctx.Request.Header.Get("Should-Proxy"); val {
			case "true":
				pct = 0
				logging.Debugf(ctx.Context, "request demanded to be proxied")
			case "false":
				pct = 100
				logging.Debugf(ctx.Context, "request demanded not to be proxied")
			}
			if mathrand.Intn(ctx.Context, 100) < pct {
				return false
			}
			target := pythonURL
			if isBeefy(ctx.Request) {
				target = beefyURL
			}
			logging.Debugf(ctx.Context, "proxying request to %s", target)
			prx.ServeHTTP(ctx.Writer, ctx.Request)
			return true
		})
		return nil
	})
}
<|endoftext|>


<commit_before>package common

import (
	"fmt"
	"log"
	"os"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/ec2metadata"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/hashicorp/go-cleanhttp"
	"github.com/hashicorp/packer/template/interpolate"
)

// AccessConfig is for common configuration related to AWS access
type AccessConfig struct {
	AccessKey         string `mapstructure:"access_key"`
	CustomEndpointEc2 string `mapstructure:"custom_endpoint_ec2"`
	MFACode           string `mapstructure:"mfa_code"`
	ProfileName       string `mapstructure:"profile"`
	RawRegion         string `mapstructure:"region"`
	SecretKey         string `mapstructure:"secret_key"`
	SkipValidation    bool   `mapstructure:"skip_region_validation"`
	Token             string `mapstructure:"token"`
	session           *session.Session
}

// Config returns a valid aws.Config object for access to AWS services, or
// an error if the authentication and region couldn't be resolved
func (c *AccessConfig) Session() (*session.Session, error) {
	if c.session != nil {
		return c.session, nil
	}

	config := aws.NewConfig().WithMaxRetries(11).WithCredentialsChainVerboseErrors(true)

	if c.ProfileName != "" {
		if err := os.Setenv("AWS_PROFILE", c.ProfileName); err != nil {
			return nil, fmt.Errorf("Set env error: %s", err)
		}
	} else if c.RawRegion != "" {
		config = config.WithRegion(c.RawRegion)
	} else if region := c.metadataRegion(); region != "" {
		config = config.WithRegion(region)
	}

	if c.CustomEndpointEc2 != "" {
		config = config.WithEndpoint(c.CustomEndpointEc2)
	}

	if c.AccessKey != "" {
		creds := credentials.NewChainCredentials(
			[]credentials.Provider{
				&credentials.StaticProvider{
					Value: credentials.Value{
						AccessKeyID:     c.AccessKey,
						SecretAccessKey: c.SecretKey,
						SessionToken:    c.Token,
					},
				},
			})
		config = config.WithCredentials(creds)
	}

	opts := session.Options{
		SharedConfigState: session.SharedConfigEnable,
		Config:            *config,
	}

	if c.MFACode != "" {
		opts.AssumeRoleTokenProvider = func() (string, error) {
			return c.MFACode, nil
		}
	}

	if sess, err := session.NewSessionWithOptions(opts); err != nil {
		return nil, err
	} else if *sess.Config.Region == "" {
		return nil, fmt.Errorf("Could not find AWS region, make sure it's set.")
	} else {
		log.Printf("Found region %s", *sess.Config.Region)
		c.session = sess
	}

	return c.session, nil
}

// metadataRegion returns the region from the metadata service
func (c *AccessConfig) metadataRegion() string {

	client := cleanhttp.DefaultClient()

	// Keep the default timeout (100ms) low as we don't want to wait in non-EC2 environments
	client.Timeout = 100 * time.Millisecond
	ec2meta := ec2metadata.New(session.New(), &aws.Config{
		HTTPClient: client,
	})
	region, err := ec2meta.Region()
	if err != nil {
		log.Println("Error getting region from metadata service, "+
			"probably because we're not running on AWS.", err)
		return ""
	}
	return region
}

func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
	var errs []error
	if c.RawRegion != "" && !c.SkipValidation {
		if valid := ValidateRegion(c.RawRegion); !valid {
			errs = append(errs, fmt.Errorf("Unknown region: %s", c.RawRegion))
		}
	}

	return errs
}
<commit_msg>Correctly set aws region if given in template along with a profile.<commit_after>package common

import (
	"fmt"
	"log"
	"os"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/ec2metadata"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/hashicorp/go-cleanhttp"
	"github.com/hashicorp/packer/template/interpolate"
)

// AccessConfig is for common configuration related to AWS access
type AccessConfig struct {
	AccessKey         string `mapstructure:"access_key"`
	CustomEndpointEc2 string `mapstructure:"custom_endpoint_ec2"`
	MFACode           string `mapstructure:"mfa_code"`
	ProfileName       string `mapstructure:"profile"`
	RawRegion         string `mapstructure:"region"`
	SecretKey         string `mapstructure:"secret_key"`
	SkipValidation    bool   `mapstructure:"skip_region_validation"`
	Token             string `mapstructure:"token"`
	session           *session.Session
}

// Config returns a valid aws.Config object for access to AWS services, or
// an error if the authentication and region couldn't be resolved
func (c *AccessConfig) Session() (*session.Session, error) {
	if c.session != nil {
		return c.session, nil
	}

	config := aws.NewConfig().WithMaxRetries(11).WithCredentialsChainVerboseErrors(true)

	if c.ProfileName != "" {
		if err := os.Setenv("AWS_PROFILE", c.ProfileName); err != nil {
			return nil, fmt.Errorf("Set env error: %s", err)
		}
	}

	if c.RawRegion != "" {
		config = config.WithRegion(c.RawRegion)
	} else if region := c.metadataRegion(); region != "" {
		config = config.WithRegion(region)
	}

	if c.CustomEndpointEc2 != "" {
		config = config.WithEndpoint(c.CustomEndpointEc2)
	}

	if c.AccessKey != "" {
		creds := credentials.NewChainCredentials(
			[]credentials.Provider{
				&credentials.StaticProvider{
					Value: credentials.Value{
						AccessKeyID:     c.AccessKey,
						SecretAccessKey: c.SecretKey,
						SessionToken:    c.Token,
					},
				},
			})
		config = config.WithCredentials(creds)
	}

	opts := session.Options{
		SharedConfigState: session.SharedConfigEnable,
		Config:            *config,
	}

	if c.MFACode != "" {
		opts.AssumeRoleTokenProvider = func() (string, error) {
			return c.MFACode, nil
		}
	}

	if sess, err := session.NewSessionWithOptions(opts); err != nil {
		return nil, err
	} else if *sess.Config.Region == "" {
		return nil, fmt.Errorf("Could not find AWS region, make sure it's set.")
	} else {
		log.Printf("Found region %s", *sess.Config.Region)
		c.session = sess
	}

	return c.session, nil
}

// metadataRegion returns the region from the metadata service
func (c *AccessConfig) metadataRegion() string {

	client := cleanhttp.DefaultClient()

	// Keep the default timeout (100ms) low as we don't want to wait in non-EC2 environments
	client.Timeout = 100 * time.Millisecond
	ec2meta := ec2metadata.New(session.New(), &aws.Config{
		HTTPClient: client,
	})
	region, err := ec2meta.Region()
	if err != nil {
		log.Println("Error getting region from metadata service, "+
			"probably because we're not running on AWS.", err)
		return ""
	}
	return region
}

func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
	var errs []error
	if c.RawRegion != "" && !c.SkipValidation {
		if valid := ValidateRegion(c.RawRegion); !valid {
			errs = append(errs, fmt.Errorf("Unknown region: %s", c.RawRegion))
		}
	}

	return errs
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"html/template"
	"os"
	"net/http"
	"bytes"
	"io/ioutil"
)

// Config structs

type ConfigContainer struct {
	IssuesUrl           string `json:"issuesUrl"`
	Jql                 string `json:"jql"`
	Fields              []string `json:"fields"`
	HighlightField      string `json:"highlightField"`
	HeadsUpField        string `json:"headsUpField"`
	MaxResults          int64 `json:"maxResults"`
	Username            string `json:"username"`
	Password            string `json:"password"`
	IssueTypesSortOrder []string `json:"issueTypesSortOrder"`
}

// Issues imports structs

type IssuesJson struct {
	StartAt   int64 `json:"startAt"`
	MaxResult int64 `json:"maxResults"`
	Total     int64 `json:"total"`
	Issues    []Issues `json:"issues"`
}

// -- Issues structs

type Issues struct {
	Key    string
	Self   string
	Fields Fields `json:"fields"`
}

type Fields interface {
}

// --- Output structs
type OutputData struct {
	Version             string
	IssueTypes          map[string][]Issues
	Dependencies        []Dependencies
	IssueTypesSortOrder []string
}

type Dependencies struct {
	Dependency string `json:"dependency"`
	Versions   []string `json:"versions"`
}

func GenerateReleaseNotes(configData []byte, templateFile, version, dependenciesData string) {

	var dependencies []Dependencies
	var config ConfigContainer

	err := json.Unmarshal(configData, &config)
	assertError(err);

	issues := getIssuesFromUrl(config)

	if (dependenciesData != "") {
		err = json.Unmarshal([]byte(dependenciesData), &dependencies)
		assertError(err);
	}

	var output = OutputData{
		Version: version,
		IssueTypes: make(map[string][]Issues),
		Dependencies: dependencies,
	}

	if (len(config.IssueTypesSortOrder) == 0) {
		config.IssueTypesSortOrder = getDefaultSortOrder()
	}

	for i := 0; i < len(issues); i++ {
		issue := issues[i]
		fields := issue.Fields.(map[string]interface{})

		if (fields["issuetype"] != nil) {
			issueType := fields["issuetype"].(map[string]interface{})

			var typeKey = "";
			if (issueType["name"] != nil) {
				typeKey = string(issueType["name"].(string))
			}
			if output.IssueTypes[typeKey] == nil {
				output.IssueTypes[typeKey] = []Issues{issue}
			} else {
				output.IssueTypes[typeKey] = append(output.IssueTypes[typeKey], issue)
			}
		} else {
			output.IssueTypes[""] = []Issues{issue}
		}
	}

	output.IssueTypesSortOrder = config.IssueTypesSortOrder

	reportTemplate, err := template.New("report").Parse(templateFile)
	assertError(err);

	err = reportTemplate.Execute(os.Stdout, output)
	assertError(err);
}
func getDefaultSortOrder() []string {
	return []string{"New Feature", "Task", "Bug", "Epic"}
}

func getIssuesFromUrl(config ConfigContainer) []Issues {

	type JsonData struct {
		Jql        string `json:"jql"`
		Fields     []string `json:"fields"`
		MaxResults int64 `json:"maxResults"`
	}

	var data = JsonData{
		Jql: config.Jql,
		Fields: config.Fields,
		MaxResults: config.MaxResults,
	}

	body, err := json.Marshal(data)
	assertError(err)

	req, err := http.NewRequest("POST", config.IssuesUrl, bytes.NewBuffer(body))
	assertError(err)

	req.SetBasicAuth(config.Username, config.Password)
	req.Header.Add("Content-Type", "application/json")

	resp, err := http.DefaultClient.Do(req)
	assertError(err);

	defer resp.Body.Close()

	responseBody, err := ioutil.ReadAll(resp.Body)
	assertError(err)

	var issues IssuesJson

	err = json.Unmarshal(responseBody, &issues)

	return issues.Issues
}<commit_msg>Added template processing for -reportConfig files, now support {{version}} variable<commit_after>package main

import (
	"encoding/json"
	"html/template"
	"os"
	"net/http"
	"bytes"
	"io/ioutil"
)

// Config structs

type ConfigContainer struct {
	IssuesUrl           string `json:"issuesUrl"`
	Jql                 string `json:"jql"`
	Fields              []string `json:"fields"`
	HighlightField      string `json:"highlightField"`
	HeadsUpField        string `json:"headsUpField"`
	MaxResults          int64 `json:"maxResults"`
	Username            string `json:"username"`
	Password            string `json:"password"`
	IssueTypesSortOrder []string `json:"issueTypesSortOrder"`
}

// Issues imports structs

type IssuesJson struct {
	StartAt   int64 `json:"startAt"`
	MaxResult int64 `json:"maxResults"`
	Total     int64 `json:"total"`
	Issues    []Issues `json:"issues"`
}

// -- Issues structs

type Issues struct {
	Key    string
	Self   string
	Fields Fields `json:"fields"`
}

type Fields interface {
}

// --- Output structs
type OutputData struct {
	Version             string
	IssueTypes          map[string][]Issues
	Dependencies        []Dependencies
	IssueTypesSortOrder []string
}

type Dependencies struct {
	Dependency string `json:"dependency"`
	Versions   []string `json:"versions"`
}

func GenerateReleaseNotes(configData []byte, templateFile, version, dependenciesData string) {

	var dependencies []Dependencies
	var config ConfigContainer

	configInfo, err2 := template.New("hello").Parse(string(configData))
	assertError(err2)

	processedConfig := new(bytes.Buffer)

	type ConfigData struct {
		Version string
	}

	err3 := configInfo.Execute(processedConfig, ConfigData{ Version: version})
	assertError(err3)

	err := json.Unmarshal(processedConfig.Bytes(), &config)
	assertError(err);

	issues := getIssuesFromUrl(config)

	if (dependenciesData != "") {
		err = json.Unmarshal([]byte(dependenciesData), &dependencies)
		assertError(err);
	}

	var output = OutputData{
		Version: version,
		IssueTypes: make(map[string][]Issues),
		Dependencies: dependencies,
	}

	if (len(config.IssueTypesSortOrder) == 0) {
		config.IssueTypesSortOrder = getDefaultSortOrder()
	}

	for i := 0; i < len(issues); i++ {
		issue := issues[i]
		fields := issue.Fields.(map[string]interface{})

		if (fields["issuetype"] != nil) {
			issueType := fields["issuetype"].(map[string]interface{})

			var typeKey = "";
			if (issueType["name"] != nil) {
				typeKey = string(issueType["name"].(string))
			}
			if output.IssueTypes[typeKey] == nil {
				output.IssueTypes[typeKey] = []Issues{issue}
			} else {
				output.IssueTypes[typeKey] = append(output.IssueTypes[typeKey], issue)
			}
		} else {
			output.IssueTypes[""] = []Issues{issue}
		}
	}

	output.IssueTypesSortOrder = config.IssueTypesSortOrder

	reportTemplate, err := template.New("report").Parse(templateFile)
	assertError(err);

	err = reportTemplate.Execute(os.Stdout, output)
	assertError(err);
}
func getDefaultSortOrder() []string {
	return []string{"New Feature", "Task", "Bug", "Epic"}
}

func getIssuesFromUrl(config ConfigContainer) []Issues {

	type JsonData struct {
		Jql        string `json:"jql"`
		Fields     []string `json:"fields"`
		MaxResults int64 `json:"maxResults"`
	}

	var data = JsonData{
		Jql: config.Jql,
		Fields: config.Fields,
		MaxResults: config.MaxResults,
	}

	body, err := json.Marshal(data)
	assertError(err)

	req, err := http.NewRequest("POST", config.IssuesUrl, bytes.NewBuffer(body))
	assertError(err)

	req.SetBasicAuth(config.Username, config.Password)
	req.Header.Add("Content-Type", "application/json")

	resp, err := http.DefaultClient.Do(req)
	assertError(err);

	defer resp.Body.Close()

	responseBody, err := ioutil.ReadAll(resp.Body)
	assertError(err)

	var issues IssuesJson

	err = json.Unmarshal(responseBody, &issues)

	return issues.Issues
}<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
)

func main() {

	flag.Parse()
	if flag.NArg() < 3 {
		fmt.Println("Please specifiy: index input output")
		return
	}

	// TODO A no write, stdout only option is necessary for sanity

	// Read and build the master replace index
	indexFile, err := os.Open(flag.Arg(0))
	if err != nil {
		log.Fatal(err)
		return
	}
	defer indexFile.Close()

	fmt.Printf("Building index ...\n")
	masterIndex, err := newReplaceIndex(indexFile)
	if err != nil {
		switch err {
		case io.EOF:
			fmt.Printf("The index file is empty, can't continue.\n")
			return
		default:
			log.Fatal(err)
			return
		}
	}

	// Read in the input file
	inputFile, err := os.Open(flag.Arg(1))
	if err != nil {
		log.Fatal(err)
		return
	}
	defer inputFile.Close()

	fmt.Printf("Reading input file into memory ...\n")
	inputData, err := ioutil.ReadAll(inputFile)
	if err != nil {
		log.Fatal(err)
		return
	}

	fmt.Printf("Processing input data ...\n")
	var off, end int
	inputChan := make(chan line)

	// Send line at a time as work task to generate deltas
	go func() {
		for {
			i := bytes.IndexByte(inputData[off:], '\n')
			end = off + i + 1
			if i < 0 {
				err = io.EOF
				break
			}
			inputChan <- line{off, inputData[off:end]}
			off = end
		}
		close(inputChan)
	}()

	// Collect the resultant deltas
	deltas := producer(inputChan, &masterIndex)

	if len(deltas) == 0 {
		fmt.Printf("No matches found\n")
		return
	}

	// open output file
	outFile, err := os.Create(flag.Arg(2))
	if err != nil {
		panic(err)
	}

	defer func() {
		if err := outFile.Close(); err != nil {
			panic(err)
		}
	}()

	fmt.Printf("Writing to output file ...\n")
	// make a write buffer
	w := bufio.NewWriter(outFile)

	var o int
	// for each delta process the corresponding line in inputData
	for _, d := range deltas {

		// TODO This approach could lead to large slices in memory

		// read up to offset as slice, and write
		_, err = w.Write(inputData[o:d.off])
		if err != nil {
			log.Fatal(err)
			return
		}

		// Write replacement value
		_, err = w.Write(masterIndex.readItem(d.index).replace)
		if err != nil {
			log.Fatal(err)
			return
		}

		o = d.off + len(masterIndex.readItem(d.index).find)
		w.Flush()

	}

	// write out remainder of slice
	_, err = w.Write(inputData[o:])
	if err != nil {
		log.Fatal(err)
		return
	}
	w.Flush()
	fmt.Printf("Done.\n")
}
<commit_msg>No idea what I actually changed, but learned to avoid binary data.<commit_after>package main

import (
	"bufio"
	"bytes"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
)

func main() {

	flag.Parse()
	if flag.NArg() < 3 {
		fmt.Println("Please specifiy: index input output")
		return
	}

	// TODO A no write, stdout only option is necessary for sanity

	// Read and build the master replace index
	indexFile, err := os.Open(flag.Arg(0))
	if err != nil {
		log.Fatal(err)
		return
	}
	defer indexFile.Close()

	fmt.Printf("Building index ...\n")
	masterIndex, err := newReplaceIndex(indexFile)
	if err != nil {
		switch err {
		case io.EOF:
			fmt.Printf("The index file is empty, can't continue.\n")
			return
		default:
			log.Fatal(err)
			return
		}
	}

	// Read in the input file
	inputFile, err := os.Open(flag.Arg(1))
	if err != nil {
		log.Fatal(err)
		return
	}
	defer inputFile.Close()

	fmt.Printf("Reading input file into memory ...\n")
	inputData, err := ioutil.ReadAll(inputFile)
	if err != nil {
		log.Fatal(err)
		return
	}

	fmt.Printf("Processing input data ...\n")
	var off, end int
	inputChan := make(chan line)

	// Send line at a time as work task to generate deltas
	go func() {
		for {
			i := bytes.IndexByte(inputData[off:], '\n')
			end = off + i + 1
			if i < 0 {
				err = io.EOF
				break
			}
			inputChan <- line{off, inputData[off:end]}
			off = end
		}
		close(inputChan)
	}()

	// Collect the resultant deltas
	deltas := producer(inputChan, &masterIndex)

	if len(deltas) == 0 {
		fmt.Printf("No matches found\n")
		return
	}

	// open output file
	outFile, err := os.Create(flag.Arg(2))
	if err != nil {
		panic(err)
	}

	defer func() {
		if err := outFile.Close(); err != nil {
			panic(err)
		}
	}()

	fmt.Printf("Writing to output file ...\n")
	// make a write buffer
	w := bufio.NewWriter(outFile)

	var o int

	// for each delta process the corresponding line in inputData
	for _, d := range deltas {

		// TODO This approach could lead to large slices in memory

		// read up to offset as slice, and write
		_, err = w.Write(inputData[o:d.off])
		if err != nil {
			log.Fatal(err)
			return
		}

		// Write replacement value
		_, err = w.Write(masterIndex.readItem(d.index).replace)
		if err != nil {
			log.Fatal(err)
			return
		}

		o = d.off + len(masterIndex.readItem(d.index).find)
		w.Flush()

	}

	// write out remainder of slice
	_, err = w.Write(inputData[o:])
	if err != nil {
		log.Fatal(err)
		return
	}
	w.Flush()
	fmt.Printf("Done.\n")
}
<|endoftext|>


<commit_before>package checkntpoffset

import (
	"bufio"
	"fmt"
	"io"
	"math"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/beevik/ntp"
	"github.com/jessevdk/go-flags"
	"github.com/mackerelio/checkers"
)

var opts struct {
	Crit       float64 `short:"c" long:"critical" default:"100" description:"Critical threshold of ntp offset(ms)"`
	Warn       float64 `short:"w" long:"warning" default:"50" description:"Warning threshold of ntp offset(ms)"`
	NTPServers string  `short:"s" long:"ntp-servers" default:"" description:"Use specified NTP Servers(plural servers can be set separated by ,). When set plural servers, use first response. If not set, use local command just like ntpd/chronyd."`
	NTPTimeout int     `short:"t" long:"ntp-timeout" default:"15" description:"Timeout of NTP Server Querying(in seconds)."`
}

var ntpTimeout int

func init() {
	ntpTimeout = 15
}

// Do the plugin
func Do() {
	ckr := run(os.Args[1:])
	ckr.Name = "NTP"
	ckr.Exit()
}

func run(args []string) *checkers.Checker {
	_, err := flags.ParseArgs(&opts, args)
	if err != nil {
		os.Exit(1)
	}
	ntpTimeout = opts.NTPTimeout

	offset, err := getNTPOffset(opts.NTPServers)
	if err != nil {
		return checkers.Unknown(err.Error())
	}

	var chkSt checkers.Status
	var msg string
	stratum, err := getNTPStratum()
	if err != nil {
		msg = fmt.Sprintf("ntp stratum not found offset is %f(actual), %f(warning threshold), %f(critical threshold)", math.Abs(offset), opts.Warn, opts.Crit)
		return checkers.NewChecker(checkers.WARNING, msg)
	}

	if opts.Crit < math.Abs(offset) {
		msg = fmt.Sprintf("ntp offset is over %f(actual) > %f(threshold)", math.Abs(offset), opts.Crit)
		chkSt = checkers.CRITICAL
	} else if opts.Warn < math.Abs(offset) {
		msg = fmt.Sprintf("ntp offset is over %f(actual) > %f(threshold)", math.Abs(offset), opts.Warn)
		chkSt = checkers.WARNING
	} else {
		msg = fmt.Sprintf("ntp stratum=%s, offset is %f(actual) < %f(warning threshold), %f(critial threshold)", stratum, math.Abs(offset), opts.Warn, opts.Crit)
		chkSt = checkers.OK
	}

	return checkers.NewChecker(chkSt, msg)
}

func withCmd(cmd *exec.Cmd, fn func(io.Reader) error) error {
	out, err := cmd.StdoutPipe()
	if err != nil {
		return err
	}
	if err := cmd.Start(); err != nil {
		return err
	}
	if err := fn(out); err != nil {
		return err
	}
	return cmd.Wait()
}

const (
	ntpNTPD    = "ntpd"
	ntpChronyd = "chronyd"

	cmdNTPq    = "ntpq"
	cmdChronyc = "chronyc"
)

func hasCommand(cmd string) bool {
	_, err := exec.LookPath(cmd)
	return err == nil
}

func detectNTPDname() (ntpdName string, err error) {
	err = withCmd(exec.Command("ps", "-eo", "comm"), func(out io.Reader) error {
		scr := bufio.NewScanner(out)
		for scr.Scan() {
			switch filepath.Base(scr.Text()) {
			case ntpChronyd:
				if hasCommand(cmdChronyc) {
					ntpdName = ntpChronyd
					return nil
				}
			case ntpNTPD:
				if hasCommand(cmdNTPq) {
					ntpdName = ntpNTPD
					return nil
				}
			}
		}
		return fmt.Errorf("no ntp daemons detected")
	})
	return ntpdName, err
}

func getNTPOffset(ntpServers string) (float64, error) {
	if ntpServers != "" {
		return getNTPOffsetFromNTPServers(ntpServers)
	}
	ntpdName, err := detectNTPDname()
	if err != nil {
		return 0.0, err
	}
	switch ntpdName {
	case ntpNTPD:
		return getNTPOffsetFromNTPD()
	case ntpChronyd:
		return getNTPOffsetFromChrony()
	}
	return 0.0, fmt.Errorf("unsupported ntp daemon %q", ntpdName)
}

// getNTPOffsetFromNTPServers ask time to ntp servers and return NTP Offset.
// Use first response, ignore others
//
// FIXME need fluent cancel mechanism
func getNTPOffsetFromNTPServers(ntpServers string) (offset float64, err error) {
	resultChan := make(chan float64)
	for _, ntpServer := range strings.Split(ntpServers, ",") {
		go func(ntpServer string) error {
			ntpServer = strings.Trim(ntpServer, " ")
			options := ntp.QueryOptions{Timeout: time.Duration(ntpTimeout) * time.Second}
			response, err := ntp.QueryWithOptions(ntpServer, options)
			if err != nil {
				return err
			}
			resultChan <- float64(response.ClockOffset / time.Millisecond)
			return nil
		}(ntpServer)
	}

	select {
	case <-time.After(time.Duration(ntpTimeout) * time.Second):
		// return error only when all NTPServers are failed
		return 0.0, fmt.Errorf("NTP offset cannot get from %q", ntpServers)
	case offset = <-resultChan:
		return offset, nil
	}
}

func getNTPOffsetFromNTPD() (offset float64, err error) {
	err = withCmd(exec.Command(cmdNTPq, "-c", "rv 0 offset"), func(out io.Reader) error {
		offset, err = parseNTPOffsetFromNTPD(out)
		return err
	})
	return offset, err
}

func parseNTPOffsetFromNTPD(out io.Reader) (float64, error) {
	scr := bufio.NewScanner(out)
	const offsetPrefix = "offset="
	for scr.Scan() {
		line := scr.Text()
		if strings.HasPrefix(line, offsetPrefix) {
			return strconv.ParseFloat(strings.TrimPrefix(line, offsetPrefix), 64)
		}
	}
	return 0.0, fmt.Errorf("couldn't get ntp offset. ntpd process may be down")
}

func getNTPOffsetFromChrony() (offset float64, err error) {
	err = withCmd(exec.Command(cmdChronyc, "tracking"), func(out io.Reader) error {
		offset, err = parseNTPOffsetFromChrony(out)
		return err
	})
	return offset, err
}

func parseNTPOffsetFromChrony(out io.Reader) (offset float64, err error) {
	scr := bufio.NewScanner(out)
	for scr.Scan() {
		line := scr.Text()
		if strings.HasPrefix(line, "Last offset") {
			flds := strings.Fields(line)
			if len(flds) != 5 {
				return 0.0, fmt.Errorf("failed to get ntp offset")
			}
			offset, err = strconv.ParseFloat(flds[3], 64)
			if err != nil {
				return 0.0, err
			}
			return offset * 1000, nil
		}
	}
	return 0.0, fmt.Errorf("failed to get ntp offset")
}

func getNTPStratum() (string, error) {
	ntpdName, err := detectNTPDname()
	if err != nil {
		return "", err
	}
	switch ntpdName {
	case ntpNTPD:
		return getNTPStratumFromNTPD()
	case ntpChronyd:
		return getNTPStratumFromChrony()
	}
	return "", fmt.Errorf("unsupported ntp daemon %q", ntpdName)
}

func getNTPStratumFromNTPD() (stratum string, err error) {
	err = withCmd(exec.Command(cmdNTPq, "-np"), func(out io.Reader) error {
		stratum, err = parseNTPStratumFromNTPD(out)
		return err
	})
	return stratum, err
}

func parseNTPStratumFromNTPD(out io.Reader) (string, error) {
	scr := bufio.NewScanner(out)
	const stratumPrefix = "*"
	for scr.Scan() {
		line := scr.Text()
		if strings.HasPrefix(line, stratumPrefix) {
			flds := strings.Fields(line)
			if len(flds) != 10 {
				return "unknown", fmt.Errorf("failed to get ntp stratum")
			}
			return strings.TrimPrefix(flds[0], stratumPrefix), nil
		}
	}
	return "unknown", fmt.Errorf("failed to get ntp stratum")
}

func getNTPStratumFromChrony() (stratum string, err error) {
	err = withCmd(exec.Command(cmdChronyc, "-n", "sources"), func(out io.Reader) error {
		stratum, err = parseNTPStratumFromChrony(out)
		return err
	})
	return stratum, err
}

func parseNTPStratumFromChrony(out io.Reader) (string, error) {
	scr := bufio.NewScanner(out)
	const stratumPrefix = "^*"
	for scr.Scan() {
		line := scr.Text()
		if strings.HasPrefix(line, stratumPrefix) {
			flds := strings.Fields(line)
			if len(flds) != 10 {
				return "unknown", fmt.Errorf("failed to get ntp stratum")
			}
			return flds[1], nil
		}
	}
	return "unknown", fmt.Errorf("failed to get ntp stratum")
}
<commit_msg>check stratum with single command<commit_after>package checkntpoffset

import (
	"bufio"
	"fmt"
	"io"
	"math"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/beevik/ntp"
	"github.com/jessevdk/go-flags"
	"github.com/mackerelio/checkers"
)

var opts struct {
	Crit       float64 `short:"c" long:"critical" default:"100" description:"Critical threshold of ntp offset(ms)"`
	Warn       float64 `short:"w" long:"warning" default:"50" description:"Warning threshold of ntp offset(ms)"`
	NTPServers string  `short:"s" long:"ntp-servers" default:"" description:"Use specified NTP Servers(plural servers can be set separated by ,). When set plural servers, use first response. If not set, use local command just like ntpd/chronyd."`
	NTPTimeout int     `short:"t" long:"ntp-timeout" default:"15" description:"Timeout of NTP Server Querying(in seconds)."`
}

var ntpTimeout int

func init() {
	ntpTimeout = 15
}

// Do the plugin
func Do() {
	ckr := run(os.Args[1:])
	ckr.Name = "NTP"
	ckr.Exit()
}

func run(args []string) *checkers.Checker {
	_, err := flags.ParseArgs(&opts, args)
	if err != nil {
		os.Exit(1)
	}
	ntpTimeout = opts.NTPTimeout

	offset, err := getNTPOffset(opts.NTPServers)
	if err != nil {
		return checkers.Unknown(err.Error())
	}

	var chkSt checkers.Status
	var msg string

	if opts.Crit < math.Abs(offset) {
		msg = fmt.Sprintf("ntp offset is over %f(actual) > %f(threshold)", math.Abs(offset), opts.Crit)
		chkSt = checkers.CRITICAL
	} else if opts.Warn < math.Abs(offset) {
		msg = fmt.Sprintf("ntp offset is over %f(actual) > %f(threshold)", math.Abs(offset), opts.Warn)
		chkSt = checkers.WARNING
	} else {
		msg = fmt.Sprintf("ntp offset is %f(actual) < %f(warning threshold), %f(critial threshold)", math.Abs(offset), opts.Warn, opts.Crit)
		chkSt = checkers.OK
	}

	return checkers.NewChecker(chkSt, msg)
}

func withCmd(cmd *exec.Cmd, fn func(io.Reader) error) error {
	out, err := cmd.StdoutPipe()
	if err != nil {
		return err
	}
	if err := cmd.Start(); err != nil {
		return err
	}
	if err := fn(out); err != nil {
		return err
	}
	return cmd.Wait()
}

const (
	ntpNTPD    = "ntpd"
	ntpChronyd = "chronyd"

	cmdNTPq    = "ntpq"
	cmdChronyc = "chronyc"
)

func hasCommand(cmd string) bool {
	_, err := exec.LookPath(cmd)
	return err == nil
}

func detectNTPDname() (ntpdName string, err error) {
	err = withCmd(exec.Command("ps", "-eo", "comm"), func(out io.Reader) error {
		scr := bufio.NewScanner(out)
		for scr.Scan() {
			switch filepath.Base(scr.Text()) {
			case ntpChronyd:
				if hasCommand(cmdChronyc) {
					ntpdName = ntpChronyd
					return nil
				}
			case ntpNTPD:
				if hasCommand(cmdNTPq) {
					ntpdName = ntpNTPD
					return nil
				}
			}
		}
		return fmt.Errorf("no ntp daemons detected")
	})
	return ntpdName, err
}

func getNTPOffset(ntpServers string) (float64, error) {
	if ntpServers != "" {
		return getNTPOffsetFromNTPServers(ntpServers)
	}
	ntpdName, err := detectNTPDname()
	if err != nil {
		return 0.0, err
	}
	switch ntpdName {
	case ntpNTPD:
		return getNTPOffsetFromNTPD()
	case ntpChronyd:
		return getNTPOffsetFromChrony()
	}
	return 0.0, fmt.Errorf("unsupported ntp daemon %q", ntpdName)
}

// getNTPOffsetFromNTPServers ask time to ntp servers and return NTP Offset.
// Use first response, ignore others
//
// FIXME need fluent cancel mechanism
func getNTPOffsetFromNTPServers(ntpServers string) (offset float64, err error) {
	resultChan := make(chan float64)
	for _, ntpServer := range strings.Split(ntpServers, ",") {
		go func(ntpServer string) error {
			ntpServer = strings.Trim(ntpServer, " ")
			options := ntp.QueryOptions{Timeout: time.Duration(ntpTimeout) * time.Second}
			response, err := ntp.QueryWithOptions(ntpServer, options)
			if err != nil {
				return err
			}
			resultChan <- float64(response.ClockOffset / time.Millisecond)
			return nil
		}(ntpServer)
	}

	select {
	case <-time.After(time.Duration(ntpTimeout) * time.Second):
		// return error only when all NTPServers are failed
		return 0.0, fmt.Errorf("NTP offset cannot get from %q", ntpServers)
	case offset = <-resultChan:
		return offset, nil
	}
}

func getNTPOffsetFromNTPD() (offset float64, err error) {
	err = withCmd(exec.Command(cmdNTPq, "-c", "rv 0 stratum,offset"), func(out io.Reader) error {
		offset, err = parseNTPOffsetFromNTPD(out)
		return err
	})
	return offset, err
}

func parseNTPOffsetFromNTPD(out io.Reader) (float64, error) {
	scr := bufio.NewScanner(out)
	const stratumPrefix = "stratum="
	var stratumErr error
	const offsetPrefix = "offset="
	for scr.Scan() {
		line := scr.Text()
		for _, column := range strings.Split(line, ",") {
			column = strings.TrimPrefix(column, " ")
			if strings.HasPrefix(column, stratumPrefix) {
				var stratum int64
				stratum, stratumErr = strconv.ParseInt(strings.TrimPrefix(column, stratumPrefix), 10, 64)
				if stratumErr != nil {
					return 0.0, stratumErr
				}
				// the stratum of the remote machine. 16 is "unsynchronized"
				// ref. https://support.ntp.org/bin/view/Support/TroubleshootingNTP
				if stratum == 16 {
					stratumErr = fmt.Errorf("not synchronized to stratum")
				}
			}
			if strings.HasPrefix(column, offsetPrefix) {
				offset, err := strconv.ParseFloat(strings.TrimPrefix(column, offsetPrefix), 64)
				if err != nil {
					return 0.0, err
				}
				return offset, stratumErr
			}
		}
	}
	return 0.0, fmt.Errorf("couldn't get ntp offset. ntpd process may be down")
}

func getNTPOffsetFromChrony() (offset float64, err error) {
	err = withCmd(exec.Command(cmdChronyc, "tracking"), func(out io.Reader) error {
		offset, err = parseNTPOffsetFromChrony(out)
		return err
	})
	return offset, err
}

func parseNTPOffsetFromChrony(out io.Reader) (offset float64, err error) {
	var stratumErr error
	scr := bufio.NewScanner(out)
	for scr.Scan() {
		line := scr.Text()
		if strings.HasPrefix(line, "Stratum") {
			flds := strings.Fields(line)
			if len(flds) != 3 {
				return 0.0, fmt.Errorf("failed to get ntp stratum")
			}
			var stratum int64
			stratum, stratumErr = strconv.ParseInt(flds[2], 10, 64)
			//
			if stratum == 0 && stratumErr == nil {
				stratumErr = fmt.Errorf("not synchronized to stratum")
			}
		}
		if strings.HasPrefix(line, "Last offset") {
			flds := strings.Fields(line)
			if len(flds) != 5 {
				return 0.0, fmt.Errorf("failed to get ntp offset")
			}
			offset, err = strconv.ParseFloat(flds[3], 64)
			if err != nil {
				return 0.0, err
			}
			return offset * 1000, stratumErr
		}
	}
	return 0.0, fmt.Errorf("failed to get ntp offset")
}
<|endoftext|>


<commit_before>//
// Copyright 2017, Sander van Harmelen
//
// 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 gitlab

import (
	"fmt"
	"net/url"
)

// TagsService handles communication with the tags related methods
// of the GitLab API.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/tags.html
type TagsService struct {
	client *Client
}

// Tag represents a GitLab tag.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/tags.html
type Tag struct {
	Commit  *Commit  `json:"commit"`
	Release *Release `json:"release"`
	Name    string   `json:"name"`
	Message string   `json:"message"`
}

// Release represents a GitLab version release.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/tags.html
type Release struct {
	TagName     string `json:"tag_name"`
	Description string `json:"description"`
}

func (t Tag) String() string {
	return Stringify(t)
}

// ListTagsOptions represents the available ListTags() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/tags.html#list-project-repository-tags
type ListTagsOptions ListOptions

// ListTags gets a list of tags from a project, sorted by name in reverse
// alphabetical order.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/tags.html#list-project-repository-tags
func (s *TagsService) ListTags(pid interface{}, opt *ListTagsOptions, options ...OptionFunc) ([]*Tag, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/repository/tags", url.QueryEscape(project))

	req, err := s.client.NewRequest("GET", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var t []*Tag
	resp, err := s.client.Do(req, &t)
	if err != nil {
		return nil, resp, err
	}

	return t, resp, err
}

// GetTag a specific repository tag determined by its name. It returns 200 together
// with the tag information if the tag exists. It returns 404 if the tag does not exist.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/tags.html#get-a-single-repository-tag
func (s *TagsService) GetTag(pid interface{}, tag string, options ...OptionFunc) (*Tag, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/repository/tags/%s", url.QueryEscape(project), tag)

	req, err := s.client.NewRequest("GET", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	var t *Tag
	resp, err := s.client.Do(req, &t)
	if err != nil {
		return nil, resp, err
	}

	return t, resp, err
}

// CreateTagOptions represents the available CreateTag() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/tags.html#create-a-new-tag
type CreateTagOptions struct {
	TagName            *string `url:"tag_name,omitempty" json:"tag_name,omitempty"`
	Ref                *string `url:"ref,omitempty" json:"ref,omitempty"`
	Message            *string `url:"message,omitempty" json:"message,omitempty"`
	ReleaseDescription *string `url:"release_description:omitempty" json:"release_description,omitempty"`
}

// CreateTag creates a new tag in the repository that points to the supplied ref.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/tags.html#create-a-new-tag
func (s *TagsService) CreateTag(pid interface{}, opt *CreateTagOptions, options ...OptionFunc) (*Tag, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/repository/tags", url.QueryEscape(project))

	req, err := s.client.NewRequest("POST", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	t := new(Tag)
	resp, err := s.client.Do(req, t)
	if err != nil {
		return nil, resp, err
	}

	return t, resp, err
}

// DeleteTag deletes a tag of a repository with given name.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/tags.html#delete-a-tag
func (s *TagsService) DeleteTag(pid interface{}, tag string, options ...OptionFunc) (*Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, err
	}
	u := fmt.Sprintf("projects/%s/repository/tags/%s", url.QueryEscape(project), tag)

	req, err := s.client.NewRequest("DELETE", u, nil, options)
	if err != nil {
		return nil, err
	}

	return s.client.Do(req, nil)
}

// CreateReleaseOptions represents the available CreateRelease() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/tags.html#create-a-new-release
type CreateReleaseOptions struct {
	Description *string `url:"description:omitempty" json:"description,omitempty"`
}

// CreateRelease Add release notes to the existing git tag.
// If there already exists a release for the given tag, status code 409 is returned.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/tags.html#create-a-new-release
func (s *TagsService) CreateRelease(pid interface{}, tag string, opt *CreateReleaseOptions, options ...OptionFunc) (*Release, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/repository/tags/%s/release", url.QueryEscape(project), tag)

	req, err := s.client.NewRequest("POST", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	r := new(Release)
	resp, err := s.client.Do(req, r)
	if err != nil {
		return nil, resp, err
	}

	return r, resp, err
}

// UpdateReleaseOptions represents the available UpdateRelease() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/tags.html#update-a-release
type UpdateReleaseOptions struct {
	Description *string `url:"description:omitempty" json:"description,omitempty"`
}

// UpdateRelease Updates the release notes of a given release.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/tags.html#update-a-release
func (s *TagsService) UpdateRelease(pid interface{}, tag string, opt *UpdateReleaseOptions, options ...OptionFunc) (*Release, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/repository/tags/%s/release", url.QueryEscape(project), tag)

	req, err := s.client.NewRequest("PUT", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	r := new(Release)
	resp, err := s.client.Do(req, r)
	if err != nil {
		return nil, resp, err
	}

	return r, resp, err
}
<commit_msg>fix url query escape tag (#431)<commit_after>//
// Copyright 2017, Sander van Harmelen
//
// 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 gitlab

import (
	"fmt"
	"net/url"
)

// TagsService handles communication with the tags related methods
// of the GitLab API.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/tags.html
type TagsService struct {
	client *Client
}

// Tag represents a GitLab tag.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/tags.html
type Tag struct {
	Commit  *Commit  `json:"commit"`
	Release *Release `json:"release"`
	Name    string   `json:"name"`
	Message string   `json:"message"`
}

// Release represents a GitLab version release.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/tags.html
type Release struct {
	TagName     string `json:"tag_name"`
	Description string `json:"description"`
}

func (t Tag) String() string {
	return Stringify(t)
}

// ListTagsOptions represents the available ListTags() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/tags.html#list-project-repository-tags
type ListTagsOptions ListOptions

// ListTags gets a list of tags from a project, sorted by name in reverse
// alphabetical order.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/tags.html#list-project-repository-tags
func (s *TagsService) ListTags(pid interface{}, opt *ListTagsOptions, options ...OptionFunc) ([]*Tag, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/repository/tags", url.QueryEscape(project))

	req, err := s.client.NewRequest("GET", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var t []*Tag
	resp, err := s.client.Do(req, &t)
	if err != nil {
		return nil, resp, err
	}

	return t, resp, err
}

// GetTag a specific repository tag determined by its name. It returns 200 together
// with the tag information if the tag exists. It returns 404 if the tag does not exist.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/tags.html#get-a-single-repository-tag
func (s *TagsService) GetTag(pid interface{}, tag string, options ...OptionFunc) (*Tag, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/repository/tags/%s", url.QueryEscape(project), url.QueryEscape(tag))

	req, err := s.client.NewRequest("GET", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	var t *Tag
	resp, err := s.client.Do(req, &t)
	if err != nil {
		return nil, resp, err
	}

	return t, resp, err
}

// CreateTagOptions represents the available CreateTag() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/tags.html#create-a-new-tag
type CreateTagOptions struct {
	TagName            *string `url:"tag_name,omitempty" json:"tag_name,omitempty"`
	Ref                *string `url:"ref,omitempty" json:"ref,omitempty"`
	Message            *string `url:"message,omitempty" json:"message,omitempty"`
	ReleaseDescription *string `url:"release_description:omitempty" json:"release_description,omitempty"`
}

// CreateTag creates a new tag in the repository that points to the supplied ref.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/tags.html#create-a-new-tag
func (s *TagsService) CreateTag(pid interface{}, opt *CreateTagOptions, options ...OptionFunc) (*Tag, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/repository/tags", url.QueryEscape(project))

	req, err := s.client.NewRequest("POST", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	t := new(Tag)
	resp, err := s.client.Do(req, t)
	if err != nil {
		return nil, resp, err
	}

	return t, resp, err
}

// DeleteTag deletes a tag of a repository with given name.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/tags.html#delete-a-tag
func (s *TagsService) DeleteTag(pid interface{}, tag string, options ...OptionFunc) (*Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, err
	}
	u := fmt.Sprintf("projects/%s/repository/tags/%s", url.QueryEscape(project), url.QueryEscape(tag))

	req, err := s.client.NewRequest("DELETE", u, nil, options)
	if err != nil {
		return nil, err
	}

	return s.client.Do(req, nil)
}

// CreateReleaseOptions represents the available CreateRelease() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/tags.html#create-a-new-release
type CreateReleaseOptions struct {
	Description *string `url:"description:omitempty" json:"description,omitempty"`
}

// CreateRelease Add release notes to the existing git tag.
// If there already exists a release for the given tag, status code 409 is returned.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/tags.html#create-a-new-release
func (s *TagsService) CreateRelease(pid interface{}, tag string, opt *CreateReleaseOptions, options ...OptionFunc) (*Release, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/repository/tags/%s/release", url.QueryEscape(project), url.QueryEscape(tag))

	req, err := s.client.NewRequest("POST", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	r := new(Release)
	resp, err := s.client.Do(req, r)
	if err != nil {
		return nil, resp, err
	}

	return r, resp, err
}

// UpdateReleaseOptions represents the available UpdateRelease() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/tags.html#update-a-release
type UpdateReleaseOptions struct {
	Description *string `url:"description:omitempty" json:"description,omitempty"`
}

// UpdateRelease Updates the release notes of a given release.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/tags.html#update-a-release
func (s *TagsService) UpdateRelease(pid interface{}, tag string, opt *UpdateReleaseOptions, options ...OptionFunc) (*Release, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/repository/tags/%s/release", url.QueryEscape(project), url.QueryEscape(tag))

	req, err := s.client.NewRequest("PUT", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	r := new(Release)
	resp, err := s.client.Do(req, r)
	if err != nil {
		return nil, resp, err
	}

	return r, resp, err
}
<|endoftext|>


<commit_before>package main

// TagsService is the service for accessing Tag-related calls from the
// Pinboard API.
type TagsService struct {
	client *Client
}

// TODO
//
// https://pinboard.in/api#tags_get
func (s *TagsService) Get() {
}

// TODO
//
// https://pinboard.in/api#tags_delete
func (s *TagsService) Delete() {
}

// TODO
//
// https://pinboard.in/api#tags_rename
func (s *TagsService) Rename() {
}
<commit_msg>Make package for tags.go pin.<commit_after>package pin

// TagsService is the service for accessing Tag-related calls from the
// Pinboard API.
type TagsService struct {
	client *Client
}

// TODO
//
// https://pinboard.in/api#tags_get
func (s *TagsService) Get() {
}

// TODO
//
// https://pinboard.in/api#tags_delete
func (s *TagsService) Delete() {
}

// TODO
//
// https://pinboard.in/api#tags_rename
func (s *TagsService) Rename() {
}
<|endoftext|>


<commit_before>package sdk

import (
	"encoding/json"
	"fmt"
	"net/http"
	"regexp"
	"strings"
)

// Project represent a team with group of users and pipelines
type Project struct {
	ID            int64                 `json:"-"`
	Key           string                `json:"key"`
	Name          string                `json:"name"`
	Pipelines     []Pipeline            `json:"pipelines,omitempty"`
	Applications  []Application         `json:"applications,omitempty"`
	ProjectGroups []GroupPermission     `json:"groups,omitempty"`
	Variable      []Variable            `json:"variables,omitempty"`
	Environments  []Environment         `json:"environments,omitempty"`
	Permission    int                   `json:"permission"`
	LastModified  int64                 `json:"last_modified"`
	ReposManager  []RepositoriesManager `json:"repositories_manager"`
}

//ProjectLastUpdates update times of project, application and pipelines
type ProjectLastUpdates struct {
	Key          string `json:"key"`
	LastModified int64  `json:"last_modified"`
	Applications []struct {
		Name         string `json:"name"`
		LastModified int64  `json:"last_modified"`
	} `json:"applications"`
	Pipelines []struct {
		Name         string `json:"name"`
		LastModified int64  `json:"last_modified"`
	} `json:"pipelines"`
}

// ProjectKeyPattern  pattern for project key
const ProjectKeyPattern = "^[A-Z0-9]{1,}$"

// NewProject instanciate a new NewProject
func NewProject(key string) *Project {
	p := &Project{
		Key: key,
	}
	return p
}

// JSON return the marshalled string of Project object
func (p *Project) JSON() string {

	data, err := json.Marshal(p)
	if err != nil {
		fmt.Printf("Project.JSON: cannot marshal: %s\n", err)
		return ""
	}

	return string(data)
}

// FromJSON unmarshal given json data into Project object
func (p *Project) FromJSON(data []byte) (*Project, error) {
	return p, json.Unmarshal(data, &p)
}

// RemoveProject call api to delete a project
func RemoveProject(key string) error {

	url := fmt.Sprintf("/project/%s", key)
	data, code, err := Request("DELETE", url, nil)
	if err != nil {
		return err
	}

	if code != http.StatusOK {
		return fmt.Errorf("Error [%d]: %s", code, data)
	}
	e := DecodeError(data)
	if e != nil {
		return e
	}

	return nil
}

// RenameProject call API to update project
func RenameProject(key, newName string) error {

	p := NewProject(key)
	p.Name = newName

	data, err := json.MarshalIndent(p, " ", " ")
	if err != nil {
		return err
	}

	url := fmt.Sprintf("/project/%s", key)
	data, code, err := Request("PUT", url, data)
	if err != nil {
		return err
	}

	if code != http.StatusCreated && code != http.StatusOK {
		return fmt.Errorf("Error [%d]: %s", code, data)
	}
	e := DecodeError(data)
	if e != nil {
		return e
	}

	return nil
}

// AddProject creates a new project available only to creator by default
func AddProject(name, key, groupName string) error {

	regexp := regexp.MustCompile(ProjectKeyPattern)
	if !regexp.MatchString(key) {
		return fmt.Errorf("project key '%s' must contain only upper-case alphanumerical characters", key)
	}

	p := NewProject(key)
	p.Name = name

	group := Group{Name: groupName}
	p.ProjectGroups = append(p.ProjectGroups, GroupPermission{Group: group, Permission: 7})

	data, err := json.MarshalIndent(p, " ", " ")
	if err != nil {
		return err
	}

	data, code, err := Request("POST", "/project", data)
	if err != nil {
		return err
	}

	if code == 409 {
		return fmt.Errorf("Conflict: please use another project key")
	}

	if code != http.StatusCreated && code != http.StatusOK {
		return fmt.Errorf("Error [%d]: %s", code, data)
	}
	e := DecodeError(data)
	if e != nil {
		return e
	}

	return nil
}

// RemoveGroupFromProject  call api to remove a group from the project
func RemoveGroupFromProject(projectKey, groupname string) error {

	path := fmt.Sprintf("/project/%s/group/%s", projectKey, groupname)
	data, code, err := Request("DELETE", path, nil)
	if err != nil {
		return err
	}

	if code != http.StatusCreated && code != http.StatusOK {
		return fmt.Errorf("Error [%d]: %s", code, data)
	}
	e := DecodeError(data)
	if e != nil {
		return e
	}

	return nil
}

// UpdateGroupInProject  call api to update group permission on project
func UpdateGroupInProject(projectKey, groupname string, permission int) error {

	if permission < 4 || permission > 7 {
		return fmt.Errorf("Permission should be between 4-7 \n")
	}

	groupProject := GroupPermission{
		Group: Group{
			Name: groupname,
		},
		Permission: permission,
	}

	data, err := json.MarshalIndent(groupProject, " ", " ")
	if err != nil {
		return err
	}

	path := fmt.Sprintf("/project/%s/group/%s", projectKey, groupname)
	data, code, err := Request("PUT", path, data)
	if err != nil {
		return err
	}

	if code != http.StatusCreated && code != http.StatusOK {
		return fmt.Errorf("Error [%d]: %s", code, data)
	}
	e := DecodeError(data)
	if e != nil {
		return e
	}

	return nil
}

// AddGroupInProject  add a group in a project
func AddGroupInProject(projectKey, groupname string, permission int, recursive bool) error {

	if permission < 4 || permission > 7 {
		return fmt.Errorf("Permission should be between 4-7 \n")
	}

	groupProject := GroupPermission{
		Group: Group{
			Name: groupname,
		},
		Permission: permission,
		Recursive:  recursive,
	}

	data, err := json.MarshalIndent(groupProject, " ", " ")
	if err != nil {
		return err
	}

	path := fmt.Sprintf("/project/%s/group", projectKey)
	data, code, err := Request("POST", path, data)
	if err != nil {
		return err
	}

	if code != http.StatusCreated && code != http.StatusOK {
		return fmt.Errorf("Error [%d]: %s", code, data)
	}
	e := DecodeError(data)
	if e != nil {
		return e
	}

	return nil
}

// ShowVariableInProject  show variables for a project
func ShowVariableInProject(projectKey string) ([]Variable, error) {

	path := fmt.Sprintf("/project/%s/variable", projectKey)
	data, code, err := Request("GET", path, nil)
	if err != nil {
		return nil, err
	}

	if code != http.StatusCreated && code != http.StatusOK {
		return nil, fmt.Errorf("Error [%d]: %s", code, data)
	}

	var variables []Variable
	err = json.Unmarshal(data, &variables)
	if err != nil {
		return nil, err
	}
	return variables, nil
}

// AddProjectVariable adds a project wide variable
func AddProjectVariable(key, name, val string, t VariableType) error {
	return AddVariableInProject(key, name, val, string(t))
}

// AddVariableInProject  add a variable in a project
func AddVariableInProject(projectKey, varName, varValue, varType string) error {

	newVar := Variable{
		Name:  varName,
		Value: varValue,
		Type:  VariableTypeFromString(varType),
	}

	data, err := json.Marshal(newVar)
	if err != nil {
		return err
	}

	path := fmt.Sprintf("/project/%s/variable/%s", projectKey, varName)
	data, code, err := Request("POST", path, data)
	if err != nil {
		return err
	}

	if code != http.StatusCreated && code != http.StatusOK {
		return fmt.Errorf("Error [%d]: %s", code, data)
	}
	e := DecodeError(data)
	if e != nil {
		return e
	}

	return nil
}

func GetVariableInProject(projectKey, name string) (*Variable, error) {
	var v Variable

	path := fmt.Sprintf("/project/%s/variable/%s", projectKey, name)
	data, code, err := Request("GET", path, nil)
	if err != nil {
		return nil, err
	}

	if code != http.StatusCreated && code != http.StatusOK {
		return nil, fmt.Errorf("Error [%d]: %s", code, data)
	}
	e := DecodeError(data)
	if e != nil {
		return nil, e
	}

	err = json.Unmarshal(data, &v)
	if err != nil {
		return nil, err
	}

	return &v, nil
}

// UpdateVariableInProject  update a variable in a project
func UpdateVariableInProject(projectKey, oldName, varName, varValue, varType string) error {
	oldVar, err := GetVariableInProject(projectKey, oldName)
	if err != nil {
		return err
	}

	newVar := Variable{
		ID:    oldVar.ID,
		Name:  varName,
		Value: varValue,
		Type:  VariableTypeFromString(varType),
	}

	data, err := json.Marshal(newVar)
	if err != nil {
		return err
	}

	path := fmt.Sprintf("/project/%s/variable/%s", projectKey, varName)
	data, code, err := Request("PUT", path, data)
	if err != nil {
		return err
	}

	if code != http.StatusCreated && code != http.StatusOK {
		return fmt.Errorf("Error [%d]: %s", code, data)
	}
	e := DecodeError(data)
	if e != nil {
		return e
	}

	return nil
}

// RemoveVariableFromProject  remove a variable from a project
func RemoveVariableFromProject(projectKey, varName string) error {
	path := fmt.Sprintf("/project/%s/variable/%s", projectKey, varName)
	data, code, err := Request("DELETE", path, nil)
	if err != nil {
		return err
	}

	if code != http.StatusCreated && code != http.StatusOK {
		return fmt.Errorf("Error [%d]: %s", code, data)
	}
	e := DecodeError(data)
	if e != nil {
		return e
	}

	return nil
}

// Mod is the functional parameter type of sdk function to alter their behavior
type Mod func(s string) string

// WithApplicationStatus is a func parameter of ListProject
func WithApplicationStatus() Mod {
	f := func(s string) string {
		return s + "&applicationStatus=true"
	}

	return f
}

// WithEnvironments is a func parameter of ListProject
func WithEnvironments() Mod {
	f := func(s string) string {
		return s + "&environment=true"
	}

	return f
}

// WithPipelines is a func parameter of ListProject
func WithPipelines() Mod {
	f := func(s string) string {
		return s + "&pipeline=true"
	}

	return f
}

// WithEverything is a func parameter of ListProject
func WithEverything() Mod {
	f := func(s string) string {
		return s + "&pipeline=true&environment=true&applicationStatus=true"
	}

	return f
}

// ListProject returns all available project to caller
func ListProject(mods ...Mod) ([]Project, error) {
	uri := "/project?gzip=true&application=true"
	for _, m := range mods {
		uri = m(uri)
	}

	data, code, err := Request("GET", uri, nil)
	if err != nil {
		return nil, err
	}

	if code != http.StatusOK {
		return nil, fmt.Errorf("Error [%d]: %s", code, data)
	}

	var projects []Project
	err = json.Unmarshal(data, &projects)
	if err != nil {
		return nil, err
	}

	return projects, nil
}

// WithApplicationHistory is a functional parameter of GetProject
func WithApplicationHistory(length int) Mod {
	f := func(s string) string {
		if strings.HasSuffix(s, "?") {
			return fmt.Sprintf("%sapplicationHistory=%d", s, length)
		}
		return fmt.Sprintf("%s&applicationHistory=%d", s, length)
	}

	return f
}

// GetProject retrieves project informations from CDS
func GetProject(pk string, mods ...Mod) (Project, error) {
	var p Project
	path := fmt.Sprintf("/project/%s?gzip=true", pk)
	for _, f := range mods {
		path = f(path)
	}

	data, _, err := Request("GET", path, nil)
	if err != nil {
		return p, err
	}

	err = json.Unmarshal(data, &p)
	if err != nil {
		return p, err
	}

	return p, nil
}

// DeleteProject removes a project and all its pipeline from CDS
func DeleteProject(pk string) error {

	path := fmt.Sprintf("/project/%s", pk)
	_, _, err := Request("DELETE", path, nil)
	if err != nil {
		return err
	}

	return nil
}
<commit_msg>fix godoc<commit_after>package sdk

import (
	"encoding/json"
	"fmt"
	"net/http"
	"regexp"
	"strings"
)

// Project represent a team with group of users and pipelines
type Project struct {
	ID            int64                 `json:"-"`
	Key           string                `json:"key"`
	Name          string                `json:"name"`
	Pipelines     []Pipeline            `json:"pipelines,omitempty"`
	Applications  []Application         `json:"applications,omitempty"`
	ProjectGroups []GroupPermission     `json:"groups,omitempty"`
	Variable      []Variable            `json:"variables,omitempty"`
	Environments  []Environment         `json:"environments,omitempty"`
	Permission    int                   `json:"permission"`
	LastModified  int64                 `json:"last_modified"`
	ReposManager  []RepositoriesManager `json:"repositories_manager"`
}

//ProjectLastUpdates update times of project, application and pipelines
type ProjectLastUpdates struct {
	Key          string `json:"key"`
	LastModified int64  `json:"last_modified"`
	Applications []struct {
		Name         string `json:"name"`
		LastModified int64  `json:"last_modified"`
	} `json:"applications"`
	Pipelines []struct {
		Name         string `json:"name"`
		LastModified int64  `json:"last_modified"`
	} `json:"pipelines"`
}

// ProjectKeyPattern  pattern for project key
const ProjectKeyPattern = "^[A-Z0-9]{1,}$"

// NewProject instanciate a new NewProject
func NewProject(key string) *Project {
	p := &Project{
		Key: key,
	}
	return p
}

// JSON return the marshalled string of Project object
func (p *Project) JSON() string {

	data, err := json.Marshal(p)
	if err != nil {
		fmt.Printf("Project.JSON: cannot marshal: %s\n", err)
		return ""
	}

	return string(data)
}

// FromJSON unmarshal given json data into Project object
func (p *Project) FromJSON(data []byte) (*Project, error) {
	return p, json.Unmarshal(data, &p)
}

// RemoveProject call api to delete a project
func RemoveProject(key string) error {

	url := fmt.Sprintf("/project/%s", key)
	data, code, err := Request("DELETE", url, nil)
	if err != nil {
		return err
	}

	if code != http.StatusOK {
		return fmt.Errorf("Error [%d]: %s", code, data)
	}
	e := DecodeError(data)
	if e != nil {
		return e
	}

	return nil
}

// RenameProject call API to update project
func RenameProject(key, newName string) error {

	p := NewProject(key)
	p.Name = newName

	data, err := json.MarshalIndent(p, " ", " ")
	if err != nil {
		return err
	}

	url := fmt.Sprintf("/project/%s", key)
	data, code, err := Request("PUT", url, data)
	if err != nil {
		return err
	}

	if code != http.StatusCreated && code != http.StatusOK {
		return fmt.Errorf("Error [%d]: %s", code, data)
	}
	e := DecodeError(data)
	if e != nil {
		return e
	}

	return nil
}

// AddProject creates a new project available only to creator by default
func AddProject(name, key, groupName string) error {

	regexp := regexp.MustCompile(ProjectKeyPattern)
	if !regexp.MatchString(key) {
		return fmt.Errorf("project key '%s' must contain only upper-case alphanumerical characters", key)
	}

	p := NewProject(key)
	p.Name = name

	group := Group{Name: groupName}
	p.ProjectGroups = append(p.ProjectGroups, GroupPermission{Group: group, Permission: 7})

	data, err := json.MarshalIndent(p, " ", " ")
	if err != nil {
		return err
	}

	data, code, err := Request("POST", "/project", data)
	if err != nil {
		return err
	}

	if code == 409 {
		return fmt.Errorf("Conflict: please use another project key")
	}

	if code != http.StatusCreated && code != http.StatusOK {
		return fmt.Errorf("Error [%d]: %s", code, data)
	}
	e := DecodeError(data)
	if e != nil {
		return e
	}

	return nil
}

// RemoveGroupFromProject  call api to remove a group from the project
func RemoveGroupFromProject(projectKey, groupname string) error {

	path := fmt.Sprintf("/project/%s/group/%s", projectKey, groupname)
	data, code, err := Request("DELETE", path, nil)
	if err != nil {
		return err
	}

	if code != http.StatusCreated && code != http.StatusOK {
		return fmt.Errorf("Error [%d]: %s", code, data)
	}
	e := DecodeError(data)
	if e != nil {
		return e
	}

	return nil
}

// UpdateGroupInProject  call api to update group permission on project
func UpdateGroupInProject(projectKey, groupname string, permission int) error {

	if permission < 4 || permission > 7 {
		return fmt.Errorf("Permission should be between 4-7 \n")
	}

	groupProject := GroupPermission{
		Group: Group{
			Name: groupname,
		},
		Permission: permission,
	}

	data, err := json.MarshalIndent(groupProject, " ", " ")
	if err != nil {
		return err
	}

	path := fmt.Sprintf("/project/%s/group/%s", projectKey, groupname)
	data, code, err := Request("PUT", path, data)
	if err != nil {
		return err
	}

	if code != http.StatusCreated && code != http.StatusOK {
		return fmt.Errorf("Error [%d]: %s", code, data)
	}
	e := DecodeError(data)
	if e != nil {
		return e
	}

	return nil
}

// AddGroupInProject  add a group in a project
func AddGroupInProject(projectKey, groupname string, permission int, recursive bool) error {

	if permission < 4 || permission > 7 {
		return fmt.Errorf("Permission should be between 4-7 \n")
	}

	groupProject := GroupPermission{
		Group: Group{
			Name: groupname,
		},
		Permission: permission,
		Recursive:  recursive,
	}

	data, err := json.MarshalIndent(groupProject, " ", " ")
	if err != nil {
		return err
	}

	path := fmt.Sprintf("/project/%s/group", projectKey)
	data, code, err := Request("POST", path, data)
	if err != nil {
		return err
	}

	if code != http.StatusCreated && code != http.StatusOK {
		return fmt.Errorf("Error [%d]: %s", code, data)
	}
	e := DecodeError(data)
	if e != nil {
		return e
	}

	return nil
}

// ShowVariableInProject  show variables for a project
func ShowVariableInProject(projectKey string) ([]Variable, error) {

	path := fmt.Sprintf("/project/%s/variable", projectKey)
	data, code, err := Request("GET", path, nil)
	if err != nil {
		return nil, err
	}

	if code != http.StatusCreated && code != http.StatusOK {
		return nil, fmt.Errorf("Error [%d]: %s", code, data)
	}

	var variables []Variable
	err = json.Unmarshal(data, &variables)
	if err != nil {
		return nil, err
	}
	return variables, nil
}

// AddProjectVariable adds a project wide variable
func AddProjectVariable(key, name, val string, t VariableType) error {
	return AddVariableInProject(key, name, val, string(t))
}

// AddVariableInProject  add a variable in a project
func AddVariableInProject(projectKey, varName, varValue, varType string) error {

	newVar := Variable{
		Name:  varName,
		Value: varValue,
		Type:  VariableTypeFromString(varType),
	}

	data, err := json.Marshal(newVar)
	if err != nil {
		return err
	}

	path := fmt.Sprintf("/project/%s/variable/%s", projectKey, varName)
	data, code, err := Request("POST", path, data)
	if err != nil {
		return err
	}

	if code != http.StatusCreated && code != http.StatusOK {
		return fmt.Errorf("Error [%d]: %s", code, data)
	}
	e := DecodeError(data)
	if e != nil {
		return e
	}

	return nil
}

// GetVariableInProject Get a variable by her name in the given project
func GetVariableInProject(projectKey, name string) (*Variable, error) {
	var v Variable

	path := fmt.Sprintf("/project/%s/variable/%s", projectKey, name)
	data, code, err := Request("GET", path, nil)
	if err != nil {
		return nil, err
	}

	if code != http.StatusCreated && code != http.StatusOK {
		return nil, fmt.Errorf("Error [%d]: %s", code, data)
	}
	e := DecodeError(data)
	if e != nil {
		return nil, e
	}

	err = json.Unmarshal(data, &v)
	if err != nil {
		return nil, err
	}

	return &v, nil
}

// UpdateVariableInProject  update a variable in a project
func UpdateVariableInProject(projectKey, oldName, varName, varValue, varType string) error {
	oldVar, err := GetVariableInProject(projectKey, oldName)
	if err != nil {
		return err
	}

	newVar := Variable{
		ID:    oldVar.ID,
		Name:  varName,
		Value: varValue,
		Type:  VariableTypeFromString(varType),
	}

	data, err := json.Marshal(newVar)
	if err != nil {
		return err
	}

	path := fmt.Sprintf("/project/%s/variable/%s", projectKey, varName)
	data, code, err := Request("PUT", path, data)
	if err != nil {
		return err
	}

	if code != http.StatusCreated && code != http.StatusOK {
		return fmt.Errorf("Error [%d]: %s", code, data)
	}
	e := DecodeError(data)
	if e != nil {
		return e
	}

	return nil
}

// RemoveVariableFromProject  remove a variable from a project
func RemoveVariableFromProject(projectKey, varName string) error {
	path := fmt.Sprintf("/project/%s/variable/%s", projectKey, varName)
	data, code, err := Request("DELETE", path, nil)
	if err != nil {
		return err
	}

	if code != http.StatusCreated && code != http.StatusOK {
		return fmt.Errorf("Error [%d]: %s", code, data)
	}
	e := DecodeError(data)
	if e != nil {
		return e
	}

	return nil
}

// Mod is the functional parameter type of sdk function to alter their behavior
type Mod func(s string) string

// WithApplicationStatus is a func parameter of ListProject
func WithApplicationStatus() Mod {
	f := func(s string) string {
		return s + "&applicationStatus=true"
	}

	return f
}

// WithEnvironments is a func parameter of ListProject
func WithEnvironments() Mod {
	f := func(s string) string {
		return s + "&environment=true"
	}

	return f
}

// WithPipelines is a func parameter of ListProject
func WithPipelines() Mod {
	f := func(s string) string {
		return s + "&pipeline=true"
	}

	return f
}

// WithEverything is a func parameter of ListProject
func WithEverything() Mod {
	f := func(s string) string {
		return s + "&pipeline=true&environment=true&applicationStatus=true"
	}

	return f
}

// ListProject returns all available project to caller
func ListProject(mods ...Mod) ([]Project, error) {
	uri := "/project?gzip=true&application=true"
	for _, m := range mods {
		uri = m(uri)
	}

	data, code, err := Request("GET", uri, nil)
	if err != nil {
		return nil, err
	}

	if code != http.StatusOK {
		return nil, fmt.Errorf("Error [%d]: %s", code, data)
	}

	var projects []Project
	err = json.Unmarshal(data, &projects)
	if err != nil {
		return nil, err
	}

	return projects, nil
}

// WithApplicationHistory is a functional parameter of GetProject
func WithApplicationHistory(length int) Mod {
	f := func(s string) string {
		if strings.HasSuffix(s, "?") {
			return fmt.Sprintf("%sapplicationHistory=%d", s, length)
		}
		return fmt.Sprintf("%s&applicationHistory=%d", s, length)
	}

	return f
}

// GetProject retrieves project informations from CDS
func GetProject(pk string, mods ...Mod) (Project, error) {
	var p Project
	path := fmt.Sprintf("/project/%s?gzip=true", pk)
	for _, f := range mods {
		path = f(path)
	}

	data, _, err := Request("GET", path, nil)
	if err != nil {
		return p, err
	}

	err = json.Unmarshal(data, &p)
	if err != nil {
		return p, err
	}

	return p, nil
}

// DeleteProject removes a project and all its pipeline from CDS
func DeleteProject(pk string) error {

	path := fmt.Sprintf("/project/%s", pk)
	_, _, err := Request("DELETE", path, nil)
	if err != nil {
		return err
	}

	return nil
}
<|endoftext|>