<commit_before>// Copyright 2019 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

import (
	"context"
	"time"

	"go.chromium.org/luci/auth"
	"go.chromium.org/luci/buildbucket"
	bbpb "go.chromium.org/luci/buildbucket/proto"
	"go.chromium.org/luci/buildbucket/protoutil"
	"go.chromium.org/luci/common/clock"
	"go.chromium.org/luci/common/errors"
	"go.chromium.org/luci/common/lhttp"
	"go.chromium.org/luci/common/logging"
	"go.chromium.org/luci/common/retry"
	"go.chromium.org/luci/common/sync/dispatcher"
	"go.chromium.org/luci/common/sync/dispatcher/buffer"
	"go.chromium.org/luci/grpc/prpc"
	"golang.org/x/time/rate"
	"google.golang.org/genproto/protobuf/field_mask"
	"google.golang.org/grpc/metadata"
)

// options for the dispatcher.Channel
func channelOpts(ctx context.Context) *dispatcher.Options {
	return &dispatcher.Options{
		QPSLimit: rate.NewLimiter(1, 1),
		Buffer: buffer.Options{
			BatchSize:    1,
			MaxLeases:    1,
			FullBehavior: &buffer.DropOldestBatch{MaxLiveItems: 1},
			Retry: func() retry.Iterator {
				return &retry.ExponentialBackoff{
					Limited: retry.Limited{
						Delay:    200 * time.Millisecond, // initial delay
						Retries:  -1,
						MaxTotal: 5 * time.Minute,
					},
					Multiplier: 1.2,
					MaxDelay:   30 * time.Second,
				}
			},
		},
		DropFn:  dispatcher.DropFnSummarized(ctx, rate.NewLimiter(.1, 1)),
		ErrorFn: dispatcher.ErrorFnQuiet,
	}
}

func newBuildsClient(ctx context.Context, infraOpts *bbpb.BuildInfra_Buildbucket) (ret dispatcher.Channel, err error) {
	hostname := infraOpts.GetHostname()
	if hostname == "" {
		err = errors.New("missing hostname in build.infra.buildbucket")
		return
	}

	var sendFn dispatcher.SendFn
	if hostname == "no-upload" {
		// led will set the hostname explicitly to "no-upload", and bbagent will
		// behave exactly as the normal bbagent, except the builds won't be uploaded
		// anywhere.
		logging.Infof(ctx, "no-upload mode enabled; making dummy buildbucket client")
		sendFn = func(b *buffer.Batch) error {
			return nil // noop
		}
	} else {
		opts := prpc.DefaultOptions()
		opts.Insecure = lhttp.IsLocalHost(hostname)
		opts.Retry = nil // luciexe handles retries itself.

		prpcClient := &prpc.Client{
			Host:    hostname,
			Options: opts,
		}

		var secrets *bbpb.BuildSecrets
		secrets, err = readBuildSecrets(ctx)
		if err != nil {
			return
		}

		prpcClient.C, err = auth.NewAuthenticator(ctx, auth.SilentLogin, auth.Options{
			MonitorAs: "bbagent/buildbucket",
		}).Client()
		if err != nil {
			return
		}

		// TODO(iannucci): Exchange secret build token+nonce for a running build token
		// here to confirm that:
		//   * We're the ONLY ones servicing this build (detect duplicate Swarming
		//     tasks). Failure to exchange the token would let us know that we got
		//     double-booked.
		//   * Auth is properly configured for buildbucket before we start running the
		//     user code.
		sendFn = mkSendFn(ctx, secrets, bbpb.NewBuildsPRPCClient(prpcClient))
	}

	return dispatcher.NewChannel(ctx, channelOpts(ctx), sendFn)
}

func mkSendFn(ctx context.Context, secrets *bbpb.BuildSecrets, client bbpb.BuildsClient) dispatcher.SendFn {
	return func(b *buffer.Batch) error {
		ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs(buildbucket.BuildTokenHeader, secrets.BuildToken))

		var req *bbpb.UpdateBuildRequest
		var final bool

		if b.Meta != nil {
			req = b.Meta.(*bbpb.UpdateBuildRequest)
			final = protoutil.IsEnded(req.Build.Status)
		} else {
			build := b.Data[0].(*bbpb.Build)
			req = &bbpb.UpdateBuildRequest{
				Build: build,
				UpdateMask: &field_mask.FieldMask{
					Paths: []string{
						"build.steps",
						"build.output",
						"build.summary_markdown",
					},
				},
			}
			final = protoutil.IsEnded(build.Status)
			if final {
				if build.Status != bbpb.Status_SUCCESS {
					req.UpdateMask.Paths = append(req.UpdateMask.Paths, "build.status")
				}
			}
			b.Meta = req
			b.Data[0] = nil
		}

		var timeout time.Duration
		if final {
			timeout = 5 * time.Minute
		} else {
			// Scale the timeout by the number of steps present.
			timeout = time.Duration(len(req.Build.GetSteps())) * (50 * time.Millisecond)
			if timeout < (2 * time.Second) {
				timeout = 2 * time.Second
			}
		}
		tctx, cancel := clock.WithTimeout(ctx, timeout)
		defer cancel()

		_, err := client.UpdateBuild(tctx, req)
		// TODO(iannucci): Always tag errors as transient for the 'final' build
		// update?
		return err
	}
}
<commit_msg>[bbagent] Cap per-upload timeout to a minute.<commit_after>// Copyright 2019 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

import (
	"context"
	"time"

	"go.chromium.org/luci/auth"
	"go.chromium.org/luci/buildbucket"
	bbpb "go.chromium.org/luci/buildbucket/proto"
	"go.chromium.org/luci/buildbucket/protoutil"
	"go.chromium.org/luci/common/clock"
	"go.chromium.org/luci/common/errors"
	"go.chromium.org/luci/common/lhttp"
	"go.chromium.org/luci/common/logging"
	"go.chromium.org/luci/common/retry"
	"go.chromium.org/luci/common/sync/dispatcher"
	"go.chromium.org/luci/common/sync/dispatcher/buffer"
	"go.chromium.org/luci/grpc/prpc"
	"golang.org/x/time/rate"
	"google.golang.org/genproto/protobuf/field_mask"
	"google.golang.org/grpc/metadata"
)

// options for the dispatcher.Channel
func channelOpts(ctx context.Context) *dispatcher.Options {
	return &dispatcher.Options{
		QPSLimit: rate.NewLimiter(1, 1),
		Buffer: buffer.Options{
			BatchSize:    1,
			MaxLeases:    1,
			FullBehavior: &buffer.DropOldestBatch{MaxLiveItems: 1},
			Retry: func() retry.Iterator {
				return &retry.ExponentialBackoff{
					Limited: retry.Limited{
						Delay:    200 * time.Millisecond, // initial delay
						Retries:  -1,
						MaxTotal: 5 * time.Minute,
					},
					Multiplier: 1.2,
					MaxDelay:   30 * time.Second,
				}
			},
		},
		DropFn:  dispatcher.DropFnSummarized(ctx, rate.NewLimiter(.1, 1)),
		ErrorFn: dispatcher.ErrorFnQuiet,
	}
}

func newBuildsClient(ctx context.Context, infraOpts *bbpb.BuildInfra_Buildbucket) (ret dispatcher.Channel, err error) {
	hostname := infraOpts.GetHostname()
	if hostname == "" {
		err = errors.New("missing hostname in build.infra.buildbucket")
		return
	}

	var sendFn dispatcher.SendFn
	if hostname == "no-upload" {
		// led will set the hostname explicitly to "no-upload", and bbagent will
		// behave exactly as the normal bbagent, except the builds won't be uploaded
		// anywhere.
		logging.Infof(ctx, "no-upload mode enabled; making dummy buildbucket client")
		sendFn = func(b *buffer.Batch) error {
			return nil // noop
		}
	} else {
		opts := prpc.DefaultOptions()
		opts.Insecure = lhttp.IsLocalHost(hostname)
		opts.Retry = nil // luciexe handles retries itself.

		prpcClient := &prpc.Client{
			Host:    hostname,
			Options: opts,
		}

		var secrets *bbpb.BuildSecrets
		secrets, err = readBuildSecrets(ctx)
		if err != nil {
			return
		}

		prpcClient.C, err = auth.NewAuthenticator(ctx, auth.SilentLogin, auth.Options{
			MonitorAs: "bbagent/buildbucket",
		}).Client()
		if err != nil {
			return
		}

		// TODO(iannucci): Exchange secret build token+nonce for a running build token
		// here to confirm that:
		//   * We're the ONLY ones servicing this build (detect duplicate Swarming
		//     tasks). Failure to exchange the token would let us know that we got
		//     double-booked.
		//   * Auth is properly configured for buildbucket before we start running the
		//     user code.
		sendFn = mkSendFn(ctx, secrets, bbpb.NewBuildsPRPCClient(prpcClient))
	}

	return dispatcher.NewChannel(ctx, channelOpts(ctx), sendFn)
}

func mkSendFn(ctx context.Context, secrets *bbpb.BuildSecrets, client bbpb.BuildsClient) dispatcher.SendFn {
	return func(b *buffer.Batch) error {
		ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs(buildbucket.BuildTokenHeader, secrets.BuildToken))

		var req *bbpb.UpdateBuildRequest
		var final bool

		if b.Meta != nil {
			req = b.Meta.(*bbpb.UpdateBuildRequest)
			final = protoutil.IsEnded(req.Build.Status)
		} else {
			build := b.Data[0].(*bbpb.Build)
			req = &bbpb.UpdateBuildRequest{
				Build: build,
				UpdateMask: &field_mask.FieldMask{
					Paths: []string{
						"build.steps",
						"build.output",
						"build.summary_markdown",
					},
				},
			}
			final = protoutil.IsEnded(build.Status)
			if final {
				if build.Status != bbpb.Status_SUCCESS {
					req.UpdateMask.Paths = append(req.UpdateMask.Paths, "build.status")
				}
			}
			b.Meta = req
			b.Data[0] = nil
		}

		var timeout time.Duration
		if final {
			timeout = 5 * time.Minute
		} else {
			// Scale the timeout by the number of steps present, bounding it between
			// 2s and 1m (only the final status gets > 1m timeout, which is probably
			// futile anyway, since this RPC is currently serviced by an AppEngine
			// frontend instance which is capped at a 60s request time).
			timeout = time.Duration(len(req.Build.GetSteps())) * (50 * time.Millisecond)
			if timeout < (2 * time.Second) {
				timeout = 2 * time.Second
			} else if timeout > time.Minute {
				timeout = time.Minute
			}
		}
		tctx, cancel := clock.WithTimeout(ctx, timeout)
		defer cancel()

		_, err := client.UpdateBuild(tctx, req)
		// TODO(iannucci): Always tag errors as transient for the 'final' build
		// update?
		return err
	}
}
<|endoftext|>


<commit_before>package soaap

import "fmt"

// A function that can produce a CallGraph when given a CallGraph.
type Analyser func(*CallGraph) (CallGraph, error)

//
// Apply an analysis to a CallGraph using an already-loaded Results file.
//
// Possible analyses include:
//
//   * "+graphtype": union with "graphtype" from the Results
//   * "^graphtype": intersection with "graphtype" from the Results
//   * ".graphtype": union-of-intersection with "graphtype"
//   * ":spec": filter leaf nodes according to "spec":
//     * "+name": keep a leaf node
//     * "*": keep all current leaf nodes (used together with '-', below)
//     * "-name": remove a leaf node
//     * examples:
//       * ":ALL:-foo:-bar" keeps all leaf nodes except "foo" and "bar"
//       * ":+foo:+bar" keeps only "foo" and "bar" leaf nodes
//
// where "graphtype" can be:
//   * vuln: the callgraph of previously-vulnerable code
//   * privaccess: the call-and-data-flow graph of access to private data
//
func ApplyAnalysis(spec string, cg *CallGraph, results *Results,
	depth int, report func(string)) (CallGraph, error) {

	union := func(g *CallGraph) (CallGraph, error) {
		err := cg.Union(*g)
		return *cg, err
	}

	switch spec[0] {
	case '+':
		report("Adding " + spec[1:])
		return extractAndCombine(spec[1:], results, report, union)

	case '^':
		report(fmt.Sprintf("Intersecting (depth %d) with %s", depth, spec[1:]))
		return extractAndCombine(spec[1:], results, report,
			func(g *CallGraph) (CallGraph, error) {
				return cg.Intersect(*g, depth, true)
			})

	case '.':
		report(fmt.Sprintf("Adding intersection (depth %d) with %s", depth, spec[1:]))
		return extractAndCombine(spec[1:], results, report,
			func(g *CallGraph) (CallGraph, error) {
				err := cg.AddIntersecting(*g, depth)
				return *cg, err
			})

	default:
		report("Adding " + spec)
		return extractAndCombine(spec, results, report, union)
	}
}

func extractAndCombine(graphname string, r *Results, report func(string),
	analyse Analyser) (CallGraph, error) {

	g, err := r.ExtractGraph(graphname, report)
	if err != nil {
		return CallGraph{}, err
	}

	nodes, edges, flows := g.Size()
	report(fmt.Sprintf("'%s': %d nodes, %d edges, %d flows",
		graphname, nodes, edges, flows))

	return analyse(&g)
}
<commit_msg>Add ability to filter graphs by leaf node name.<commit_after>package soaap

import (
	"fmt"
	"regexp"
	"strings"
)

// A function that can produce a CallGraph when given a CallGraph.
type Analyser func(*CallGraph) (CallGraph, error)

//
// Apply an analysis to a CallGraph using an already-loaded Results file.
//
// Possible analyses include:
//
//   * "+graphtype": union with "graphtype" from the Results
//   * "^graphtype": intersection with "graphtype" from the Results
//   * ".graphtype": union-of-intersection with "graphtype"
//   * ":spec": filter leaf nodes according to "spec" (see Filter)
//
// where "graphtype" can be:
//   * vuln: the callgraph of previously-vulnerable code
//   * privaccess: the call-and-data-flow graph of access to private data
//
func ApplyAnalysis(spec string, cg *CallGraph, results *Results,
	depth int, report func(string)) (CallGraph, error) {

	union := func(g *CallGraph) (CallGraph, error) {
		err := cg.Union(*g)
		return *cg, err
	}

	switch spec[0] {
	case '+':
		report("Adding " + spec[1:])
		return extractAndCombine(spec[1:], results, report, union)

	case '^':
		report(fmt.Sprintf("Intersecting (depth %d) with %s", depth, spec[1:]))
		return extractAndCombine(spec[1:], results, report,
			func(g *CallGraph) (CallGraph, error) {
				return cg.Intersect(*g, depth, true)
			})

	case '.':
		report(fmt.Sprintf("Adding intersection (depth %d) with %s", depth, spec[1:]))
		return extractAndCombine(spec[1:], results, report,
			func(g *CallGraph) (CallGraph, error) {
				err := cg.AddIntersecting(*g, depth)
				return *cg, err
			})

	case ':':
		report("Filtering with '" + spec + "'")
		return Filter(*cg, spec[1:])

	default:
		report("Adding " + spec)
		return extractAndCombine(spec, results, report, union)
	}
}

func extractAndCombine(graphname string, r *Results, report func(string),
	analyse Analyser) (CallGraph, error) {

	g, err := r.ExtractGraph(graphname, report)
	if err != nil {
		return CallGraph{}, err
	}

	nodes, edges, flows := g.Size()
	report(fmt.Sprintf("'%s': %d nodes, %d edges, %d flows",
		graphname, nodes, edges, flows))

	return analyse(&g)
}

//
// Filter a graph according to a colon-separated list of filter specifications,
// where each element can be:
//
//  * "*": add all leaf nodes in the graph
//  * "+regex": keep leaf nodes that match a pattern
//  * "-regex": remove leaf nodes that match a pattern
//
// Examples:
//
//  ":*:-foo:-bar" keeps all leaf nodes except "foo" and "bar"
//
//  ":+.*foo.*:+.*bar.*" keeps only those leaf nodes (plus ancestors)
//  with "foo" and "bar" in their names
//
func Filter(cg CallGraph, spec string) (CallGraph, error) {
	leaves := make(strset)

	for _, s := range strings.Split(spec, ":") {
		if s == "*" {
			leaves = leaves.Union(cg.leaves)
			continue
		}

		var add bool
		switch s[0] {
		case '+':
			add = true

		case '-':
			add = false

		default:
			return CallGraph{},
				fmt.Errorf("unknown filter clause: '%s'", s)
		}

		pattern, err := regexp.Compile(s[1:])
		if err != nil {
			return cg, err
		}

		for leaf := range cg.leaves {
			if pattern.MatchString(leaf) {
				if add {
					leaves.Add(leaf)
				} else {
					leaves.Remove(leaf)
				}
			}
		}
	}

	keep := make(strset)
	for leaf := range leaves {
		keep = keep.Union(cg.Ancestors(leaf, -1))
	}

	return cg.Filter(keep), nil
}
<|endoftext|>


<commit_before>package repository

import (
	"errors"
	"time"

	"github.com/cj123/calendar/model"
	"github.com/jinzhu/gorm"
)

type NoteRepository struct {
	Repository
}

func NewNoteRepository(db *gorm.DB) *NoteRepository {
	return &NoteRepository{
		Repository: {
			db: db,
		},
	}
}

func (r *NoteRepository) Model() Model {
	return &model.Note{}
}

func (r *NoteRepository) Create(m Model) error {
	if note, ok := m.(*model.Note); ok {
		return r.db.Create(&note).Error
	}

	return errors.New("invalid note type")
}

func (r *NoteRepository) FindByID(id interface{}) (model.Note, error) {
	var note model.Note

	err := r.db.Preload("DeletedDates").First(&note, "id = ?", id).Error

	return note, err
}

func (r *NoteRepository) modelSlice(notes []*model.Note) []Model {
	models := make([]Model, len(notes))

	for i, note := range notes {
		models[i] = note
	}

	return models
}

func (r *NoteRepository) FindBetweenDates(start, finish time.Time) ([]Model, error) {
	finish = finish.Add((time.Hour * 24) - time.Second)

	var notes []*model.Note

	err := r.db.
		Preload("DeletedDates").
		Where(`
				start IS NOT NULL AND start <= ?
				AND (recurrence_rule != '' OR (start <= ? AND start >= ?))
			`, finish, finish, start).
		Order("id asc").
		Find(&notes).
		Error

	return r.modelSlice(notes), err
}

func (r *NoteRepository) DeleteItem(uid uint) error {
	return r.db.Delete(model.Note{}, "id = ?", uid).Error
}

func (r *NoteRepository) DeleteRecurrence(itemID uint, date time.Time) error {
	deletedDate := model.NoteDeletedDate{
		DeletedDate: model.DeletedDate{
			Date: date,
		},
		NoteID: itemID,
	}

	return r.db.Create(&deletedDate).Error
}

func (r *NoteRepository) Update(itemID uint, new Model) error {
	note, err := r.FindByID(itemID)

	if err != nil {
		return err
	}

	if updates, ok := new.(*model.Note); ok {
		return r.db.Model(&note).Updates(updates).Error
	}

	return InvalidAppointmentAssertionError
}
<commit_msg>fix failing build<commit_after>package repository

import (
	"errors"
	"time"

	"github.com/cj123/calendar/model"
	"github.com/jinzhu/gorm"
)

type NoteRepository struct {
	Repository
}

func NewNoteRepository(db *gorm.DB) *NoteRepository {
	return &NoteRepository{
		Repository{
			db: db,
		},
	}
}

func (r *NoteRepository) Model() Model {
	return &model.Note{}
}

func (r *NoteRepository) Create(m Model) error {
	if note, ok := m.(*model.Note); ok {
		return r.db.Create(&note).Error
	}

	return errors.New("invalid note type")
}

func (r *NoteRepository) FindByID(id interface{}) (model.Note, error) {
	var note model.Note

	err := r.db.Preload("DeletedDates").First(&note, "id = ?", id).Error

	return note, err
}

func (r *NoteRepository) modelSlice(notes []*model.Note) []Model {
	models := make([]Model, len(notes))

	for i, note := range notes {
		models[i] = note
	}

	return models
}

func (r *NoteRepository) FindBetweenDates(start, finish time.Time) ([]Model, error) {
	finish = finish.Add((time.Hour * 24) - time.Second)

	var notes []*model.Note

	err := r.db.
		Preload("DeletedDates").
		Where(`
				start IS NOT NULL AND start <= ?
				AND (recurrence_rule != '' OR (start <= ? AND start >= ?))
			`, finish, finish, start).
		Order("id asc").
		Find(&notes).
		Error

	return r.modelSlice(notes), err
}

func (r *NoteRepository) DeleteItem(uid uint) error {
	return r.db.Delete(model.Note{}, "id = ?", uid).Error
}

func (r *NoteRepository) DeleteRecurrence(itemID uint, date time.Time) error {
	deletedDate := model.NoteDeletedDate{
		DeletedDate: model.DeletedDate{
			Date: date,
		},
		NoteID: itemID,
	}

	return r.db.Create(&deletedDate).Error
}

func (r *NoteRepository) Update(itemID uint, new Model) error {
	note, err := r.FindByID(itemID)

	if err != nil {
		return err
	}

	if updates, ok := new.(*model.Note); ok {
		return r.db.Model(&note).Updates(updates).Error
	}

	return InvalidAppointmentAssertionError
}
<|endoftext|>


<commit_before>package models

import (
	"errors"
	"fmt"
	"strconv"
	"strings"

	log "github.com/Sirupsen/logrus"
	envmanModels "github.com/bitrise-io/envman/models"
)

var (
	// DefaultIsAlwaysRun ...
	DefaultIsAlwaysRun = false
	// DefaultIsRequiresAdminUser ...
	DefaultIsRequiresAdminUser = false
	// DefaultIsSkippable ...
	DefaultIsSkippable = false
)

// ValidateStepInputOutputModel ...
func ValidateStepInputOutputModel(env envmanModels.EnvironmentItemModel) error {
	key, _, err := env.GetKeyValuePair()
	if err != nil {
		return err
	}
	if key == "" {
		return errors.New("Invalid environment: empty env_key")
	}

	options, err := env.GetOptions()
	if err != nil {
		return err
	}

	if options.Title == nil || *options.Title == "" {
		return errors.New("Invalid environment: missing or empty title")
	}

	return nil
}

// CompareVersions ...
// semantic version (X.Y.Z)
// 1 if version 2 is greater then version 1, -1 if not
// -2 & error if can't compare (if not supported component found)
func CompareVersions(version1, version2 string) (int, error) {
	version1Slice := strings.Split(version1, ".")
	version2Slice := strings.Split(version2, ".")

	lenDiff := len(version1Slice) - len(version2Slice)
	if lenDiff != 0 {
		makeDefVerComps := func(compLen int) []string {
			comps := make([]string, compLen, compLen)
			for idx := len(comps) - 1; idx >= 0; idx-- {
				comps[idx] = "0"
			}
			return comps
		}
		if lenDiff > 0 {
			// v1 slice is longer
			version2Slice = append(version2Slice, makeDefVerComps(lenDiff)...)
		} else {
			// v2 slice is longer
			version1Slice = append(version1Slice, makeDefVerComps(-lenDiff)...)
		}
	}

	cnt := len(version1Slice)
	for i, num := range version1Slice {
		num1, err := strconv.ParseInt(num, 0, 64)
		if err != nil {
			log.Error("[STEPMAN] - Failed to parse int:", err)
			return -2, err
		}

		num2, err2 := strconv.ParseInt(version2Slice[i], 0, 64)
		if err2 != nil {
			log.Error("[STEPMAN] - Failed to parse int:", err2)
			return -2, err
		}

		if num2 > num1 {
			return 1, nil
		}
		if i == cnt-1 {
			// last one
			if num2 == num1 {
				return 0, nil
			}
		}
	}
	return -1, nil
}

// -------------------
// --- Struct methods

// Normalize ...
func (step StepModel) Normalize() error {
	for _, input := range step.Inputs {
		if err := input.Normalize(); err != nil {
			return err
		}
	}
	for _, output := range step.Outputs {
		if err := output.Normalize(); err != nil {
			return err
		}
	}
	return nil
}

// ValidateSource ...
func (source StepSourceModel) ValidateSource() error {
	if source.Git == "" {
		return errors.New("Invalid step: missing or empty required 'source.git' property")
	}
	if source.Commit == "" {
		return errors.New("Invalid step: missing or empty required 'source.commit' property")
	}
	return nil
}

// ValidateStep ...
func (step StepModel) ValidateStep() error {
	if step.Title == nil || *step.Title == "" {
		return errors.New("Invalid step: missing or empty required 'title' property")
	}
	if step.Summary == nil || *step.Summary == "" {
		return errors.New("Invalid step: missing or empty required 'summary' property")
	}
	if step.Website == nil || *step.Website == "" {
		return errors.New("Invalid step: missing or empty required 'website' property")
	}
	if err := step.Source.ValidateSource(); err != nil {
		return err
	}
	for _, input := range step.Inputs {
		err := ValidateStepInputOutputModel(input)
		if err != nil {
			return err
		}
	}
	for _, output := range step.Outputs {
		err := ValidateStepInputOutputModel(output)
		if err != nil {
			return err
		}
	}
	return nil
}

// FillMissingDefaults ...
func (step *StepModel) FillMissingDefaults() error {
	defaultString := ""

	if step.Description == nil {
		step.Description = &defaultString
	}
	if step.SourceCodeURL == nil {
		step.SourceCodeURL = &defaultString
	}
	if step.SupportURL == nil {
		step.SupportURL = &defaultString
	}
	if step.IsRequiresAdminUser == nil {
		step.IsRequiresAdminUser = &DefaultIsRequiresAdminUser
	}
	if step.IsAlwaysRun == nil {
		step.IsAlwaysRun = &DefaultIsAlwaysRun
	}
	if step.IsSkippable == nil {
		step.IsSkippable = &DefaultIsSkippable
	}
	if step.RunIf == nil {
		step.RunIf = &defaultString
	}

	for _, input := range step.Inputs {
		err := input.FillMissingDefaults()
		if err != nil {
			return err
		}
	}
	for _, output := range step.Outputs {
		err := output.FillMissingDefaults()
		if err != nil {
			return err
		}
	}
	return nil
}

// GetStep ...
func (collection StepCollectionModel) GetStep(id, version string) (StepModel, bool) {
	stepHash := collection.Steps
	stepVersions, found := stepHash[id]
	if !found {
		return StepModel{}, false
	}
	step, found := stepVersions.Versions[version]
	if !found {
		return StepModel{}, false
	}
	return step, true
}

// GetDownloadLocations ...
func (collection StepCollectionModel) GetDownloadLocations(id, version string) ([]DownloadLocationModel, error) {
	step, found := collection.GetStep(id, version)
	if found == false {
		return []DownloadLocationModel{}, fmt.Errorf("Collection doesn't contains step %s (%s)", id, version)
	}

	locations := []DownloadLocationModel{}
	for _, downloadLocation := range collection.DownloadLocations {
		switch downloadLocation.Type {
		case "zip":
			url := downloadLocation.Src + id + "/" + version + "/step.zip"
			location := DownloadLocationModel{
				Type: downloadLocation.Type,
				Src:  url,
			}
			locations = append(locations, location)
		case "git":
			location := DownloadLocationModel{
				Type: downloadLocation.Type,
				Src:  step.Source.Git,
			}
			locations = append(locations, location)
		default:
			return []DownloadLocationModel{}, fmt.Errorf("[STEPMAN] - Invalid download location (%#v) for step (%#v)", downloadLocation, id)
		}
	}
	if len(locations) < 1 {
		return []DownloadLocationModel{}, fmt.Errorf("[STEPMAN] - No download location found for step (%#v)", id)
	}
	return locations, nil
}

// GetLatestStepVersion ...
func (collection StepCollectionModel) GetLatestStepVersion(id string) (string, error) {
	stepHash := collection.Steps
	stepGroup, found := stepHash[id]
	if !found {
		return "", fmt.Errorf("Collection doesn't contains step %s", id)
	}

	if stepGroup.LatestVersionNumber == "" {
		return "", fmt.Errorf("Failed to find latest version of step %s", id)
	}

	return stepGroup.LatestVersionNumber, nil
}
<commit_msg>log fixes<commit_after>package models

import (
	"errors"
	"fmt"
	"strconv"
	"strings"

	log "github.com/Sirupsen/logrus"
	envmanModels "github.com/bitrise-io/envman/models"
)

var (
	// DefaultIsAlwaysRun ...
	DefaultIsAlwaysRun = false
	// DefaultIsRequiresAdminUser ...
	DefaultIsRequiresAdminUser = false
	// DefaultIsSkippable ...
	DefaultIsSkippable = false
)

// ValidateStepInputOutputModel ...
func ValidateStepInputOutputModel(env envmanModels.EnvironmentItemModel) error {
	key, _, err := env.GetKeyValuePair()
	if err != nil {
		return err
	}
	if key == "" {
		return errors.New("Invalid environment: empty env_key")
	}

	options, err := env.GetOptions()
	if err != nil {
		return err
	}

	if options.Title == nil || *options.Title == "" {
		return errors.New("Invalid environment: missing or empty title")
	}

	return nil
}

// CompareVersions ...
// semantic version (X.Y.Z)
// 1 if version 2 is greater then version 1, -1 if not
// -2 & error if can't compare (if not supported component found)
func CompareVersions(version1, version2 string) (int, error) {
	version1Slice := strings.Split(version1, ".")
	version2Slice := strings.Split(version2, ".")

	lenDiff := len(version1Slice) - len(version2Slice)
	if lenDiff != 0 {
		makeDefVerComps := func(compLen int) []string {
			comps := make([]string, compLen, compLen)
			for idx := len(comps) - 1; idx >= 0; idx-- {
				comps[idx] = "0"
			}
			return comps
		}
		if lenDiff > 0 {
			// v1 slice is longer
			version2Slice = append(version2Slice, makeDefVerComps(lenDiff)...)
		} else {
			// v2 slice is longer
			version1Slice = append(version1Slice, makeDefVerComps(-lenDiff)...)
		}
	}

	cnt := len(version1Slice)
	for i, num := range version1Slice {
		num1, err := strconv.ParseInt(num, 0, 64)
		if err != nil {
			log.Error("[STEPMAN] - Failed to parse int:", err)
			return -2, err
		}

		num2, err2 := strconv.ParseInt(version2Slice[i], 0, 64)
		if err2 != nil {
			log.Error("[STEPMAN] - Failed to parse int:", err2)
			return -2, err
		}

		if num2 > num1 {
			return 1, nil
		}
		if i == cnt-1 {
			// last one
			if num2 == num1 {
				return 0, nil
			}
		}
	}
	return -1, nil
}

// -------------------
// --- Struct methods

// Normalize ...
func (step StepModel) Normalize() error {
	for _, input := range step.Inputs {
		if err := input.Normalize(); err != nil {
			return err
		}
	}
	for _, output := range step.Outputs {
		if err := output.Normalize(); err != nil {
			return err
		}
	}
	return nil
}

// ValidateSource ...
func (source StepSourceModel) ValidateSource() error {
	if source.Git == "" {
		return errors.New("Invalid step: missing or empty required 'source.git' property")
	}
	if source.Commit == "" {
		return errors.New("Invalid step: missing or empty required 'source.commit' property")
	}
	return nil
}

// ValidateStep ...
func (step StepModel) ValidateStep() error {
	if step.Title == nil || *step.Title == "" {
		return errors.New("Invalid step: missing or empty required 'title' property")
	}
	if step.Summary == nil || *step.Summary == "" {
		return errors.New("Invalid step: missing or empty required 'summary' property")
	}
	if step.Website == nil || *step.Website == "" {
		return errors.New("Invalid step: missing or empty required 'website' property")
	}
	if err := step.Source.ValidateSource(); err != nil {
		return err
	}
	for _, input := range step.Inputs {
		err := ValidateStepInputOutputModel(input)
		if err != nil {
			return err
		}
	}
	for _, output := range step.Outputs {
		err := ValidateStepInputOutputModel(output)
		if err != nil {
			return err
		}
	}
	return nil
}

// FillMissingDefaults ...
func (step *StepModel) FillMissingDefaults() error {
	defaultString := ""

	if step.Description == nil {
		step.Description = &defaultString
	}
	if step.SourceCodeURL == nil {
		step.SourceCodeURL = &defaultString
	}
	if step.SupportURL == nil {
		step.SupportURL = &defaultString
	}
	if step.IsRequiresAdminUser == nil {
		step.IsRequiresAdminUser = &DefaultIsRequiresAdminUser
	}
	if step.IsAlwaysRun == nil {
		step.IsAlwaysRun = &DefaultIsAlwaysRun
	}
	if step.IsSkippable == nil {
		step.IsSkippable = &DefaultIsSkippable
	}
	if step.RunIf == nil {
		step.RunIf = &defaultString
	}

	for _, input := range step.Inputs {
		err := input.FillMissingDefaults()
		if err != nil {
			return err
		}
	}
	for _, output := range step.Outputs {
		err := output.FillMissingDefaults()
		if err != nil {
			return err
		}
	}
	return nil
}

// GetStep ...
func (collection StepCollectionModel) GetStep(id, version string) (StepModel, bool) {
	stepHash := collection.Steps
	stepVersions, found := stepHash[id]
	if !found {
		return StepModel{}, false
	}
	step, found := stepVersions.Versions[version]
	if !found {
		return StepModel{}, false
	}
	return step, true
}

// GetDownloadLocations ...
func (collection StepCollectionModel) GetDownloadLocations(id, version string) ([]DownloadLocationModel, error) {
	step, found := collection.GetStep(id, version)
	if found == false {
		return []DownloadLocationModel{}, fmt.Errorf("Collection (%s) doesn't contains step %s (%s)", collection.SteplibSource, id, version)
	}

	locations := []DownloadLocationModel{}
	for _, downloadLocation := range collection.DownloadLocations {
		switch downloadLocation.Type {
		case "zip":
			url := downloadLocation.Src + id + "/" + version + "/step.zip"
			location := DownloadLocationModel{
				Type: downloadLocation.Type,
				Src:  url,
			}
			locations = append(locations, location)
		case "git":
			location := DownloadLocationModel{
				Type: downloadLocation.Type,
				Src:  step.Source.Git,
			}
			locations = append(locations, location)
		default:
			return []DownloadLocationModel{}, fmt.Errorf("[STEPMAN] - Invalid download location (%#v) for step (%#v)", downloadLocation, id)
		}
	}
	if len(locations) < 1 {
		return []DownloadLocationModel{}, fmt.Errorf("[STEPMAN] - No download location found for step (%#v)", id)
	}
	return locations, nil
}

// GetLatestStepVersion ...
func (collection StepCollectionModel) GetLatestStepVersion(id string) (string, error) {
	stepHash := collection.Steps
	stepGroup, found := stepHash[id]
	if !found {
		return "", fmt.Errorf("Collection (%s) doesn't contains step (%s)", collection.SteplibSource, id)
	}

	if stepGroup.LatestVersionNumber == "" {
		return "", fmt.Errorf("Failed to find latest version of step %s", id)
	}

	return stepGroup.LatestVersionNumber, nil
}
<|endoftext|>


<commit_before>/*
 * ZAnnotate Copyright 2017 Regents of the University of Michigan
 *
 * 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 zannotate

import (
	"bufio"
	"encoding/json"
	"net"
	"os"
	"sync"

	log "github.com/Sirupsen/logrus"
	"github.com/oschwald/geoip2-golang"
)

type GlobalConf struct {
	InputFilePath    string
	InputFileType    string
	OutputFilePath   string
	MetadataFilePath string
	LogFilePath      string
	Verbosity        int
	Threads          int

	GeoIP2     bool
	GeoIP2Conf GeoIP2Conf
}

type Result struct {
	Ip     string        `json:"ip"`
	GeoIP2 *GeoIP2Output `json:"geoip2"`
}

func AnnotateRead(path string, in chan<- string) {
	log.Debug("read thread started")
	var f *os.File
	if path == "" || path == "-" {
		log.Debug("reading input from stdin")
		f = os.Stdin
	} else {
		var err error
		f, err = os.Open(path)
		if err != nil {
			log.Fatal("unable to open input file:", err.Error())
		}
		log.Debug("reading input from ", path)
	}
	s := bufio.NewScanner(f)
	for s.Scan() {
		in <- s.Text()
	}
	if err := s.Err(); err != nil {
		log.Fatal("input unable to read file", err)
	}
	close(in)
	log.Debug("read thread finished")
}

func AnnotateWrite(path string, out <-chan string, wg *sync.WaitGroup) {
	log.Debug("write thread started")
	var f *os.File
	if path == "" || path == "-" {
		f = os.Stdout
	} else {
		var err error
		f, err = os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0666)
		if err != nil {
			log.Fatal("unable to open output file:", err.Error())
		}
		defer f.Close()
	}
	for n := range out {
		f.WriteString(n + "\n")
	}
	wg.Done()
	log.Debug("write thread finished")
}

func AnnotateWorker(conf *GlobalConf, in <-chan string, out chan<- string,
	wg *sync.WaitGroup, i int) {
	log.Debug("annotate worker ", i, " started")
	// not entirely sure if this geolocation library is thread safe
	// so for now we're just going to open the MaxMind database in every thraed
	var geoIP2db *geoip2.Reader
	if conf.GeoIP2 {
		geoIP2db = GeoIP2Open(&conf.GeoIP2Conf)
		defer geoIP2db.Close()
	}
	for line := range in {
		ip := net.ParseIP(line)
		if ip == nil {
			log.Fatal("invalid IP received: ", line)
		}
		var res Result
		res.Ip = ip.String()
		if conf.GeoIP2 == true {
			record, err := geoIP2db.City(ip)
			if err != nil {
				log.Fatal(err)
			}
			res.GeoIP2 = GeoIP2FillStruct(record, &conf.GeoIP2Conf)
		}
		//fmt.Printf("%+v\n", res)
		jsonRes, err := json.Marshal(res)
		if err != nil {
			log.Fatal("Unable to marshal JSON result", err)
		}
		log.Warn(res)
		log.Warn(string(jsonRes))
		out <- string(jsonRes)
	}
	wg.Done()
	log.Debug("annotate worker ", i, " finished")
}

func DoAnnotation(conf *GlobalConf) {
	outChan := make(chan string)
	inChan := make(chan string)

	var outputWG sync.WaitGroup
	outputWG.Add(1)

	//startTime := time.Now().Format(time.RFC3339)
	go AnnotateRead(conf.InputFilePath, inChan)
	go AnnotateWrite(conf.InputFilePath, outChan, &outputWG)

	var annotateWG sync.WaitGroup
	annotateWG.Add(conf.Threads)
	for i := 0; i < conf.Threads; i++ {
		go AnnotateWorker(conf, inChan, outChan, &annotateWG, i)
	}
	annotateWG.Wait()
	close(outChan)
	outputWG.Wait()
	//endTime := time.Now().Format(time.RFC3339)
}
<commit_msg>geolocation that actually seems reasonable<commit_after>/*
 * ZAnnotate Copyright 2017 Regents of the University of Michigan
 *
 * 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 zannotate

import (
	"bufio"
	"encoding/json"
	"net"
	"os"
	"sync"

	log "github.com/Sirupsen/logrus"
	"github.com/oschwald/geoip2-golang"
)

type GlobalConf struct {
	InputFilePath    string
	InputFileType    string
	OutputFilePath   string
	MetadataFilePath string
	LogFilePath      string
	Verbosity        int
	Threads          int

	GeoIP2     bool
	GeoIP2Conf GeoIP2Conf
}

type Result struct {
	Ip     string        `json:"ip"`
	GeoIP2 *GeoIP2Output `json:"geoip2"`
}

func AnnotateRead(path string, in chan<- string) {
	log.Debug("read thread started")
	var f *os.File
	if path == "" || path == "-" {
		log.Debug("reading input from stdin")
		f = os.Stdin
	} else {
		var err error
		f, err = os.Open(path)
		if err != nil {
			log.Fatal("unable to open input file:", err.Error())
		}
		log.Debug("reading input from ", path)
	}
	s := bufio.NewScanner(f)
	for s.Scan() {
		in <- s.Text()
	}
	if err := s.Err(); err != nil {
		log.Fatal("input unable to read file", err)
	}
	close(in)
	log.Debug("read thread finished")
}

func AnnotateWrite(path string, out <-chan string, wg *sync.WaitGroup) {
	log.Debug("write thread started")
	var f *os.File
	if path == "" || path == "-" {
		f = os.Stdout
	} else {
		var err error
		f, err = os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0666)
		if err != nil {
			log.Fatal("unable to open output file:", err.Error())
		}
		defer f.Close()
	}
	for n := range out {
		f.WriteString(n + "\n")
	}
	wg.Done()
	log.Debug("write thread finished")
}

func AnnotateWorker(conf *GlobalConf, in <-chan string, out chan<- string,
	wg *sync.WaitGroup, i int) {
	log.Debug("annotate worker ", i, " started")
	// not entirely sure if this geolocation library is thread safe
	// so for now we're just going to open the MaxMind database in every thraed
	var geoIP2db *geoip2.Reader
	if conf.GeoIP2 {
		geoIP2db = GeoIP2Open(&conf.GeoIP2Conf)
		defer geoIP2db.Close()
	}
	log.Debug("annotate worker ", i, " initialization finished")
	for line := range in {
		ip := net.ParseIP(line)
		if ip == nil {
			log.Fatal("invalid IP received: ", line)
		}
		var res Result
		res.Ip = ip.String()
		if conf.GeoIP2 == true {
			record, err := geoIP2db.City(ip)
			if err != nil {
				log.Fatal(err)
			}
			res.GeoIP2 = GeoIP2FillStruct(record, &conf.GeoIP2Conf)
		}
		//fmt.Printf("%+v\n", res)
		jsonRes, err := json.Marshal(res)
		if err != nil {
			log.Fatal("Unable to marshal JSON result", err)
		}
		out <- string(jsonRes)
	}
	wg.Done()
	log.Debug("annotate worker ", i, " finished")
}

func DoAnnotation(conf *GlobalConf) {
	outChan := make(chan string)
	inChan := make(chan string)

	var outputWG sync.WaitGroup
	outputWG.Add(1)

	//startTime := time.Now().Format(time.RFC3339)
	go AnnotateRead(conf.InputFilePath, inChan)
	go AnnotateWrite(conf.InputFilePath, outChan, &outputWG)

	var annotateWG sync.WaitGroup
	annotateWG.Add(conf.Threads)
	for i := 0; i < conf.Threads; i++ {
		go AnnotateWorker(conf, inChan, outChan, &annotateWG, i)
	}
	annotateWG.Wait()
	close(outChan)
	outputWG.Wait()
	//endTime := time.Now().Format(time.RFC3339)
}
<|endoftext|>


<commit_before>/*
 * Minio Go Library for Amazon S3 Compatible Cloud Storage
 * Copyright 2015-2017 Minio, Inc.
 *
 * 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 minio

import (
	"context"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/minio/minio-go/pkg/s3utils"
)

// BucketExists verify if bucket exists and you have permission to access it.
func (c Client) BucketExists(bucketName string) (bool, error) {
	// Input validation.
	if err := s3utils.CheckValidBucketName(bucketName); err != nil {
		return false, err
	}

	// Execute HEAD on bucketName.
	resp, err := c.executeMethod(context.Background(), "HEAD", requestMetadata{
		bucketName:       bucketName,
		contentSHA256Hex: emptySHA256Hex,
	})
	defer closeResponse(resp)
	if err != nil {
		if ToErrorResponse(err).Code == "NoSuchBucket" {
			return false, nil
		}
		return false, err
	}
	if resp != nil {
		if resp.StatusCode != http.StatusOK {
			return false, httpRespToErrorResponse(resp, bucketName, "")
		}
	}
	return true, nil
}

// List of header keys to be filtered, usually
// from all S3 API http responses.
var defaultFilterKeys = []string{
	"Connection",
	"Transfer-Encoding",
	"Accept-Ranges",
	"Date",
	"Server",
	"Vary",
	"x-amz-bucket-region",
	"x-amz-request-id",
	"x-amz-id-2",
	// Add new headers to be ignored.
}

// Extract only necessary metadata header key/values by
// filtering them out with a list of custom header keys.
func extractObjMetadata(header http.Header) http.Header {
	filterKeys := append([]string{
		"ETag",
		"Content-Length",
		"Last-Modified",
		"Content-Type",
	}, defaultFilterKeys...)
	return filterHeader(header, filterKeys)
}

// StatObject verifies if object exists and you have permission to access.
func (c Client) StatObject(bucketName, objectName string, opts StatObjectOptions) (ObjectInfo, error) {
	// Input validation.
	if err := s3utils.CheckValidBucketName(bucketName); err != nil {
		return ObjectInfo{}, err
	}
	if err := s3utils.CheckValidObjectName(objectName); err != nil {
		return ObjectInfo{}, err
	}
	return c.statObject(context.Background(), bucketName, objectName, opts)
}

// Lower level API for statObject supporting pre-conditions and range headers.
func (c Client) statObject(ctx context.Context, bucketName, objectName string, opts StatObjectOptions) (ObjectInfo, error) {
	// Input validation.
	if err := s3utils.CheckValidBucketName(bucketName); err != nil {
		return ObjectInfo{}, err
	}
	if err := s3utils.CheckValidObjectName(objectName); err != nil {
		return ObjectInfo{}, err
	}

	// Execute HEAD on objectName.
	resp, err := c.executeMethod(ctx, "HEAD", requestMetadata{
		bucketName:       bucketName,
		objectName:       objectName,
		contentSHA256Hex: emptySHA256Hex,
		customHeader:     opts.Header(),
	})
	defer closeResponse(resp)
	if err != nil {
		return ObjectInfo{}, err
	}
	if resp != nil {
		if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusPartialContent {
			return ObjectInfo{}, httpRespToErrorResponse(resp, bucketName, objectName)
		}
	}

	// Trim off the odd double quotes from ETag in the beginning and end.
	md5sum := strings.TrimPrefix(resp.Header.Get("ETag"), "\"")
	md5sum = strings.TrimSuffix(md5sum, "\"")

	// Parse content length is exists
	var size int64 = -1
	contentLengthStr := resp.Header.Get("Content-Length")
	if contentLengthStr != "" {
		size, err = strconv.ParseInt(contentLengthStr, 10, 64)
		if err != nil {
			// Content-Length is not valid
			return ObjectInfo{}, ErrorResponse{
				Code:       "InternalError",
				Message:    "Content-Length is invalid. " + reportIssue,
				BucketName: bucketName,
				Key:        objectName,
				RequestID:  resp.Header.Get("x-amz-request-id"),
				HostID:     resp.Header.Get("x-amz-id-2"),
				Region:     resp.Header.Get("x-amz-bucket-region"),
			}
		}
	}

	// Parse Last-Modified has http time format.
	date, err := time.Parse(http.TimeFormat, resp.Header.Get("Last-Modified"))
	if err != nil {
		return ObjectInfo{}, ErrorResponse{
			Code:       "InternalError",
			Message:    "Last-Modified time format is invalid. " + reportIssue,
			BucketName: bucketName,
			Key:        objectName,
			RequestID:  resp.Header.Get("x-amz-request-id"),
			HostID:     resp.Header.Get("x-amz-id-2"),
			Region:     resp.Header.Get("x-amz-bucket-region"),
		}
	}

	// Fetch content type if any present.
	contentType := strings.TrimSpace(resp.Header.Get("Content-Type"))
	if contentType == "" {
		contentType = "application/octet-stream"
	}

	// Save object metadata info.
	return ObjectInfo{
		ETag:         md5sum,
		Key:          objectName,
		Size:         size,
		LastModified: date,
		ContentType:  contentType,
		// Extract only the relevant header keys describing the object.
		// following function filters out a list of standard set of keys
		// which are not part of object metadata.
		Metadata: extractObjMetadata(resp.Header),
	}, nil
}
<commit_msg>Avoid returning security related header when stating an object (#975)<commit_after>/*
 * Minio Go Library for Amazon S3 Compatible Cloud Storage
 * Copyright 2015-2017 Minio, Inc.
 *
 * 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 minio

import (
	"context"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/minio/minio-go/pkg/s3utils"
)

// BucketExists verify if bucket exists and you have permission to access it.
func (c Client) BucketExists(bucketName string) (bool, error) {
	// Input validation.
	if err := s3utils.CheckValidBucketName(bucketName); err != nil {
		return false, err
	}

	// Execute HEAD on bucketName.
	resp, err := c.executeMethod(context.Background(), "HEAD", requestMetadata{
		bucketName:       bucketName,
		contentSHA256Hex: emptySHA256Hex,
	})
	defer closeResponse(resp)
	if err != nil {
		if ToErrorResponse(err).Code == "NoSuchBucket" {
			return false, nil
		}
		return false, err
	}
	if resp != nil {
		if resp.StatusCode != http.StatusOK {
			return false, httpRespToErrorResponse(resp, bucketName, "")
		}
	}
	return true, nil
}

// List of header keys to be filtered, usually
// from all S3 API http responses.
var defaultFilterKeys = []string{
	"Connection",
	"Transfer-Encoding",
	"Accept-Ranges",
	"Date",
	"Server",
	"Vary",
	"x-amz-bucket-region",
	"x-amz-request-id",
	"x-amz-id-2",
	"Content-Security-Policy",
	"X-Xss-Protection",

	// Add new headers to be ignored.
}

// Extract only necessary metadata header key/values by
// filtering them out with a list of custom header keys.
func extractObjMetadata(header http.Header) http.Header {
	filterKeys := append([]string{
		"ETag",
		"Content-Length",
		"Last-Modified",
		"Content-Type",
	}, defaultFilterKeys...)
	return filterHeader(header, filterKeys)
}

// StatObject verifies if object exists and you have permission to access.
func (c Client) StatObject(bucketName, objectName string, opts StatObjectOptions) (ObjectInfo, error) {
	// Input validation.
	if err := s3utils.CheckValidBucketName(bucketName); err != nil {
		return ObjectInfo{}, err
	}
	if err := s3utils.CheckValidObjectName(objectName); err != nil {
		return ObjectInfo{}, err
	}
	return c.statObject(context.Background(), bucketName, objectName, opts)
}

// Lower level API for statObject supporting pre-conditions and range headers.
func (c Client) statObject(ctx context.Context, bucketName, objectName string, opts StatObjectOptions) (ObjectInfo, error) {
	// Input validation.
	if err := s3utils.CheckValidBucketName(bucketName); err != nil {
		return ObjectInfo{}, err
	}
	if err := s3utils.CheckValidObjectName(objectName); err != nil {
		return ObjectInfo{}, err
	}

	// Execute HEAD on objectName.
	resp, err := c.executeMethod(ctx, "HEAD", requestMetadata{
		bucketName:       bucketName,
		objectName:       objectName,
		contentSHA256Hex: emptySHA256Hex,
		customHeader:     opts.Header(),
	})
	defer closeResponse(resp)
	if err != nil {
		return ObjectInfo{}, err
	}
	if resp != nil {
		if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusPartialContent {
			return ObjectInfo{}, httpRespToErrorResponse(resp, bucketName, objectName)
		}
	}

	// Trim off the odd double quotes from ETag in the beginning and end.
	md5sum := strings.TrimPrefix(resp.Header.Get("ETag"), "\"")
	md5sum = strings.TrimSuffix(md5sum, "\"")

	// Parse content length is exists
	var size int64 = -1
	contentLengthStr := resp.Header.Get("Content-Length")
	if contentLengthStr != "" {
		size, err = strconv.ParseInt(contentLengthStr, 10, 64)
		if err != nil {
			// Content-Length is not valid
			return ObjectInfo{}, ErrorResponse{
				Code:       "InternalError",
				Message:    "Content-Length is invalid. " + reportIssue,
				BucketName: bucketName,
				Key:        objectName,
				RequestID:  resp.Header.Get("x-amz-request-id"),
				HostID:     resp.Header.Get("x-amz-id-2"),
				Region:     resp.Header.Get("x-amz-bucket-region"),
			}
		}
	}

	// Parse Last-Modified has http time format.
	date, err := time.Parse(http.TimeFormat, resp.Header.Get("Last-Modified"))
	if err != nil {
		return ObjectInfo{}, ErrorResponse{
			Code:       "InternalError",
			Message:    "Last-Modified time format is invalid. " + reportIssue,
			BucketName: bucketName,
			Key:        objectName,
			RequestID:  resp.Header.Get("x-amz-request-id"),
			HostID:     resp.Header.Get("x-amz-id-2"),
			Region:     resp.Header.Get("x-amz-bucket-region"),
		}
	}

	// Fetch content type if any present.
	contentType := strings.TrimSpace(resp.Header.Get("Content-Type"))
	if contentType == "" {
		contentType = "application/octet-stream"
	}

	// Save object metadata info.
	return ObjectInfo{
		ETag:         md5sum,
		Key:          objectName,
		Size:         size,
		LastModified: date,
		ContentType:  contentType,
		// Extract only the relevant header keys describing the object.
		// following function filters out a list of standard set of keys
		// which are not part of object metadata.
		Metadata: extractObjMetadata(resp.Header),
	}, nil
}
<|endoftext|>


<commit_before>// Copyright (c) 2014 Pagoda Box Inc.
//
// This Source Code Form is subject to the terms of the Mozilla Public License,
// v. 2.0. If a copy of the MPL was not distributed with this file, You can
// obtain one at http://mozilla.org/MPL/2.0/.

package api

import (
	"fmt"
	"net/http"
	"strconv"
	"strings"

	"github.com/pagodabox/nanobox-boxfile"
	"github.com/pagodabox/nanobox-server/config"
	"github.com/pagodabox/nanobox-server/util"
)

func (api *API) Exec(rw http.ResponseWriter, req *http.Request) {
	util.RemoveContainer("exec1")
	conn, _, err := rw.(http.Hijacker).Hijack()
	if err != nil {
		rw.WriteHeader(http.StatusInternalServerError)
		rw.Write([]byte(err.Error()))
		return
	}
	defer conn.Close()

	cmd := []string{"/bin/bash"}
	if additionalCmd := req.FormValue("cmd"); additionalCmd != "" {
		cmd = append(cmd, "-c", additionalCmd)
	}

	container, err := util.CreateExecContainer("exec1", cmd)
	if err != nil {
		conn.Write([]byte(err.Error()))
		return
	}

	forwards := []string{}
	if req.FormValue("forward") != "" {
	  forwards = append(forwards, strings.Split(req.FormValue("forward"), ",")...)
	}

	box := mergedBox()
	fmt.Printf("%#v", box.Parsed)
	if boxForwards, ok := box.Node("console").Value("forwards").([]interface{}); ok {
		for _, boxFInterface := range boxForwards {
			if boxForward, ok := boxFInterface.(string); ok {
				forwards = append(forwards, boxForward)
			}
		}
	}
	fmt.Printf("%#v", forwards)
	// maybe add a forward port mapping
	for _, rule := range forwards {
		strSlice := strings.Split(rule, ":")
		if len(strSlice) == 2 {
			portInt, _ := strconv.Atoi(strSlice[0])
			config.Router.AddForward("enter-"+rule, portInt, container.NetworkSettings.IPAddress+":"+strSlice[1])
			defer config.Router.RemoveForward("enter-" + rule)
		}
	}

	// Flush the options to make sure the client sets the raw mode
	conn.Write([]byte{})

	util.AttachToContainer(container.ID, conn, conn, conn)
	util.RemoveContainer(container.ID)
}

func (api *API) KillExec(rw http.ResponseWriter, req *http.Request) {
	fmt.Printf("signal recieved: %s\n", req.FormValue("signal"))
	err := util.KillContainer("exec1", req.FormValue("signal"))
	fmt.Println(err)
}

func (api *API) ResizeExec(rw http.ResponseWriter, req *http.Request) {
	h, _ := strconv.Atoi(req.FormValue("h"))
	w, _ := strconv.Atoi(req.FormValue("w"))
	if h == 0 || w == 0 {
		return
	}
	err := util.ResizeContainerTTY("exec1", h, w)
	fmt.Println(err)
}

func mergedBox() (box boxfile.Boxfile) {
	box = boxfile.NewFromPath("/vagrant/code/" + config.App + "/Boxfile")
	if out, err := util.ExecHook("boxfile", "build1", map[string]interface{}{}); err == nil {
		box.Merge(boxfile.New([]byte(out)))
	}
	return
}
<commit_msg>remove debug code<commit_after>// Copyright (c) 2014 Pagoda Box Inc.
//
// This Source Code Form is subject to the terms of the Mozilla Public License,
// v. 2.0. If a copy of the MPL was not distributed with this file, You can
// obtain one at http://mozilla.org/MPL/2.0/.

package api

import (
	"fmt"
	"net/http"
	"strconv"
	"strings"

	"github.com/pagodabox/nanobox-boxfile"
	"github.com/pagodabox/nanobox-server/config"
	"github.com/pagodabox/nanobox-server/util"
)

func (api *API) Exec(rw http.ResponseWriter, req *http.Request) {
	util.RemoveContainer("exec1")
	conn, _, err := rw.(http.Hijacker).Hijack()
	if err != nil {
		rw.WriteHeader(http.StatusInternalServerError)
		rw.Write([]byte(err.Error()))
		return
	}
	defer conn.Close()

	cmd := []string{"/bin/bash"}
	if additionalCmd := req.FormValue("cmd"); additionalCmd != "" {
		cmd = append(cmd, "-c", additionalCmd)
	}

	container, err := util.CreateExecContainer("exec1", cmd)
	if err != nil {
		conn.Write([]byte(err.Error()))
		return
	}

	forwards := []string{}
	if req.FormValue("forward") != "" {
	  forwards = append(forwards, strings.Split(req.FormValue("forward"), ",")...)
	}

	box := mergedBox()
	if boxForwards, ok := box.Node("console").Value("forwards").([]interface{}); ok {
		for _, boxFInterface := range boxForwards {
			if boxForward, ok := boxFInterface.(string); ok {
				forwards = append(forwards, boxForward)
			}
		}
	}
	// maybe add a forward port mapping
	for _, rule := range forwards {
		strSlice := strings.Split(rule, ":")
		if len(strSlice) == 2 {
			portInt, _ := strconv.Atoi(strSlice[0])
			config.Router.AddForward("enter-"+rule, portInt, container.NetworkSettings.IPAddress+":"+strSlice[1])
			defer config.Router.RemoveForward("enter-" + rule)
		}
	}

	// Flush the options to make sure the client sets the raw mode
	conn.Write([]byte{})

	util.AttachToContainer(container.ID, conn, conn, conn)
	util.RemoveContainer(container.ID)
}

func (api *API) KillExec(rw http.ResponseWriter, req *http.Request) {
	fmt.Printf("signal recieved: %s\n", req.FormValue("signal"))
	err := util.KillContainer("exec1", req.FormValue("signal"))
	fmt.Println(err)
}

func (api *API) ResizeExec(rw http.ResponseWriter, req *http.Request) {
	h, _ := strconv.Atoi(req.FormValue("h"))
	w, _ := strconv.Atoi(req.FormValue("w"))
	if h == 0 || w == 0 {
		return
	}
	err := util.ResizeContainerTTY("exec1", h, w)
	fmt.Println(err)
}

func mergedBox() (box boxfile.Boxfile) {
	box = boxfile.NewFromPath("/vagrant/code/" + config.App + "/Boxfile")
	if out, err := util.ExecHook("boxfile", "build1", map[string]interface{}{}); err == nil {
		box.Merge(boxfile.New([]byte(out)))
	}
	return
}
<|endoftext|>


<commit_before>//  Copyright (c) 2013 Couchbase, Inc.
//  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 system

import (
	"encoding/json"
	"fmt"
	"strings"

	"github.com/couchbaselabs/dparval"
	"github.com/couchbaselabs/tuqtng/catalog"
	"github.com/couchbaselabs/tuqtng/query"
)

type indexbucket struct {
	pool    *pool
	name    string
	primary catalog.PrimaryIndex
}

func (b *indexbucket) Release() {
}

func (b *indexbucket) PoolId() string {
	return b.pool.Id()
}

func (b *indexbucket) Id() string {
	return b.Name()
}

func (b *indexbucket) Name() string {
	return b.name
}

func (b *indexbucket) Count() (int64, query.Error) {
	return 0, query.NewError(nil, "Not Supported")
}

func (b *indexbucket) IndexIds() ([]string, query.Error) {
	return []string{b.primary.Id()}, nil
}

func (b *indexbucket) IndexNames() ([]string, query.Error) {
	return []string{b.primary.Name()}, nil
}

func (b *indexbucket) IndexById(id string) (catalog.Index, query.Error) {
	return b.primary, nil
}

func (b *indexbucket) IndexByName(name string) (catalog.Index, query.Error) {
	return b.primary, nil
}

func (b *indexbucket) IndexByPrimary() (catalog.PrimaryIndex, query.Error) {
	return b.primary, nil
}

func (b *indexbucket) Indexes() ([]catalog.Index, query.Error) {
	return []catalog.Index{b.primary}, nil
}

func (b *indexbucket) BulkFetch(ids []string) (map[string]*dparval.Value, query.Error) {
	rv := make(map[string]*dparval.Value, 0)
	for _, id := range ids {
		item, e := b.Fetch(id)
		if e != nil {
			return nil, e
		}
		rv[id] = item
	}
	return rv, nil
}

func (b *indexbucket) Fetch(id string) (item *dparval.Value, e query.Error) {
	ids := strings.SplitN(id, "/", 3)

	pool, err := b.pool.site.actualSite.PoolById(ids[0])
	if pool != nil {
		bucket, _ := pool.BucketById(ids[1])
		if bucket != nil {
			index, _ := bucket.IndexById(ids[2])
			if index != nil {
				doc := map[string]interface{}{
					"id":         index.Id(),
					"name":       index.Name(),
					"bucket_id":  bucket.Id(),
					"pool_id":    pool.Id(),
					"site_id":    b.pool.site.actualSite.Id(),
					"index_key":  catalogObjectToJSONSafe(index.Key()),
					"index_type": catalogObjectToJSONSafe(index.Type()),
				}
				return dparval.NewValue(doc), nil
			}
		}
	}
	return nil, err
}

func catalogObjectToJSONSafe(catobj interface{}) interface{} {
	var rv interface{}
	bytes, err := json.Marshal(catobj)
	if err == nil {
		json.Unmarshal(bytes, &rv)
	}
	return rv
}

func (b *indexbucket) CreatePrimaryIndex() (catalog.PrimaryIndex, query.Error) {
	if b.primary != nil {
		return b.primary, nil
	}

	return nil, query.NewError(nil, "Not supported.")
}

func (b *indexbucket) CreateIndex(name string, key catalog.IndexKey, using catalog.IndexType) (catalog.Index, query.Error) {
	return nil, query.NewError(nil, "Not supported.")
}

func newIndexesBucket(p *pool) (*indexbucket, query.Error) {
	b := new(indexbucket)
	b.pool = p
	b.name = BUCKET_NAME_INDEXES

	b.primary = &indexIndex{name: "primary", bucket: b}

	return b, nil
}

type indexIndex struct {
	name   string
	bucket *indexbucket
}

func (pi *indexIndex) BucketId() string {
	return pi.bucket.Id()
}

func (pi *indexIndex) Id() string {
	return pi.Name()
}

func (pi *indexIndex) Name() string {
	return pi.name
}

func (pi *indexIndex) Type() catalog.IndexType {
	return catalog.UNSPECIFIED
}

func (pi *indexIndex) IsPrimary() bool {
	return true
}

func (pi *indexIndex) Key() catalog.IndexKey {
	// FIXME
	return nil
}

func (pi *indexIndex) Drop() query.Error {
	return query.NewError(nil, "Primary index cannot be dropped.")
}

func (pi *indexIndex) ScanBucket(ch dparval.ValueChannel, warnch, errch query.ErrorChannel) {
	pi.ScanEntries(ch, warnch, errch)
}

func (pi *indexIndex) ScanEntries(ch dparval.ValueChannel, warnch, errch query.ErrorChannel) {
	defer close(ch)
	defer close(warnch)
	defer close(errch)

	poolIds, err := pi.bucket.pool.site.actualSite.PoolIds()
	if err == nil {
		for _, poolId := range poolIds {
			pool, err := pi.bucket.pool.site.actualSite.PoolById(poolId)
			if err == nil {
				bucketIds, err := pool.BucketIds()
				if err == nil {
					for _, bucketId := range bucketIds {
						bucket, err := pool.BucketById(bucketId)
						if err == nil {
							indexIds, err := bucket.IndexIds()
							if err == nil {
								for _, indexId := range indexIds {
									doc := dparval.NewValue(map[string]interface{}{})
									doc.SetAttachment("meta", map[string]interface{}{"id": fmt.Sprintf("%s/%s/%s", poolId, bucketId, indexId)})
									ch <- doc
								}
							}
						}
					}
				}
			}
		}
	}
}
<commit_msg>change format of index_key back to array of strings<commit_after>//  Copyright (c) 2013 Couchbase, Inc.
//  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 system

import (
	"encoding/json"
	"fmt"
	"strings"

	"github.com/couchbaselabs/dparval"
	"github.com/couchbaselabs/tuqtng/catalog"
	"github.com/couchbaselabs/tuqtng/query"
)

type indexbucket struct {
	pool    *pool
	name    string
	primary catalog.PrimaryIndex
}

func (b *indexbucket) Release() {
}

func (b *indexbucket) PoolId() string {
	return b.pool.Id()
}

func (b *indexbucket) Id() string {
	return b.Name()
}

func (b *indexbucket) Name() string {
	return b.name
}

func (b *indexbucket) Count() (int64, query.Error) {
	return 0, query.NewError(nil, "Not Supported")
}

func (b *indexbucket) IndexIds() ([]string, query.Error) {
	return []string{b.primary.Id()}, nil
}

func (b *indexbucket) IndexNames() ([]string, query.Error) {
	return []string{b.primary.Name()}, nil
}

func (b *indexbucket) IndexById(id string) (catalog.Index, query.Error) {
	return b.primary, nil
}

func (b *indexbucket) IndexByName(name string) (catalog.Index, query.Error) {
	return b.primary, nil
}

func (b *indexbucket) IndexByPrimary() (catalog.PrimaryIndex, query.Error) {
	return b.primary, nil
}

func (b *indexbucket) Indexes() ([]catalog.Index, query.Error) {
	return []catalog.Index{b.primary}, nil
}

func (b *indexbucket) BulkFetch(ids []string) (map[string]*dparval.Value, query.Error) {
	rv := make(map[string]*dparval.Value, 0)
	for _, id := range ids {
		item, e := b.Fetch(id)
		if e != nil {
			return nil, e
		}
		rv[id] = item
	}
	return rv, nil
}

func (b *indexbucket) Fetch(id string) (item *dparval.Value, e query.Error) {
	ids := strings.SplitN(id, "/", 3)

	pool, err := b.pool.site.actualSite.PoolById(ids[0])
	if pool != nil {
		bucket, _ := pool.BucketById(ids[1])
		if bucket != nil {
			index, _ := bucket.IndexById(ids[2])
			if index != nil {
				doc := map[string]interface{}{
					"id":         index.Id(),
					"name":       index.Name(),
					"bucket_id":  bucket.Id(),
					"pool_id":    pool.Id(),
					"site_id":    b.pool.site.actualSite.Id(),
					"index_key":  catalogObjectToJSONSafe(indexKeyToIndexKeyStringArray(index.Key())),
					"index_type": catalogObjectToJSONSafe(index.Type()),
				}
				return dparval.NewValue(doc), nil
			}
		}
	}
	return nil, err
}

func indexKeyToIndexKeyStringArray(key catalog.IndexKey) []string {
	rv := make([]string, len(key))
	for i, kp := range key {
		rv[i] = kp.String()
	}
	return rv
}

func catalogObjectToJSONSafe(catobj interface{}) interface{} {
	var rv interface{}
	bytes, err := json.Marshal(catobj)
	if err == nil {
		json.Unmarshal(bytes, &rv)
	}
	return rv
}

func (b *indexbucket) CreatePrimaryIndex() (catalog.PrimaryIndex, query.Error) {
	if b.primary != nil {
		return b.primary, nil
	}

	return nil, query.NewError(nil, "Not supported.")
}

func (b *indexbucket) CreateIndex(name string, key catalog.IndexKey, using catalog.IndexType) (catalog.Index, query.Error) {
	return nil, query.NewError(nil, "Not supported.")
}

func newIndexesBucket(p *pool) (*indexbucket, query.Error) {
	b := new(indexbucket)
	b.pool = p
	b.name = BUCKET_NAME_INDEXES

	b.primary = &indexIndex{name: "primary", bucket: b}

	return b, nil
}

type indexIndex struct {
	name   string
	bucket *indexbucket
}

func (pi *indexIndex) BucketId() string {
	return pi.bucket.Id()
}

func (pi *indexIndex) Id() string {
	return pi.Name()
}

func (pi *indexIndex) Name() string {
	return pi.name
}

func (pi *indexIndex) Type() catalog.IndexType {
	return catalog.UNSPECIFIED
}

func (pi *indexIndex) IsPrimary() bool {
	return true
}

func (pi *indexIndex) Key() catalog.IndexKey {
	// FIXME
	return nil
}

func (pi *indexIndex) Drop() query.Error {
	return query.NewError(nil, "Primary index cannot be dropped.")
}

func (pi *indexIndex) ScanBucket(ch dparval.ValueChannel, warnch, errch query.ErrorChannel) {
	pi.ScanEntries(ch, warnch, errch)
}

func (pi *indexIndex) ScanEntries(ch dparval.ValueChannel, warnch, errch query.ErrorChannel) {
	defer close(ch)
	defer close(warnch)
	defer close(errch)

	poolIds, err := pi.bucket.pool.site.actualSite.PoolIds()
	if err == nil {
		for _, poolId := range poolIds {
			pool, err := pi.bucket.pool.site.actualSite.PoolById(poolId)
			if err == nil {
				bucketIds, err := pool.BucketIds()
				if err == nil {
					for _, bucketId := range bucketIds {
						bucket, err := pool.BucketById(bucketId)
						if err == nil {
							indexIds, err := bucket.IndexIds()
							if err == nil {
								for _, indexId := range indexIds {
									doc := dparval.NewValue(map[string]interface{}{})
									doc.SetAttachment("meta", map[string]interface{}{"id": fmt.Sprintf("%s/%s/%s", poolId, bucketId, indexId)})
									ch <- doc
								}
							}
						}
					}
				}
			}
		}
	}
}
<|endoftext|>


<commit_before>package cmds

import (
	"bufio"
	"errors"
	"fmt"
	"os"
	"strings"

	"google.golang.org/grpc/status"

	"github.com/pachyderm/pachyderm/src/client"
	"github.com/pachyderm/pachyderm/src/client/auth"
	"github.com/pachyderm/pachyderm/src/client/pkg/config"
	"github.com/pachyderm/pachyderm/src/server/pkg/cmdutil"

	"github.com/spf13/cobra"
)

var githubAuthLink = `https://github.com/login/oauth/authorize?client_id=d3481e92b4f09ea74ff8&redirect_uri=https%3A%2F%2Fpachyderm.io%2Flogin-hook%2Fdisplay-token.html`

// StripGRPCCode removes GRPC error code information from 'err' if it came from
// GRPC (and returns it unchanged otherwise)
func StripGRPCCode(err error) error {
	if err == nil {
		return nil
	}
	if s, ok := status.FromError(err); err != nil && ok {
		return errors.New(s.Message())
	}
	return err
}

// ActivateCmd returns a cobra.Command to activate Pachyderm's auth system
func ActivateCmd() *cobra.Command {
	var admins []string
	activate := &cobra.Command{
		Use:   "activate --admins=[admins...]",
		Short: "Activate Pachyderm's auth system",
		Long:  "Activate Pachyderm's auth system, and restrict access to existing data to cluster admins",
		Run: cmdutil.Run(func(args []string) error {
			if len(admins) == 0 {
				return fmt.Errorf("must specify at least one cluster admin to enable " +
					"auth")
			}
			c, err := client.NewOnUserMachine(true, "user")
			if err != nil {
				return fmt.Errorf("could not connect: %s", err.Error())
			}
			_, err = c.Activate(c.Ctx(), &auth.ActivateRequest{
				Admins: admins,
			})
			return StripGRPCCode(err)
		}),
	}
	activate.PersistentFlags().StringSliceVar(&admins, "admins", []string{},
		"Comma-separated list of GitHub usernames. These users will be the initial "+
			"cluster admins (must be nonempty, as any Pachyderm cluster with auth "+
			"activated must have at least one admin.")
	return activate
}

// DeactivateCmd returns a cobra.Command to delete all ACLs, tokens, and admins,
// deactivating Pachyderm's auth system
func DeactivateCmd() *cobra.Command {
	deactivate := &cobra.Command{
		Use:   "deactivate",
		Short: "Delete all ACLs, tokens, and admins, and deactivate Pachyderm auth",
		Long: "Deactivate Pachyderm's auth system, which will delete ALL auth " +
			"tokens, ACLs and admins, and expose all data in the cluster to any " +
			"user with cluster access. Use with caution.",
		Run: cmdutil.Run(func(args []string) error {
			fmt.Println("Are you sure you want to delete ALL auth information " +
				"(ACLs, tokens, and admins) in this cluster, and expose ALL data? yN")
			confirm, err := bufio.NewReader(os.Stdin).ReadString('\n')
			if !strings.Contains("yY", confirm[:1]) {
				return fmt.Errorf("operation aborted")
			}
			c, err := client.NewOnUserMachine(true, "user")
			if err != nil {
				return fmt.Errorf("could not connect: %s", err.Error())
			}
			_, err = c.Deactivate(c.Ctx(), &auth.DeactivateRequest{})
			return StripGRPCCode(err)
		}),
	}
	return deactivate
}

// LoginCmd returns a cobra.Command to login to a Pachyderm cluster with your
// GitHub account. Any resources that have been restricted to the email address
// registered with your GitHub account will subsequently be accessible.
func LoginCmd() *cobra.Command {
	var username string
	login := &cobra.Command{
		Use:   "login",
		Short: "Login to Pachyderm with your GitHub account",
		Long: "Login to Pachyderm with your GitHub account. Any resources that " +
			"have been restricted to the email address registered with your GitHub " +
			"account will subsequently be accessible.",
		Run: cmdutil.Run(func([]string) error {
			fmt.Println("(1) Please paste this link into a browser:\n\n" +
				githubAuthLink + "\n\n" +
				"(You will be directed to GitHub and asked to authorize Pachyderm's " +
				"login app on Github. If you accept, you will be given a token to " +
				"paste here, which will give you an externally verified account in " +
				"this Pachyderm cluster)\n\n(2) Please paste the token you receive " +
				"from GitHub here:")
			token, err := bufio.NewReader(os.Stdin).ReadString('\n')
			if err != nil {
				return fmt.Errorf("error reading token: %s", err.Error())
			}
			token = strings.TrimSpace(token) // drop trailing newline
			cfg, err := config.Read()
			if err != nil {
				return fmt.Errorf("error reading Pachyderm config (for cluster "+
					"address): %s", err.Error())
			}
			c, err := client.NewOnUserMachine(true, "user")
			if err != nil {
				return fmt.Errorf("could not connect: %s", err.Error())
			}
			resp, err := c.Authenticate(
				c.Ctx(),
				&auth.AuthenticateRequest{GithubUsername: username, GithubToken: token})
			if err != nil {
				return fmt.Errorf("error authenticating with Pachyderm cluster: %s",
					StripGRPCCode(err).Error())
			}
			if cfg.V1 == nil {
				cfg.V1 = &config.ConfigV1{}
			}
			cfg.V1.SessionToken = resp.PachToken
			return cfg.Write()
		}),
	}
	login.PersistentFlags().StringVar(&username, "user", "", "GitHub username of "+
		"the user logging in. If unset, the username will be inferred from the "+
		"github authorization code")
	return login
}

// CheckCmd returns a cobra command that sends an "Authorize" RPC to Pachd, to
// determine whether the specified user has access to the specified repo.
func CheckCmd() *cobra.Command {
	check := &cobra.Command{
		Use:   "check (none|reader|writer|owner) repo",
		Short: "Check whether you have reader/writer/etc-level access to 'repo'",
		Long: "Check whether you or another user has a reader/writer/etc-level " +
			"access to 'repo'. For " +
			"example, 'pachctl auth check reader private-data' prints " +
			"\"true\" if the you have at least \"reader\" access " +
			"to the repo \"private-data\" (you could be a reader, writer, " +
			"or owner). Unlike. `pachctl get-acl`, you do not need to have access " +
			"to 'repo' to discover your own acess level.",
		Run: cmdutil.RunFixedArgs(2, func(args []string) error {
			scope, err := auth.ParseScope(args[0])
			if err != nil {
				return err
			}
			repo := args[1]
			c, err := client.NewOnUserMachine(true, "user")
			if err != nil {
				return fmt.Errorf("could not connect: %s", err.Error())
			}
			resp, err := c.Authorize(c.Ctx(), &auth.AuthorizeRequest{
				Repo:  repo,
				Scope: scope,
			})
			if err != nil {
				return StripGRPCCode(err)
			}
			fmt.Printf("%t\n", resp.Authorized)
			return nil
		}),
	}
	return check
}

// GetCmd returns a cobra command that gets either the ACL for a Pachyderm
// repo or another user's scope of access to that repo
func GetCmd() *cobra.Command {
	setScope := &cobra.Command{
		Use:   "get [username] repo",
		Short: "Get the ACL for 'repo' or the access that 'username' has to 'repo'",
		Long: "Get the ACL for 'repo' or the access that 'username' has to " +
			"'repo'. For example, 'pachctl auth get github-alice private-data' " +
			"prints \"reader\", \"writer\", \"owner\", or \"none\", depending on " +
			"the privileges that \"github-alice\" has in \"repo\". Currently all " +
			"Pachyderm authentication uses GitHub OAuth, so 'username' must be a " +
			"GitHub username",
		Run: cmdutil.RunBoundedArgs(1, 2, func(args []string) error {
			c, err := client.NewOnUserMachine(true, "user")
			if err != nil {
				return fmt.Errorf("could not connect: %s", err.Error())
			}
			if len(args) == 1 {
				// Get ACL for a repo
				repo := args[0]
				resp, err := c.GetACL(c.Ctx(), &auth.GetACLRequest{
					Repo: repo,
				})
				if err != nil {
					return StripGRPCCode(err)
				}
				fmt.Println(resp.ACL.String())
				return nil
			}
			// Get User's scope on an acl
			username, repo := args[0], args[1]
			resp, err := c.GetScope(c.Ctx(), &auth.GetScopeRequest{
				Repos:    []string{repo},
				Username: username,
			})
			if err != nil {
				return StripGRPCCode(err)
			}
			fmt.Println(resp.Scopes[0].String())
			return nil
		}),
	}
	return setScope
}

// SetScopeCmd returns a cobra command that lets a user set the level of access
// that another user has to a repo
func SetScopeCmd() *cobra.Command {
	setScope := &cobra.Command{
		Use:   "set username (none|reader|writer|owner) repo",
		Short: "Set the scope of access that 'username' has to 'repo'",
		Long: "Set the scope of access that 'username' has to 'repo'. For " +
			"example, 'pachctl auth set github-alice none private-data' prevents " +
			"\"github-alice\" from interacting with the \"private-data\" repo in any " +
			"way (the default). Similarly, 'pachctl auth set github-alice reader " +
			"private-data' would let \"github-alice\" read from \"private-data\" but " +
			"not create commits (writer) or modify the repo's access permissions " +
			"(owner). Currently all Pachyderm authentication uses GitHub OAuth, so " +
			"'username' must be a GitHub username",
		Run: cmdutil.RunFixedArgs(3, func(args []string) error {
			scope, err := auth.ParseScope(args[1])
			if err != nil {
				return err
			}
			username, repo := args[0], args[2]
			c, err := client.NewOnUserMachine(true, "user")
			if err != nil {
				return fmt.Errorf("could not connect: %s", err.Error())
			}
			_, err = c.SetScope(c.Ctx(), &auth.SetScopeRequest{
				Repo:     repo,
				Scope:    scope,
				Username: username,
			})
			return StripGRPCCode(err)
		}),
	}
	return setScope
}

// ListAdminsCmd returns a cobra command that lists the current cluster admins
func ListAdminsCmd() *cobra.Command {
	listAdmins := &cobra.Command{
		Use:   "list-admins",
		Short: "List the current cluster admins",
		Long:  "List the current cluster admins",
		Run: cmdutil.Run(func([]string) error {
			c, err := client.NewOnUserMachine(true, "user")
			if err != nil {
				return err
			}
			resp, err := c.GetAdmins(c.Ctx(), &auth.GetAdminsRequest{})
			if err != nil {
				return StripGRPCCode(err)
			}
			for _, user := range resp.Admins {
				fmt.Println(user)
			}
			return nil
		}),
	}
	return listAdmins
}

// ModifyAdminsCmd returns a cobra command that modifies the set of current
// cluster admins
func ModifyAdminsCmd() *cobra.Command {
	var add []string
	var remove []string
	modifyAdmins := &cobra.Command{
		Use:   "modify-admins",
		Short: "Modify the current cluster admins",
		Long: "Modify the current cluster admins. --add accepts a comma-" +
			"separated list of users to grant admin status, and --remove accepts a " +
			"comma-separated list of users to revoke admin status",
		Run: cmdutil.Run(func([]string) error {
			c, err := client.NewOnUserMachine(true, "user")
			if err != nil {
				return err
			}
			_, err = c.ModifyAdmins(c.Ctx(), &auth.ModifyAdminsRequest{
				Add:    add,
				Remove: remove,
			})
			return StripGRPCCode(err)
		}),
	}
	modifyAdmins.PersistentFlags().StringSliceVar(&add, "add", []string{},
		"Comma-separated list of users to grant admin status")
	modifyAdmins.PersistentFlags().StringSliceVar(&remove, "remove", []string{},
		"Comma-separated list of users revoke admin status")
	return modifyAdmins
}

// Cmds returns a list of cobra commands for authenticating and authorizing
// users in an auth-enabled Pachyderm cluster.
func Cmds() []*cobra.Command {
	auth := &cobra.Command{
		Use:   "auth",
		Short: "Auth commands manage access to data in a Pachyderm cluster",
		Long:  "Auth commands manage access to data in a Pachyderm cluster",
	}
	auth.AddCommand(ActivateCmd())
	auth.AddCommand(DeactivateCmd())
	auth.AddCommand(LoginCmd())
	auth.AddCommand(CheckCmd())
	auth.AddCommand(SetScopeCmd())
	auth.AddCommand(GetCmd())
	auth.AddCommand(ListAdminsCmd())
	auth.AddCommand(ModifyAdminsCmd())
	return []*cobra.Command{auth}
}
<commit_msg>Replace StripGRPCCode with grpcutil.ScrubGRPC<commit_after>package cmds

import (
	"bufio"
	"fmt"
	"os"
	"strings"

	"github.com/pachyderm/pachyderm/src/client"
	"github.com/pachyderm/pachyderm/src/client/auth"
	"github.com/pachyderm/pachyderm/src/client/pkg/config"
	"github.com/pachyderm/pachyderm/src/client/pkg/grpcutil"
	"github.com/pachyderm/pachyderm/src/server/pkg/cmdutil"

	"github.com/spf13/cobra"
)

var githubAuthLink = `https://github.com/login/oauth/authorize?client_id=d3481e92b4f09ea74ff8&redirect_uri=https%3A%2F%2Fpachyderm.io%2Flogin-hook%2Fdisplay-token.html`

// ActivateCmd returns a cobra.Command to activate Pachyderm's auth system
func ActivateCmd() *cobra.Command {
	var admins []string
	activate := &cobra.Command{
		Use:   "activate --admins=[admins...]",
		Short: "Activate Pachyderm's auth system",
		Long:  "Activate Pachyderm's auth system, and restrict access to existing data to cluster admins",
		Run: cmdutil.Run(func(args []string) error {
			if len(admins) == 0 {
				return fmt.Errorf("must specify at least one cluster admin to enable " +
					"auth")
			}
			c, err := client.NewOnUserMachine(true, "user")
			if err != nil {
				return fmt.Errorf("could not connect: %s", err.Error())
			}
			_, err = c.Activate(c.Ctx(), &auth.ActivateRequest{
				Admins: admins,
			})
			return grpcutil.ScrubGRPC(err)
		}),
	}
	activate.PersistentFlags().StringSliceVar(&admins, "admins", []string{},
		"Comma-separated list of GitHub usernames. These users will be the initial "+
			"cluster admins (must be nonempty, as any Pachyderm cluster with auth "+
			"activated must have at least one admin.")
	return activate
}

// DeactivateCmd returns a cobra.Command to delete all ACLs, tokens, and admins,
// deactivating Pachyderm's auth system
func DeactivateCmd() *cobra.Command {
	deactivate := &cobra.Command{
		Use:   "deactivate",
		Short: "Delete all ACLs, tokens, and admins, and deactivate Pachyderm auth",
		Long: "Deactivate Pachyderm's auth system, which will delete ALL auth " +
			"tokens, ACLs and admins, and expose all data in the cluster to any " +
			"user with cluster access. Use with caution.",
		Run: cmdutil.Run(func(args []string) error {
			fmt.Println("Are you sure you want to delete ALL auth information " +
				"(ACLs, tokens, and admins) in this cluster, and expose ALL data? yN")
			confirm, err := bufio.NewReader(os.Stdin).ReadString('\n')
			if !strings.Contains("yY", confirm[:1]) {
				return fmt.Errorf("operation aborted")
			}
			c, err := client.NewOnUserMachine(true, "user")
			if err != nil {
				return fmt.Errorf("could not connect: %s", err.Error())
			}
			_, err = c.Deactivate(c.Ctx(), &auth.DeactivateRequest{})
			return grpcutil.ScrubGRPC(err)
		}),
	}
	return deactivate
}

// LoginCmd returns a cobra.Command to login to a Pachyderm cluster with your
// GitHub account. Any resources that have been restricted to the email address
// registered with your GitHub account will subsequently be accessible.
func LoginCmd() *cobra.Command {
	var username string
	login := &cobra.Command{
		Use:   "login",
		Short: "Login to Pachyderm with your GitHub account",
		Long: "Login to Pachyderm with your GitHub account. Any resources that " +
			"have been restricted to the email address registered with your GitHub " +
			"account will subsequently be accessible.",
		Run: cmdutil.Run(func([]string) error {
			fmt.Println("(1) Please paste this link into a browser:\n\n" +
				githubAuthLink + "\n\n" +
				"(You will be directed to GitHub and asked to authorize Pachyderm's " +
				"login app on Github. If you accept, you will be given a token to " +
				"paste here, which will give you an externally verified account in " +
				"this Pachyderm cluster)\n\n(2) Please paste the token you receive " +
				"from GitHub here:")
			token, err := bufio.NewReader(os.Stdin).ReadString('\n')
			if err != nil {
				return fmt.Errorf("error reading token: %s", err.Error())
			}
			token = strings.TrimSpace(token) // drop trailing newline
			cfg, err := config.Read()
			if err != nil {
				return fmt.Errorf("error reading Pachyderm config (for cluster "+
					"address): %s", err.Error())
			}
			c, err := client.NewOnUserMachine(true, "user")
			if err != nil {
				return fmt.Errorf("could not connect: %s", err.Error())
			}
			resp, err := c.Authenticate(
				c.Ctx(),
				&auth.AuthenticateRequest{GithubUsername: username, GithubToken: token})
			if err != nil {
				return fmt.Errorf("error authenticating with Pachyderm cluster: %s",
					grpcutil.ScrubGRPC(err).Error())
			}
			if cfg.V1 == nil {
				cfg.V1 = &config.ConfigV1{}
			}
			cfg.V1.SessionToken = resp.PachToken
			return cfg.Write()
		}),
	}
	login.PersistentFlags().StringVar(&username, "user", "", "GitHub username of "+
		"the user logging in. If unset, the username will be inferred from the "+
		"github authorization code")
	return login
}

// CheckCmd returns a cobra command that sends an "Authorize" RPC to Pachd, to
// determine whether the specified user has access to the specified repo.
func CheckCmd() *cobra.Command {
	check := &cobra.Command{
		Use:   "check (none|reader|writer|owner) repo",
		Short: "Check whether you have reader/writer/etc-level access to 'repo'",
		Long: "Check whether you or another user has a reader/writer/etc-level " +
			"access to 'repo'. For " +
			"example, 'pachctl auth check reader private-data' prints " +
			"\"true\" if the you have at least \"reader\" access " +
			"to the repo \"private-data\" (you could be a reader, writer, " +
			"or owner). Unlike. `pachctl get-acl`, you do not need to have access " +
			"to 'repo' to discover your own acess level.",
		Run: cmdutil.RunFixedArgs(2, func(args []string) error {
			scope, err := auth.ParseScope(args[0])
			if err != nil {
				return err
			}
			repo := args[1]
			c, err := client.NewOnUserMachine(true, "user")
			if err != nil {
				return fmt.Errorf("could not connect: %s", err.Error())
			}
			resp, err := c.Authorize(c.Ctx(), &auth.AuthorizeRequest{
				Repo:  repo,
				Scope: scope,
			})
			if err != nil {
				return grpcutil.ScrubGRPC(err)
			}
			fmt.Printf("%t\n", resp.Authorized)
			return nil
		}),
	}
	return check
}

// GetCmd returns a cobra command that gets either the ACL for a Pachyderm
// repo or another user's scope of access to that repo
func GetCmd() *cobra.Command {
	setScope := &cobra.Command{
		Use:   "get [username] repo",
		Short: "Get the ACL for 'repo' or the access that 'username' has to 'repo'",
		Long: "Get the ACL for 'repo' or the access that 'username' has to " +
			"'repo'. For example, 'pachctl auth get github-alice private-data' " +
			"prints \"reader\", \"writer\", \"owner\", or \"none\", depending on " +
			"the privileges that \"github-alice\" has in \"repo\". Currently all " +
			"Pachyderm authentication uses GitHub OAuth, so 'username' must be a " +
			"GitHub username",
		Run: cmdutil.RunBoundedArgs(1, 2, func(args []string) error {
			c, err := client.NewOnUserMachine(true, "user")
			if err != nil {
				return fmt.Errorf("could not connect: %s", err.Error())
			}
			if len(args) == 1 {
				// Get ACL for a repo
				repo := args[0]
				resp, err := c.GetACL(c.Ctx(), &auth.GetACLRequest{
					Repo: repo,
				})
				if err != nil {
					return grpcutil.ScrubGRPC(err)
				}
				fmt.Println(resp.ACL.String())
				return nil
			}
			// Get User's scope on an acl
			username, repo := args[0], args[1]
			resp, err := c.GetScope(c.Ctx(), &auth.GetScopeRequest{
				Repos:    []string{repo},
				Username: username,
			})
			if err != nil {
				return grpcutil.ScrubGRPC(err)
			}
			fmt.Println(resp.Scopes[0].String())
			return nil
		}),
	}
	return setScope
}

// SetScopeCmd returns a cobra command that lets a user set the level of access
// that another user has to a repo
func SetScopeCmd() *cobra.Command {
	setScope := &cobra.Command{
		Use:   "set username (none|reader|writer|owner) repo",
		Short: "Set the scope of access that 'username' has to 'repo'",
		Long: "Set the scope of access that 'username' has to 'repo'. For " +
			"example, 'pachctl auth set github-alice none private-data' prevents " +
			"\"github-alice\" from interacting with the \"private-data\" repo in any " +
			"way (the default). Similarly, 'pachctl auth set github-alice reader " +
			"private-data' would let \"github-alice\" read from \"private-data\" but " +
			"not create commits (writer) or modify the repo's access permissions " +
			"(owner). Currently all Pachyderm authentication uses GitHub OAuth, so " +
			"'username' must be a GitHub username",
		Run: cmdutil.RunFixedArgs(3, func(args []string) error {
			scope, err := auth.ParseScope(args[1])
			if err != nil {
				return err
			}
			username, repo := args[0], args[2]
			c, err := client.NewOnUserMachine(true, "user")
			if err != nil {
				return fmt.Errorf("could not connect: %s", err.Error())
			}
			_, err = c.SetScope(c.Ctx(), &auth.SetScopeRequest{
				Repo:     repo,
				Scope:    scope,
				Username: username,
			})
			return grpcutil.ScrubGRPC(err)
		}),
	}
	return setScope
}

// ListAdminsCmd returns a cobra command that lists the current cluster admins
func ListAdminsCmd() *cobra.Command {
	listAdmins := &cobra.Command{
		Use:   "list-admins",
		Short: "List the current cluster admins",
		Long:  "List the current cluster admins",
		Run: cmdutil.Run(func([]string) error {
			c, err := client.NewOnUserMachine(true, "user")
			if err != nil {
				return err
			}
			resp, err := c.GetAdmins(c.Ctx(), &auth.GetAdminsRequest{})
			if err != nil {
				return grpcutil.ScrubGRPC(err)
			}
			for _, user := range resp.Admins {
				fmt.Println(user)
			}
			return nil
		}),
	}
	return listAdmins
}

// ModifyAdminsCmd returns a cobra command that modifies the set of current
// cluster admins
func ModifyAdminsCmd() *cobra.Command {
	var add []string
	var remove []string
	modifyAdmins := &cobra.Command{
		Use:   "modify-admins",
		Short: "Modify the current cluster admins",
		Long: "Modify the current cluster admins. --add accepts a comma-" +
			"separated list of users to grant admin status, and --remove accepts a " +
			"comma-separated list of users to revoke admin status",
		Run: cmdutil.Run(func([]string) error {
			c, err := client.NewOnUserMachine(true, "user")
			if err != nil {
				return err
			}
			_, err = c.ModifyAdmins(c.Ctx(), &auth.ModifyAdminsRequest{
				Add:    add,
				Remove: remove,
			})
			return grpcutil.ScrubGRPC(err)
		}),
	}
	modifyAdmins.PersistentFlags().StringSliceVar(&add, "add", []string{},
		"Comma-separated list of users to grant admin status")
	modifyAdmins.PersistentFlags().StringSliceVar(&remove, "remove", []string{},
		"Comma-separated list of users revoke admin status")
	return modifyAdmins
}

// Cmds returns a list of cobra commands for authenticating and authorizing
// users in an auth-enabled Pachyderm cluster.
func Cmds() []*cobra.Command {
	auth := &cobra.Command{
		Use:   "auth",
		Short: "Auth commands manage access to data in a Pachyderm cluster",
		Long:  "Auth commands manage access to data in a Pachyderm cluster",
	}
	auth.AddCommand(ActivateCmd())
	auth.AddCommand(DeactivateCmd())
	auth.AddCommand(LoginCmd())
	auth.AddCommand(CheckCmd())
	auth.AddCommand(SetScopeCmd())
	auth.AddCommand(GetCmd())
	auth.AddCommand(ListAdminsCmd())
	auth.AddCommand(ModifyAdminsCmd())
	return []*cobra.Command{auth}
}
<|endoftext|>


<commit_before>package version

// Version is the version of the build.
const Version = "0.1.15"
<commit_msg>bump 0.1.16-dev<commit_after>package version

// Version is the version of the build.
const Version = "0.1.16-dev"
<|endoftext|>