<commit_before>package queue

import (
	"context"
	"fmt"
	"time"

	"koding/db/models"
	"koding/db/mongodb"
	"koding/db/mongodb/modelhelper"
	"koding/kites/kloud/contexthelper/request"
	"koding/kites/kloud/klient"
	"koding/kites/kloud/machinestate"
	"koding/kites/kloud/stackplan"
	"koding/kites/kloud/utils/object"

	"github.com/koding/kite"
	"github.com/koding/kite/kontrol"
	"github.com/koding/logging"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

var (
	defaultInterval = 15 * time.Second
	planTimeout     = 50 * time.Minute
)

type Queue struct {
	Log      logging.Logger
	Interval time.Duration
	MongoDB  *mongodb.MongoDB
	Kite     *kite.Kite

	stackers map[string]*stackplan.Stacker
}

// RunChecker runs the checker for Koding and AWS providers every given
// interval time. It fetches a single document.
func (q *Queue) Run() {
	q.Log.Debug("queue started with interval %s", q.interval())

	t := time.NewTicker(q.interval())
	defer t.Stop()

	for range t.C {
		q.Log.Debug("queue interval hit")

		for _, s := range q.stackers {
			q.Log.Debug("queue running checker for %q provider", s.Provider.Name)

			go func() {
				if err := q.Check(s); err != nil {
					q.Log.Debug("failed to check %q provider: %s", s.Provider.Name, err)
				}
			}()
		}
	}
}

// Fetch provider fetches the machine and populates the fields for the given
// provider.
func (q *Queue) FetchProvider(provider string, machine interface{}) error {
	query := func(c *mgo.Collection) error {
		// check only machines that:
		// 1. belongs to the given provider
		// 2. are running
		// 3. are not always on machines
		// 3. are not assigned to anyone yet (unlocked)
		// 4. are not picked up by others yet recently in last 30 seconds
		//
		// The $ne is used to catch documents whose field is not true including
		// that do not contain that particular field
		egligibleMachines := bson.M{
			"provider":            provider,
			"status.state":        machinestate.Running.String(),
			"meta.alwaysOn":       bson.M{"$ne": true},
			"assignee.inProgress": bson.M{"$ne": true},
			"assignee.assignedAt": bson.M{"$lt": time.Now().UTC().Add(-time.Second * 30)},
		}

		// update so we don't pick up recent things
		update := mgo.Change{
			Update: bson.M{
				"$set": bson.M{
					"assignee.assignedAt": time.Now().UTC(),
				},
			},
		}

		// We sort according to the latest assignment date, which let's us pick
		// always the oldest one instead of random/first. Returning an error
		// means there is no document that matches our criteria.
		_, err := c.Find(egligibleMachines).Sort("assignee.assignedAt").Limit(1).Apply(update, machine)
		return err
	}

	return q.MongoDB.Run("jMachines", query)
}

func (q *Queue) Register(s *stackplan.Stacker) {
	if q.stackers == nil {
		q.stackers = make(map[string]*stackplan.Stacker)
	}

	if _, ok := q.stackers[s.Provider.Name]; ok {
		panic("queue: duplicate stacker: " + s.Provider.Name)
	}

	q.stackers[s.Provider.Name] = s
}

func (q *Queue) interval() time.Duration {
	if q.Interval != 0 {
		return q.Interval
	}

	return defaultInterval
}

func (q *Queue) Check(s *stackplan.Stacker) error {
	var m models.Machine

	err := q.FetchProvider(s.Provider.Name, &m)
	if err != nil {
		q.Log.Debug("no running machines for %q provider found", s.Provider.Name)

		// do not show an error if the query didn't find anything, that
		// means there is no such a document, which we don't care
		if err == mgo.ErrNotFound {
			return nil
		}

		return fmt.Errorf("check %q provider error: %s", s.Provider.Name, err)
	}

	req := &kite.Request{
		Method: "internal",
	}

	if u := m.Owner(); u != nil {
		req.Username = u.Username
	}

	ctx := request.NewContext(context.Background(), req)

	bm, err := s.BuildBaseMachine(ctx, &m)
	if err != nil {
		return err
	}

	machine, err := s.BuildMachine(ctx, bm)
	if err != nil {
		return err
	}

	switch err := q.CheckUsage(s.Provider.Name, machine, bm, ctx); err {
	case nil:
		return nil
	case kite.ErrNoKitesAvailable, kontrol.ErrQueryFieldsEmpty, klient.ErrDialingFailed:
		return nil
	default:
		return fmt.Errorf("[%s] check usage of AWS klient kite [%s] err: %s", m.ObjectId.Hex(), m.IpAddress, err)
	}
}

func (q *Queue) CheckUsage(provider string, m stackplan.Machine, bm *stackplan.BaseMachine, ctx context.Context) error {
	q.Log.Debug("Checking %q machine\n%+v\n", provider, bm.Machine)

	c, err := klient.Connect(q.Kite, bm.QueryString)
	if err != nil {
		q.Log.Debug("Error connecting to klient, stopping if needed. Error: %s", err)
		return err
	}

	// replace with the real and authenticated username
	if bm.User == nil {
		bm.User = &models.User{}
	}

	bm.User.Name = c.Username

	// get the usage directly from the klient, which is the most predictable source
	usg, err := c.Usage()
	c.Close() // close the underlying connection once we get the usage
	if err != nil {
		return fmt.Errorf("failure getting %q klient usage: %s", bm.QueryString, err)
	}

	q.Log.Debug("machine [%s] (aws) is inactive for %s (plan limit: %s)",
		bm.IpAddress, usg.InactiveDuration, planTimeout)

	// It still have plenty of time to work, do not stop it
	if usg.InactiveDuration <= planTimeout {
		return nil
	}

	q.Log.Info("machine [%s] has reached current plan limit of %s. Shutting down...",
		bm.IpAddress, usg.InactiveDuration)

	// Hasta la vista, baby!
	q.Log.Info("[%s] ======> STOP started (closing inactive machine)<======", bm.ObjectId.Hex())

	meta, err := m.Stop(ctx)
	if err != nil {
		// returning is ok, because Kloud will mark it anyways as stopped if
		// Klient is not rechable anymore with the `info` method
		q.Log.Info("[%s] ======> STOP aborted (closing inactive machine: %s)<======", bm.ObjectId.Hex(), err)

		return err
	}

	q.Log.Info("[%s] ======> STOP finished (closing inactive machine)<======", bm.ObjectId.Hex())

	obj := object.MetaBuilder.Build(meta)
	obj["status.modifiedAt"] = time.Now().UTC()
	obj["status.state"] = machinestate.Stopped.String()
	obj["status.reason"] = "Machine is stopped due to inactivity"

	return modelhelper.UpdateMachine(bm.ObjectId, bson.M{"$set": obj})
}
<commit_msg>kloud/queue: fix for iterator<commit_after>package queue

import (
	"context"
	"fmt"
	"time"

	"koding/db/models"
	"koding/db/mongodb"
	"koding/db/mongodb/modelhelper"
	"koding/kites/kloud/contexthelper/request"
	"koding/kites/kloud/klient"
	"koding/kites/kloud/machinestate"
	"koding/kites/kloud/stackplan"
	"koding/kites/kloud/utils/object"

	"github.com/koding/kite"
	"github.com/koding/kite/kontrol"
	"github.com/koding/logging"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

var (
	defaultInterval = 15 * time.Second
	planTimeout     = 50 * time.Minute
)

type Queue struct {
	Log      logging.Logger
	Interval time.Duration
	MongoDB  *mongodb.MongoDB
	Kite     *kite.Kite

	stackers map[string]*stackplan.Stacker
}

// RunChecker runs the checker for Koding and AWS providers every given
// interval time. It fetches a single document.
func (q *Queue) Run() {
	q.Log.Debug("queue started with interval %s", q.interval())

	t := time.NewTicker(q.interval())
	defer t.Stop()

	for range t.C {
		q.Log.Debug("queue interval hit")

		for _, s := range q.stackers {
			q.Log.Debug("queue running checker for %q provider", s.Provider.Name)

			go func(s *stackplan.Stacker) {
				if err := q.Check(s); err != nil {
					q.Log.Debug("failed to check %q provider: %s", s.Provider.Name, err)
				}
			}(s)
		}
	}
}

// Fetch provider fetches the machine and populates the fields for the given
// provider.
func (q *Queue) FetchProvider(provider string, machine interface{}) error {
	query := func(c *mgo.Collection) error {
		// check only machines that:
		// 1. belongs to the given provider
		// 2. are running
		// 3. are not always on machines
		// 3. are not assigned to anyone yet (unlocked)
		// 4. are not picked up by others yet recently in last 30 seconds
		//
		// The $ne is used to catch documents whose field is not true including
		// that do not contain that particular field
		egligibleMachines := bson.M{
			"provider":            provider,
			"status.state":        machinestate.Running.String(),
			"meta.alwaysOn":       bson.M{"$ne": true},
			"assignee.inProgress": bson.M{"$ne": true},
			"assignee.assignedAt": bson.M{"$lt": time.Now().UTC().Add(-time.Second * 30)},
		}

		// update so we don't pick up recent things
		update := mgo.Change{
			Update: bson.M{
				"$set": bson.M{
					"assignee.assignedAt": time.Now().UTC(),
				},
			},
		}

		// We sort according to the latest assignment date, which let's us pick
		// always the oldest one instead of random/first. Returning an error
		// means there is no document that matches our criteria.
		_, err := c.Find(egligibleMachines).Sort("assignee.assignedAt").Limit(1).Apply(update, machine)
		return err
	}

	return q.MongoDB.Run("jMachines", query)
}

func (q *Queue) Register(s *stackplan.Stacker) {
	if q.stackers == nil {
		q.stackers = make(map[string]*stackplan.Stacker)
	}

	if _, ok := q.stackers[s.Provider.Name]; ok {
		panic("queue: duplicate stacker: " + s.Provider.Name)
	}

	q.stackers[s.Provider.Name] = s
}

func (q *Queue) interval() time.Duration {
	if q.Interval != 0 {
		return q.Interval
	}

	return defaultInterval
}

func (q *Queue) Check(s *stackplan.Stacker) error {
	var m models.Machine

	err := q.FetchProvider(s.Provider.Name, &m)
	if err != nil {
		q.Log.Debug("no running machines for %q provider found", s.Provider.Name)

		// do not show an error if the query didn't find anything, that
		// means there is no such a document, which we don't care
		if err == mgo.ErrNotFound {
			return nil
		}

		return fmt.Errorf("check %q provider error: %s", s.Provider.Name, err)
	}

	req := &kite.Request{
		Method: "internal",
	}

	if u := m.Owner(); u != nil {
		req.Username = u.Username
	}

	ctx := request.NewContext(context.Background(), req)

	bm, err := s.BuildBaseMachine(ctx, &m)
	if err != nil {
		return err
	}

	machine, err := s.BuildMachine(ctx, bm)
	if err != nil {
		return err
	}

	switch err := q.CheckUsage(s.Provider.Name, machine, bm, ctx); err {
	case nil:
		return nil
	case kite.ErrNoKitesAvailable, kontrol.ErrQueryFieldsEmpty, klient.ErrDialingFailed:
		return nil
	default:
		return fmt.Errorf("[%s] check usage of AWS klient kite [%s] err: %s", m.ObjectId.Hex(), m.IpAddress, err)
	}
}

func (q *Queue) CheckUsage(provider string, m stackplan.Machine, bm *stackplan.BaseMachine, ctx context.Context) error {
	q.Log.Debug("Checking %q machine\n%+v\n", provider, bm.Machine)

	c, err := klient.Connect(q.Kite, bm.QueryString)
	if err != nil {
		q.Log.Debug("Error connecting to klient, stopping if needed. Error: %s", err)
		return err
	}

	// replace with the real and authenticated username
	if bm.User == nil {
		bm.User = &models.User{}
	}

	bm.User.Name = c.Username

	// get the usage directly from the klient, which is the most predictable source
	usg, err := c.Usage()
	c.Close() // close the underlying connection once we get the usage
	if err != nil {
		return fmt.Errorf("failure getting %q klient usage: %s", bm.QueryString, err)
	}

	q.Log.Debug("machine [%s] (aws) is inactive for %s (plan limit: %s)",
		bm.IpAddress, usg.InactiveDuration, planTimeout)

	// It still have plenty of time to work, do not stop it
	if usg.InactiveDuration <= planTimeout {
		return nil
	}

	q.Log.Info("machine [%s] has reached current plan limit of %s. Shutting down...",
		bm.IpAddress, usg.InactiveDuration)

	// Hasta la vista, baby!
	q.Log.Info("[%s] ======> STOP started (closing inactive machine)<======", bm.ObjectId.Hex())

	meta, err := m.Stop(ctx)
	if err != nil {
		// returning is ok, because Kloud will mark it anyways as stopped if
		// Klient is not rechable anymore with the `info` method
		q.Log.Info("[%s] ======> STOP aborted (closing inactive machine: %s)<======", bm.ObjectId.Hex(), err)

		return err
	}

	q.Log.Info("[%s] ======> STOP finished (closing inactive machine)<======", bm.ObjectId.Hex())

	obj := object.MetaBuilder.Build(meta)
	obj["status.modifiedAt"] = time.Now().UTC()
	obj["status.state"] = machinestate.Stopped.String()
	obj["status.reason"] = "Machine is stopped due to inactivity"

	return modelhelper.UpdateMachine(bm.ObjectId, bson.M{"$set": obj})
}
<|endoftext|>


<commit_before>package tarp

import (
	"errors"
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
	"log"
	"os"
	"strings"

	"github.com/fatih/set"
	"github.com/spf13/cobra"
)

var (
	failOnFinding  bool
	analyzePackage string
)

func init() {
	RootCmd.AddCommand(analyzeCmd)
	analyzeCmd.Flags().BoolVarP(&failOnFinding, "fail-on-finding", "f", false, "Call os.Exit(1) when functions without direct tests are found")
	analyzeCmd.Flags().StringVarP(&analyzePackage, "package", "p", "", "Package to run analyze on")
}

func getDeclaredNames(in *ast.File, out *set.Set) {
	for _, x := range in.Decls {
		switch f := x.(type) {
		case *ast.FuncDecl:
			functionName := f.Name.Name // "Avoid Stutter" lol
			var parentName string
			if f.Recv != nil && len(f.Recv.List) == 1 { // handles things like `type Example struct`
				r := f.Recv.List[0]
				parentName = r.Type.(*ast.StarExpr).X.(*ast.Ident).Name
			}

			if parentName != "" {
				out.Add(fmt.Sprintf("%s.%s", parentName, functionName))
			} else {
				out.Add(functionName)
			}
		}
	}
}

func getCalledNames(in *ast.File, out *set.Set) {
	// Using switches here to avoid panics, this is probably wrong and bad but ¯\_(ツ)_/¯
	nameToTypeMap := map[string]string{}
	for _, x := range in.Decls {
		switch n := x.(type) {
		case *ast.GenDecl:
			for _, spec := range n.Specs {
				switch global := spec.(type) {
				case *ast.ValueSpec: // for things like `var e Example` declared outside of functions
					varName := global.Names[0].Name
					typeName := global.Type.(*ast.Ident).Name
					nameToTypeMap[varName] = typeName
				}
			}
		case *ast.FuncDecl:
			for _, le := range n.Body.List {
				switch e := le.(type) {
				case *ast.AssignStmt: // handles things like `e := Example{}` (with or without &)
					varName := e.Lhs[0].(*ast.Ident).Name
					typeName := e.Rhs[0].(*ast.UnaryExpr).X.(*ast.CompositeLit).Type.(*ast.Ident).Name
					nameToTypeMap[varName] = typeName
				case *ast.DeclStmt: // handles things like `var e Example`
					varName := e.Decl.(*ast.GenDecl).Specs[0].(*ast.ValueSpec).Names[0].Name
					typeName := e.Decl.(*ast.GenDecl).Specs[0].(*ast.ValueSpec).Type.(*ast.Ident).Name
					nameToTypeMap[varName] = typeName
				case *ast.ExprStmt: // handles function calls
					switch c := e.X.(type) {
					case *ast.CallExpr:
						switch f := c.Fun.(type) {
						case *ast.Ident:
							out.Add(f.Name)
						case *ast.SelectorExpr:
							structVarName := f.X.(*ast.Ident).Name
							calledMethodName := f.Sel.Name
							out.Add(fmt.Sprintf("%s.%s", nameToTypeMap[structVarName], calledMethodName))
						}
					}
				}
			}
		}
	}
}

func buildPackagePath(path string) string {
	gopath := os.Getenv("GOPATH")
	pkgDir := strings.Join([]string{gopath, "src", path}, "/")
	return pkgDir
}

func analyze(_ *cobra.Command, _ []string) {
	pkgDir := buildPackagePath(analyzePackage)
	log.Println("analyzing ", pkgDir)

	_, err := os.Stat(pkgDir)
	if os.IsNotExist(err) {
		log.Fatalf("package dir doesn't exist: %s", pkgDir)
	}

	astPkg, err := parser.ParseDir(token.NewFileSet(), pkgDir, nil, parser.AllErrors)
	if err != nil {
		log.Fatal(err)
	}

	if len(astPkg) == 0 {
		log.Fatal("No go files found!")
	}

	declaredFuncs := set.New()
	calledFuncs := set.New()

	for _, pkg := range astPkg {
		for name, f := range pkg.Files {
			log.Println("analyzing ", name)
			isTest := strings.HasSuffix(name, "_test.go")
			if isTest {
				getCalledNames(f, calledFuncs)
			} else {
				getDeclaredNames(f, declaredFuncs)
			}
		}
	}

	if true {
		declaredFuncNames := set.StringSlice(declaredFuncs)
		log.Printf(`declared functions:
			%s
		`, strings.Join(declaredFuncNames, "\n\t"))

		calledFuncNames := set.StringSlice(calledFuncs)
		log.Printf(`called functions:
			%s
		`, strings.Join(calledFuncNames, "\n\t"))
	}

	diff := set.StringSlice(set.Difference(declaredFuncs, calledFuncs))

	if failOnFinding && len(diff) > 0 {
		errorString := fmt.Sprintf(`The following functions are declared but not called in any tests:
%s
	`, strings.Join(diff, ",\n\t"))
		log.Fatal(errorString)
	}
}

// analyzeCmd represents the analyze command
var analyzeCmd = &cobra.Command{
	Use:   "analyze",
	Short: "Analyze a given package",
	Long:  "Analyze takes a given package's code and determines which functions lack direct test coverage.",
	PreRunE: func(cmd *cobra.Command, args []string) error {
		if analyzePackage == "" {
			return errors.New("the required flag `-p, --package` was not specified")
		}
		return nil
	},
	Run: analyze,
}
<commit_msg>Revert "Fail when no go files are found"<commit_after>package tarp

import (
	"errors"
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
	"log"
	"os"
	"strings"

	"github.com/fatih/set"
	"github.com/spf13/cobra"
)

var (
	failOnFinding  bool
	analyzePackage string
)

func init() {
	RootCmd.AddCommand(analyzeCmd)
	analyzeCmd.Flags().BoolVarP(&failOnFinding, "fail-on-finding", "f", false, "Call os.Exit(1) when functions without direct tests are found")
	analyzeCmd.Flags().StringVarP(&analyzePackage, "package", "p", "", "Package to run analyze on")
}

func getDeclaredNames(in *ast.File, out *set.Set) {
	for _, x := range in.Decls {
		switch f := x.(type) {
		case *ast.FuncDecl:
			functionName := f.Name.Name // "Avoid Stutter" lol
			var parentName string
			if f.Recv != nil && len(f.Recv.List) == 1 { // handles things like `type Example struct`
				r := f.Recv.List[0]
				parentName = r.Type.(*ast.StarExpr).X.(*ast.Ident).Name
			}

			if parentName != "" {
				out.Add(fmt.Sprintf("%s.%s", parentName, functionName))
			} else {
				out.Add(functionName)
			}
		}
	}
}

func getCalledNames(in *ast.File, out *set.Set) {
	// Using switches here to avoid panics, this is probably wrong and bad but ¯\_(ツ)_/¯
	nameToTypeMap := map[string]string{}
	for _, x := range in.Decls {
		switch n := x.(type) {
		case *ast.GenDecl:
			for _, spec := range n.Specs {
				switch global := spec.(type) {
				case *ast.ValueSpec: // for things like `var e Example` declared outside of functions
					varName := global.Names[0].Name
					typeName := global.Type.(*ast.Ident).Name
					nameToTypeMap[varName] = typeName
				}
			}
		case *ast.FuncDecl:
			for _, le := range n.Body.List {
				switch e := le.(type) {
				case *ast.AssignStmt: // handles things like `e := Example{}` (with or without &)
					varName := e.Lhs[0].(*ast.Ident).Name
					typeName := e.Rhs[0].(*ast.UnaryExpr).X.(*ast.CompositeLit).Type.(*ast.Ident).Name
					nameToTypeMap[varName] = typeName
				case *ast.DeclStmt: // handles things like `var e Example`
					varName := e.Decl.(*ast.GenDecl).Specs[0].(*ast.ValueSpec).Names[0].Name
					typeName := e.Decl.(*ast.GenDecl).Specs[0].(*ast.ValueSpec).Type.(*ast.Ident).Name
					nameToTypeMap[varName] = typeName
				case *ast.ExprStmt: // handles function calls
					switch c := e.X.(type) {
					case *ast.CallExpr:
						switch f := c.Fun.(type) {
						case *ast.Ident:
							out.Add(f.Name)
						case *ast.SelectorExpr:
							structVarName := f.X.(*ast.Ident).Name
							calledMethodName := f.Sel.Name
							out.Add(fmt.Sprintf("%s.%s", nameToTypeMap[structVarName], calledMethodName))
						}
					}
				}
			}
		}
	}
}

func buildPackagePath(path string) string {
	gopath := os.Getenv("GOPATH")
	pkgDir := strings.Join([]string{gopath, "src", path}, "/")
	return pkgDir
}

func analyze(_ *cobra.Command, _ []string) {
	pkgDir := buildPackagePath(analyzePackage)
	log.Println("analyzing ", pkgDir)

	_, err := os.Stat(pkgDir)
	if os.IsNotExist(err) {
		log.Fatalf("package dir doesn't exist: %s", pkgDir)
	}

	astPkg, err := parser.ParseDir(token.NewFileSet(), pkgDir, nil, parser.AllErrors)
	if err != nil {
		log.Fatal(err)
	}

	log.Println(len(astPkg))
	declaredFuncs := set.New()
	calledFuncs := set.New()

	for _, pkg := range astPkg {
		for name, f := range pkg.Files {
			log.Println("analyzing ", name)
			isTest := strings.HasSuffix(name, "_test.go")
			if isTest {
				getCalledNames(f, calledFuncs)
			} else {
				getDeclaredNames(f, declaredFuncs)
			}
		}
	}

	if true {
		declaredFuncNames := set.StringSlice(declaredFuncs)
		log.Printf(`declared functions:
			%s
		`, strings.Join(declaredFuncNames, "\n\t"))

		calledFuncNames := set.StringSlice(calledFuncs)
		log.Printf(`called functions:
			%s
		`, strings.Join(calledFuncNames, "\n\t"))
	}

	diff := set.StringSlice(set.Difference(declaredFuncs, calledFuncs))

	if failOnFinding && len(diff) > 0 {
		errorString := fmt.Sprintf(`The following functions are declared but not called in any tests:
%s
	`, strings.Join(diff, ",\n\t"))
		log.Fatal(errorString)
	}
}

// analyzeCmd represents the analyze command
var analyzeCmd = &cobra.Command{
	Use:   "analyze",
	Short: "Analyze a given package",
	Long:  "Analyze takes a given package's code and determines which functions lack direct test coverage.",
	PreRunE: func(cmd *cobra.Command, args []string) error {
		if analyzePackage == "" {
			return errors.New("the required flag `-p, --package` was not specified")
		}
		return nil
	},
	Run: analyze,
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"reflect"
	"strings"

	"github.com/codegangsta/cli"
	"github.com/jamesog/cloudflare"
)

var api *cloudflare.API

// Map type used for printing a table
type table map[string]string

// Print a nicely-formatted table
func makeTable(zones []table, cols ...string) {
	// Store the maximum length of all columns
	// The default is the length of the title
	lens := make(map[string]int)
	for _, col := range cols {
		lens[col] = len(col)
	}
	// Increase the size of the column if it is larger than the current value
	for _, z := range zones {
		for col, val := range z {
			if _, ok := lens[col]; ok && len(val) > lens[col] {
				lens[col] = len(val)
			}
		}
	}
	// Print the headings and an underline for each heading
	for _, col := range cols {
		fmt.Printf("%s%s ", strings.Title(col), strings.Repeat(" ", lens[col]-len(col)))
	}
	fmt.Println()
	for _, col := range cols {
		fmt.Printf("%s ", strings.Repeat("-", lens[col]))
	}
	fmt.Println()
	// And finally print the table data
	for _, z := range zones {
		for _, col := range cols {
			fmt.Printf("%s%s ", z[col], strings.Repeat(" ", lens[col]-len(z[col])))
		}
		fmt.Println()
	}

}

func userInfo(*cli.Context) {
	user, err := api.UserDetails()
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("%+v\n", user)
	var output []table
	output = append(output, table{
		"ID":       user.ID,
		"Email":    user.Email,
		"Username": user.Username,
		"Name":     user.FirstName + " " + user.LastName,
		"2FA":      fmt.Sprintf("%t", user.TwoFA),
	})
	makeTable(output, "ID", "Email", "Username", "Name", "2FA")
}

func userUpdate(*cli.Context) {
}

func zoneList(c *cli.Context) {
	zones, err := api.ListZones()
	if err != nil {
		fmt.Println(err)
		return
	}
	var output []table
	for _, z := range zones {
		output = append(output, table{
			"ID":     z.ID,
			"Name":   z.Name,
			"Plan":   z.Plan.LegacyID,
			"Status": z.Status,
		})
	}
	makeTable(output, "ID", "Name", "Plan", "Status")
}

func zoneInfo(c *cli.Context) {
	var zone string
	if len(c.Args()) > 0 {
		zone = c.Args()[0]
	} else {
		fmt.Println("No zone specified")
		return
	}
	zones, err := api.ListZones(zone)
	if err != nil {
		fmt.Println(err)
		return
	}
	var output []table
	for _, z := range zones {
		output = append(output, table{
			"ID":           z.ID,
			"Zone":         z.Name,
			"Plan":         z.Plan.LegacyID,
			"Status":       z.Status,
			"Name Servers": strings.Join(z.NameServers, ", "),
			"Paused":       fmt.Sprintf("%t", z.Paused),
			"Type":         z.Type,
		})
	}
	makeTable(output, "ID", "Zone", "Plan", "Status", "Name Servers", "Paused", "Type")
}

func zonePlan(*cli.Context) {
}

func zoneSettings(*cli.Context) {
}

func zoneRecords(c *cli.Context) {
	var zone string
	if len(c.Args()) > 0 {
		zone = c.Args()[0]
	} else if c.String("zone") != "" {
		zone = c.String("zone")
	} else {
		fmt.Println("No zone specified")
		return
	}
	records, err := api.DNSRecords(zone)
	if err != nil {
		fmt.Println(err)
		return
	}
	var output []table
	for _, r := range records {
		switch r.Type {
		case "MX":
			r.Content = fmt.Sprintf("%d %s", r.Priority, r.Content)
		case "SRV":
			dp := reflect.ValueOf(r.Data).Interface().(map[string]interface{})
			r.Content = fmt.Sprintf("%.f %s", dp["priority"], r.Content)
			// CloudFlare's API, annoyingly, automatically prepends the weight
			// and port into content, separated by tabs.
			// XXX: File this as a bug. LOC doesn't do this.
			r.Content = strings.Replace(r.Content, "\t", " ", -1)
		}
		output = append(output, table{
			"ID":      r.ID,
			"Type":    r.Type,
			"Name":    r.Name,
			"Content": r.Content,
			"Proxied": fmt.Sprintf("%t", r.Proxied),
			"TTL":     fmt.Sprintf("%d", r.TTL),
		})
	}
	makeTable(output, "ID", "Type", "Name", "Content", "Proxied", "TTL")
}

func dnsCreate(c *cli.Context) {
	zone := c.String("zone")
	if zone == "" {
		fmt.Println("zone not specified")
		return
	}
	name := c.String("name")
	if name == "" {
		fmt.Println("name not specified")
		return
	}
	rtype := c.String("type")
	if rtype == "" {
		fmt.Println("type not specified")
		return
	}
	content := c.String("content")
	if content == "" {
		fmt.Println("content not specified")
		return
	}

	record := cloudflare.DNSRecord{
		Name:    name,
		Type:    strings.ToUpper(rtype),
		Content: content,
		TTL:     300,
	}
	err := api.CreateDNSRecord(zone, record)
	if err != nil {
		fmt.Println("Error creating DNS record:", err)
	}
}

func dnsUpdate(*cli.Context) {
}

func dnsDelete(c *cli.Context) {
	zone := c.String("zone")
	if zone == "" {
		fmt.Println("zone not specified")
		return
	}
	name := c.String("id")
	if name == "" {
		fmt.Println("name not specified")
		return
	}
	err := api.DeleteDNSRecord(zone, name)
	if err != nil {
		fmt.Println("Error deleting DNS record:", err)
	}
}

func zoneCerts(*cli.Context) {
}

func zoneKeyless(*cli.Context) {
}

func zoneRailgun(*cli.Context) {
}

func railgun(*cli.Context) {
}

func main() {
	// zone := cloudflare.Zone{Name: "example.com"}
	// zone.CreateZone()

	// apiKey := os.Getenv("CF_API_KEY")
	// apiEmail := os.Getenv("CF_API_EMAIL")
	api = cloudflare.New(os.Getenv("CF_API_KEY"), os.Getenv("CF_API_EMAIL"))

	if api.APIKey == "" {
		fmt.Println("API key not defined")
		return
	}
	if api.APIEmail == "" {
		fmt.Println("API email not defined")
		return
	}

	// userCommand := cli.Command{
	// railgunCommand := cli.Command{
	app := cli.NewApp()
	app.Name = "cloudflare"
	app.Commands = []cli.Command{
		{
			Name:    "user",
			Aliases: []string{"u"},
			Usage:   "User information",
			Subcommands: []cli.Command{
				{
					Name:    "info",
					Aliases: []string{"i"},
					Action:  userInfo,
					Usage:   "User details",
				},
				{
					Name:    "update",
					Aliases: []string{"u"},
					Action:  userUpdate,
					Usage:   "Update user details",
				},
			},
		},

		{
			Name:    "zone",
			Aliases: []string{"z"},
			Usage:   "Zone information",
			Subcommands: []cli.Command{
				{
					Name:    "list",
					Aliases: []string{"l"},
					Action:  zoneList,
					Usage:   "List all zones on an account",
				},
				{
					Name:    "info",
					Aliases: []string{"i"},
					Action:  zoneInfo,
					Usage:   "Information on one zone",
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "zone",
							Usage: "zone name",
						},
					},
				},
				{
					Name:    "plan",
					Aliases: []string{"p"},
					Action:  zonePlan,
					Usage:   "Plan information for one zone",
				},
				{
					Name:    "settings",
					Aliases: []string{"s"},
					Action:  zoneSettings,
					Usage:   "Settings for one zone",
				},
				{
					Name:    "dns",
					Aliases: []string{"d"},
					Action:  zoneRecords,
					Usage:   "DNS records for a zone",
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "zone",
							Usage: "zone name",
						},
					},
				},
				{
					Name:    "railgun",
					Aliases: []string{"r"},
					Action:  zoneRailgun,
					Usage:   "Railguns for a zone",
				},
				{
					Name:    "certs",
					Aliases: []string{"c"},
					Action:  zoneCerts,
					Usage:   "Custom SSL certificates for a zone",
				},
				{
					Name:    "keyless",
					Aliases: []string{"k"},
					Action:  zoneKeyless,
					Usage:   "Keyless SSL for a zone",
				},
			},
		},

		{
			Name:    "dns",
			Aliases: []string{"d"},
			Usage:   "DNS records",
			Subcommands: []cli.Command{
				{
					Name:    "list",
					Aliases: []string{"l"},
					Action:  zoneRecords,
					Usage:   "List DNS records for a zone",
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "zone",
							Usage: "zone name",
						},
					},
				},
				{
					Name:    "create",
					Aliases: []string{"c"},
					Action:  dnsCreate,
					Usage:   "Create a DNS record",
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "zone",
							Usage: "zone name",
						},
						cli.StringFlag{
							Name:  "name",
							Usage: "record name",
						},
						cli.StringFlag{
							Name:  "type",
							Usage: "record type",
						},
						cli.StringFlag{
							Name:  "content",
							Usage: "record content",
						},
					},
				},
				{
					Name:    "update",
					Aliases: []string{"u"},
					Action:  dnsUpdate,
					Usage:   "Update a DNS record",
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "zone",
							Usage: "zone name",
						},
						cli.StringFlag{
							Name:  "name",
							Usage: "record name",
						},
						cli.StringFlag{
							Name:  "content",
							Usage: "record content",
						},
					},
				},
				{
					Name:    "delete",
					Aliases: []string{"d"},
					Action:  dnsDelete,
					Usage:   "Delete a DNS record",
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "zone",
							Usage: "zone name",
						},
						cli.StringFlag{
							Name:  "id",
							Usage: "record id",
						},
					},
				},
			},
		},

		{
			Name:    "railgun",
			Aliases: []string{"r"},
			Usage:   "Railgun information",
			Action:  railgun,
		},
	}
	app.Run(os.Args)
}
<commit_msg>goflare: Add ttl and proxy flags for record creation<commit_after>package main

import (
	"fmt"
	"os"
	"reflect"
	"strings"

	"github.com/codegangsta/cli"
	"github.com/jamesog/cloudflare"
)

var api *cloudflare.API

// Map type used for printing a table
type table map[string]string

// Print a nicely-formatted table
func makeTable(zones []table, cols ...string) {
	// Store the maximum length of all columns
	// The default is the length of the title
	lens := make(map[string]int)
	for _, col := range cols {
		lens[col] = len(col)
	}
	// Increase the size of the column if it is larger than the current value
	for _, z := range zones {
		for col, val := range z {
			if _, ok := lens[col]; ok && len(val) > lens[col] {
				lens[col] = len(val)
			}
		}
	}
	// Print the headings and an underline for each heading
	for _, col := range cols {
		fmt.Printf("%s%s ", strings.Title(col), strings.Repeat(" ", lens[col]-len(col)))
	}
	fmt.Println()
	for _, col := range cols {
		fmt.Printf("%s ", strings.Repeat("-", lens[col]))
	}
	fmt.Println()
	// And finally print the table data
	for _, z := range zones {
		for _, col := range cols {
			fmt.Printf("%s%s ", z[col], strings.Repeat(" ", lens[col]-len(z[col])))
		}
		fmt.Println()
	}

}

func userInfo(*cli.Context) {
	user, err := api.UserDetails()
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("%+v\n", user)
	var output []table
	output = append(output, table{
		"ID":       user.ID,
		"Email":    user.Email,
		"Username": user.Username,
		"Name":     user.FirstName + " " + user.LastName,
		"2FA":      fmt.Sprintf("%t", user.TwoFA),
	})
	makeTable(output, "ID", "Email", "Username", "Name", "2FA")
}

func userUpdate(*cli.Context) {
}

func zoneList(c *cli.Context) {
	zones, err := api.ListZones()
	if err != nil {
		fmt.Println(err)
		return
	}
	var output []table
	for _, z := range zones {
		output = append(output, table{
			"ID":     z.ID,
			"Name":   z.Name,
			"Plan":   z.Plan.LegacyID,
			"Status": z.Status,
		})
	}
	makeTable(output, "ID", "Name", "Plan", "Status")
}

func zoneInfo(c *cli.Context) {
	var zone string
	if len(c.Args()) > 0 {
		zone = c.Args()[0]
	} else {
		fmt.Println("No zone specified")
		return
	}
	zones, err := api.ListZones(zone)
	if err != nil {
		fmt.Println(err)
		return
	}
	var output []table
	for _, z := range zones {
		output = append(output, table{
			"ID":           z.ID,
			"Zone":         z.Name,
			"Plan":         z.Plan.LegacyID,
			"Status":       z.Status,
			"Name Servers": strings.Join(z.NameServers, ", "),
			"Paused":       fmt.Sprintf("%t", z.Paused),
			"Type":         z.Type,
		})
	}
	makeTable(output, "ID", "Zone", "Plan", "Status", "Name Servers", "Paused", "Type")
}

func zonePlan(*cli.Context) {
}

func zoneSettings(*cli.Context) {
}

func zoneRecords(c *cli.Context) {
	var zone string
	if len(c.Args()) > 0 {
		zone = c.Args()[0]
	} else if c.String("zone") != "" {
		zone = c.String("zone")
	} else {
		fmt.Println("No zone specified")
		return
	}
	records, err := api.DNSRecords(zone)
	if err != nil {
		fmt.Println(err)
		return
	}
	var output []table
	for _, r := range records {
		switch r.Type {
		case "MX":
			r.Content = fmt.Sprintf("%d %s", r.Priority, r.Content)
		case "SRV":
			dp := reflect.ValueOf(r.Data).Interface().(map[string]interface{})
			r.Content = fmt.Sprintf("%.f %s", dp["priority"], r.Content)
			// CloudFlare's API, annoyingly, automatically prepends the weight
			// and port into content, separated by tabs.
			// XXX: File this as a bug. LOC doesn't do this.
			r.Content = strings.Replace(r.Content, "\t", " ", -1)
		}
		output = append(output, table{
			"ID":      r.ID,
			"Type":    r.Type,
			"Name":    r.Name,
			"Content": r.Content,
			"Proxied": fmt.Sprintf("%t", r.Proxied),
			"TTL":     fmt.Sprintf("%d", r.TTL),
		})
	}
	makeTable(output, "ID", "Type", "Name", "Content", "Proxied", "TTL")
}

func dnsCreate(c *cli.Context) {
	zone := c.String("zone")
	if zone == "" {
		fmt.Println("zone not specified")
		return
	}
	name := c.String("name")
	if name == "" {
		fmt.Println("name not specified")
		return
	}
	rtype := c.String("type")
	if rtype == "" {
		fmt.Println("type not specified")
		return
	}
	content := c.String("content")
	if content == "" {
		fmt.Println("content not specified")
		return
	}
	ttl := c.Int("ttl")
	proxy := c.Bool("proxy")

	record := cloudflare.DNSRecord{
		Name:    name,
		Type:    strings.ToUpper(rtype),
		Content: content,
		TTL:     ttl,
		Proxied: proxy,
	}
	err := api.CreateDNSRecord(zone, record)
	if err != nil {
		fmt.Println("Error creating DNS record:", err)
	}
}

func dnsUpdate(*cli.Context) {
}

func dnsDelete(c *cli.Context) {
	zone := c.String("zone")
	if zone == "" {
		fmt.Println("zone not specified")
		return
	}
	name := c.String("id")
	if name == "" {
		fmt.Println("name not specified")
		return
	}
	err := api.DeleteDNSRecord(zone, name)
	if err != nil {
		fmt.Println("Error deleting DNS record:", err)
	}
}

func zoneCerts(*cli.Context) {
}

func zoneKeyless(*cli.Context) {
}

func zoneRailgun(*cli.Context) {
}

func railgun(*cli.Context) {
}

func main() {
	// zone := cloudflare.Zone{Name: "example.com"}
	// zone.CreateZone()

	// apiKey := os.Getenv("CF_API_KEY")
	// apiEmail := os.Getenv("CF_API_EMAIL")
	api = cloudflare.New(os.Getenv("CF_API_KEY"), os.Getenv("CF_API_EMAIL"))

	if api.APIKey == "" {
		fmt.Println("API key not defined")
		return
	}
	if api.APIEmail == "" {
		fmt.Println("API email not defined")
		return
	}

	// userCommand := cli.Command{
	// railgunCommand := cli.Command{
	app := cli.NewApp()
	app.Name = "cloudflare"
	app.Commands = []cli.Command{
		{
			Name:    "user",
			Aliases: []string{"u"},
			Usage:   "User information",
			Subcommands: []cli.Command{
				{
					Name:    "info",
					Aliases: []string{"i"},
					Action:  userInfo,
					Usage:   "User details",
				},
				{
					Name:    "update",
					Aliases: []string{"u"},
					Action:  userUpdate,
					Usage:   "Update user details",
				},
			},
		},

		{
			Name:    "zone",
			Aliases: []string{"z"},
			Usage:   "Zone information",
			Subcommands: []cli.Command{
				{
					Name:    "list",
					Aliases: []string{"l"},
					Action:  zoneList,
					Usage:   "List all zones on an account",
				},
				{
					Name:    "info",
					Aliases: []string{"i"},
					Action:  zoneInfo,
					Usage:   "Information on one zone",
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "zone",
							Usage: "zone name",
						},
					},
				},
				{
					Name:    "plan",
					Aliases: []string{"p"},
					Action:  zonePlan,
					Usage:   "Plan information for one zone",
				},
				{
					Name:    "settings",
					Aliases: []string{"s"},
					Action:  zoneSettings,
					Usage:   "Settings for one zone",
				},
				{
					Name:    "dns",
					Aliases: []string{"d"},
					Action:  zoneRecords,
					Usage:   "DNS records for a zone",
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "zone",
							Usage: "zone name",
						},
					},
				},
				{
					Name:    "railgun",
					Aliases: []string{"r"},
					Action:  zoneRailgun,
					Usage:   "Railguns for a zone",
				},
				{
					Name:    "certs",
					Aliases: []string{"c"},
					Action:  zoneCerts,
					Usage:   "Custom SSL certificates for a zone",
				},
				{
					Name:    "keyless",
					Aliases: []string{"k"},
					Action:  zoneKeyless,
					Usage:   "Keyless SSL for a zone",
				},
			},
		},

		{
			Name:    "dns",
			Aliases: []string{"d"},
			Usage:   "DNS records",
			Subcommands: []cli.Command{
				{
					Name:    "list",
					Aliases: []string{"l"},
					Action:  zoneRecords,
					Usage:   "List DNS records for a zone",
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "zone",
							Usage: "zone name",
						},
					},
				},
				{
					Name:    "create",
					Aliases: []string{"c"},
					Action:  dnsCreate,
					Usage:   "Create a DNS record",
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "zone",
							Usage: "zone name",
						},
						cli.StringFlag{
							Name:  "name",
							Usage: "record name",
						},
						cli.StringFlag{
							Name:  "type",
							Usage: "record type",
						},
						cli.StringFlag{
							Name:  "content",
							Usage: "record content",
						},
						cli.IntFlag{
							Name:  "ttl",
							Usage: "TTL (1 = automatic)",
							Value: 1,
						},
						cli.BoolFlag{
							Name:  "proxy",
							Usage: "proxy through CloudFlare (orange cloud)",
						},
					},
				},
				{
					Name:    "update",
					Aliases: []string{"u"},
					Action:  dnsUpdate,
					Usage:   "Update a DNS record",
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "zone",
							Usage: "zone name",
						},
						cli.StringFlag{
							Name:  "name",
							Usage: "record name",
						},
						cli.StringFlag{
							Name:  "content",
							Usage: "record content",
						},
					},
				},
				{
					Name:    "delete",
					Aliases: []string{"d"},
					Action:  dnsDelete,
					Usage:   "Delete a DNS record",
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "zone",
							Usage: "zone name",
						},
						cli.StringFlag{
							Name:  "id",
							Usage: "record id",
						},
					},
				},
			},
		},

		{
			Name:    "railgun",
			Aliases: []string{"r"},
			Usage:   "Railgun information",
			Action:  railgun,
		},
	}
	app.Run(os.Args)
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"log"
	"net/url"
	"os"

	"github.com/lestrrat/go-jwx/jwk"
	"github.com/lestrrat/go-jwx/jws"
	"github.com/pkg/errors"
)

func main() {
	os.Exit(_main())
}

type JWKConfig struct {
	JWKLocation string
	Payload     string
}

type JWEConfig struct {
	Algorithm string
}

func _main() int {
	var f func() int

	if len(os.Args) < 2 {
		f = doHelp
	} else {
		switch os.Args[1] {
		case "jwk":
			f = doJWK
		case "jwe":
			f = doJWE
		default:
			f = doHelp
		}

		os.Args = os.Args[1:]
	}
	return f()
}

func doHelp() int {
	fmt.Println(`jwx [command] [args]`)
	return 0
}

func doJWE() int {
	c := JWEConfig{}
	flag.StringVar(&c.Algorithm, "alg", "", "Key encryption algorithm")
	flag.Parse()

	return 0
}

func doJWK() int {
	c := JWKConfig{}
	flag.StringVar(&c.JWKLocation, "jwk", "", "JWK location, either a local file or a URL")
	flag.Parse()

	var key *jwk.Set
	if c.JWKLocation == "" {
		fmt.Printf("-jwk must be specified\n")
		return 1
	}

	if u, err := url.Parse(c.JWKLocation); err == nil && (u.Scheme == "http" || u.Scheme == "https") {
		var err error
		key, err = jwk.FetchHTTP(c.JWKLocation)
		if err != nil {
			log.Printf("%s", err)
			return 0
		}
	} else {
		var err error
		key, err = jwk.FetchFile(c.JWKLocation)
		if err != nil {
			log.Printf("%s", err)
			return 0
		}
	}

	keybuf, err := json.MarshalIndent(key, "", "  ")
	if err != nil {
		log.Printf("%s", err)
		return 0
	}
	log.Printf("=== JWK ===")
	for _, l := range bytes.Split(keybuf, []byte{'\n'}) {
		log.Printf("%s", l)
	}

	// TODO make it flexible
	pubkey, err := (key.Keys[0]).(*jwk.RsaPublicKey).PublicKey()
	if err != nil {
		log.Printf("%s", err)
		return 0
	}

	var payload io.Reader
	if c.Payload == "" {
		payload = os.Stdin
	} else {
		f, err := os.Open(c.Payload)
		if err != nil {
			log.Printf("%s", errors.Wrap(err, "failed to open file "+c.Payload))
			return 1
		}
		payload = f
		defer f.Close()
	}

	message, err := jws.Parse(payload)
	if err != nil {
		log.Printf("%s", err)
		return 0
	}

	log.Printf("=== Payload ===")
	// See if this is JSON. if it is, display it nicely
	m := map[string]interface{}{}
	if err := json.Unmarshal(message.Payload, &m); err == nil {
		payloadbuf, err := json.MarshalIndent(m, "", "  ")
		if err != nil {
			log.Printf("%s", errors.Wrap(err, "failed to marshal payload"))
			return 0
		}
		for _, l := range bytes.Split(payloadbuf, []byte{'\n'}) {
			log.Printf("%s", l)
		}
	} else {
		log.Printf("%s", message.Payload)
	}

	for i, sig := range message.Signatures {
		log.Printf("=== Signature %d ===", i)
		sigbuf, err := json.MarshalIndent(sig, "", "  ")
		if err != nil {
			log.Printf("%s", errors.Wrap(err, "failed to marshal signature as JSON"))
			return 0
		}
		for _, l := range bytes.Split(sigbuf, []byte{'\n'}) {
			log.Printf("%s", l)
		}

		v, err := jws.NewRsaVerify(sig.ProtectedHeader.Algorithm, pubkey)
		if err != nil {
			log.Printf("%s", err)
			continue
		}
		if err := v.Verify(message); err == nil {
			log.Printf("=== Verified with signature %d! ===", i)
		}
	}

	return 1
}
<commit_msg>fix JWS usage in command<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"log"
	"net/url"
	"os"

	"github.com/lestrrat/go-jwx/jwk"
	"github.com/lestrrat/go-jwx/jws"
	"github.com/pkg/errors"
)

func main() {
	os.Exit(_main())
}

type JWKConfig struct {
	JWKLocation string
	Payload     string
}

type JWEConfig struct {
	Algorithm string
}

func _main() int {
	var f func() int

	if len(os.Args) < 2 {
		f = doHelp
	} else {
		switch os.Args[1] {
		case "jwk":
			f = doJWK
		case "jwe":
			f = doJWE
		default:
			f = doHelp
		}

		os.Args = os.Args[1:]
	}
	return f()
}

func doHelp() int {
	fmt.Println(`jwx [command] [args]`)
	return 0
}

func doJWE() int {
	c := JWEConfig{}
	flag.StringVar(&c.Algorithm, "alg", "", "Key encryption algorithm")
	flag.Parse()

	return 0
}

func doJWK() int {
	c := JWKConfig{}
	flag.StringVar(&c.JWKLocation, "jwk", "", "JWK location, either a local file or a URL")
	flag.Parse()

	var key *jwk.Set
	if c.JWKLocation == "" {
		fmt.Printf("-jwk must be specified\n")
		return 1
	}

	if u, err := url.Parse(c.JWKLocation); err == nil && (u.Scheme == "http" || u.Scheme == "https") {
		var err error
		key, err = jwk.FetchHTTP(c.JWKLocation)
		if err != nil {
			log.Printf("%s", err)
			return 0
		}
	} else {
		var err error
		key, err = jwk.FetchFile(c.JWKLocation)
		if err != nil {
			log.Printf("%s", err)
			return 0
		}
	}

	keybuf, err := json.MarshalIndent(key, "", "  ")
	if err != nil {
		log.Printf("%s", err)
		return 0
	}
	log.Printf("=== JWK ===")
	for _, l := range bytes.Split(keybuf, []byte{'\n'}) {
		log.Printf("%s", l)
	}

	// TODO make it flexible
	pubkey, err := (key.Keys[0]).(*jwk.RsaPublicKey).PublicKey()
	if err != nil {
		log.Printf("%s", err)
		return 0
	}

	var src io.Reader
	if c.Payload == "" {
		src = os.Stdin
	} else {
		f, err := os.Open(c.Payload)
		if err != nil {
			log.Printf("%s", errors.Wrap(err, "failed to open file "+c.Payload))
			return 1
		}
		src = f
		defer f.Close()
	}

	var buf bytes.Buffer
	src = io.TeeReader(src, &buf)

	message, err := jws.Parse(src)
	if err != nil {
		log.Printf("%s", err)
		return 0
	}

	log.Printf("=== Payload ===")
	// See if this is JSON. if it is, display it nicely
	m := map[string]interface{}{}
	if err := json.Unmarshal(message.Payload(), &m); err == nil {
		payloadbuf, err := json.MarshalIndent(m, "", "  ")
		if err != nil {
			log.Printf("%s", errors.Wrap(err, "failed to marshal payload"))
			return 0
		}
		for _, l := range bytes.Split(payloadbuf, []byte{'\n'}) {
			log.Printf("%s", l)
		}
	} else {
		log.Printf("%s", message.Payload())
	}

	for i, sig := range message.Signatures() {
		log.Printf("=== Signature %d ===", i)
		sigbuf, err := json.MarshalIndent(sig, "", "  ")
		if err != nil {
			log.Printf("%s", errors.Wrap(err, "failed to marshal signature as JSON"))
			return 0
		}
		for _, l := range bytes.Split(sigbuf, []byte{'\n'}) {
			log.Printf("%s", l)
		}

		if _, err := jws.Verify(buf.Bytes(), sig.ProtectedHeaders().Algorithm(), pubkey); err == nil {
			log.Printf("=== Verified with signature %d! ===", i)
		}
	}

	return 1
}
<|endoftext|>


<commit_before>package main

import (
	"database/sql"
	"errors"
	"fmt"
	_ "github.com/bmizerany/pq"
	"log"
	"os"
	"path"
	"sort"
	"strconv"
	"strings"
	"time"
)

type DBVersion struct {
	VersionId int
	TStamp    time.Time
}

type Migration struct {
	Next     int    // next version, or -1 if none
	Previous int    // previous version, -1 if none
	Source   string // .go or .sql script
}

type MigrationMap struct {
	Versions   []int             // sorted slice of version keys
	Migrations map[int]Migration // sources (.sql or .go) keyed by version
	Direction  bool              // sort direction: true -> Up, false -> Down
}

func runMigrations(conf *DBConf, migrationsDir string, target int) {

	db, err := sql.Open(conf.Driver, conf.OpenStr)
	if err != nil {
		log.Fatal("couldn't open DB:", err)
	}
	defer db.Close()

	current, e := ensureDBVersion(db)
	if e != nil {
		log.Fatal("couldn't get/set DB version")
	}

	mm, err := collectMigrations(migrationsDir, current, target)
	if err != nil {
		log.Fatal(err)
	}

	if len(mm.Versions) == 0 {
		fmt.Printf("goose: no migrations to run. current version: %d\n", current)
		return
	}

	fmt.Printf("goose: migrating db configuration '%v', current version: %d, target: %d\n",
		conf.Name, mm.Versions[0], mm.Versions[len(mm.Versions)-1])

	for _, v := range mm.Versions {

		var numStatements int
		var e error

		filepath := mm.Migrations[v].Source

		switch path.Ext(filepath) {
		case ".go":
			numStatements, e = runGoMigration(conf, filepath, v, mm.Direction)
		case ".sql":
			numStatements, e = runSQLMigration(db, filepath, v, mm.Direction)
		}

		if e != nil {
			log.Fatalf("FAIL %v, quitting migration", e)
		}

		fmt.Printf("OK   %s (%d statements)\n", path.Base(filepath), numStatements)
	}
}

// collect all the valid looking migration scripts in the
// migrations folder, and key them by version
func collectMigrations(dirpath string, current, target int) (mm *MigrationMap, err error) {

	dir, err := os.Open(dirpath)
	if err != nil {
		log.Fatal(err)
	}

	names, err := dir.Readdirnames(0)
	if err != nil {
		log.Fatal(err)
	}

	mm = &MigrationMap{
		Migrations: make(map[int]Migration),
	}

	// if target is the default -1,
	// we need to find the most recent possible version to target
	if target < 0 {
		target = mostRecentVersionAvailable(names)
	}

	// extract the numeric component of each migration,
	// filter out any uninteresting files,
	// and ensure we only have one file per migration version.
	for _, name := range names {

		if ext := path.Ext(name); ext != ".go" && ext != ".sql" {
			continue
		}

		v, e := numericComponent(name)
		if e != nil {
			continue
		}

		if _, ok := mm.Migrations[v]; ok {
			log.Fatalf("more than one file specifies the migration for version %d (%s and %s)",
				v, mm.Versions[v], path.Join(dirpath, name))
		}

		if versionFilter(v, current, target) {
			mm.Append(v, path.Join(dirpath, name))
		}
	}

	if len(mm.Versions) > 0 {
		mm.Sort(current < target)
	}

	return mm, nil
}

// helper to identify the most recent possible version
// within a folder of migration scripts
func mostRecentVersionAvailable(names []string) int {

	mostRecent := -1

	for _, name := range names {

		if ext := path.Ext(name); ext != ".go" && ext != ".sql" {
			continue
		}

		v, e := numericComponent(name)
		if e != nil {
			continue
		}

		if v > mostRecent {
			mostRecent = v
		}
	}

	return mostRecent
}

func versionFilter(v, current, target int) bool {

	// special case - default target value
	if target < 0 {
		return v > current
	}

	if target > current {
		return v > current && v <= target
	}

	if target < current {
		return v <= current && v >= target
	}

	return false
}

func (m *MigrationMap) Append(v int, source string) {
	m.Versions = append(m.Versions, v)
	m.Migrations[v] = Migration{
		Next:     -1,
		Previous: -1,
		Source:   source,
	}
}

func (m *MigrationMap) Sort(direction bool) {
	sort.Ints(m.Versions)

	// set direction, and reverse order if need be
	m.Direction = direction
	if m.Direction == false {
		for i, j := 0, len(m.Versions)-1; i < j; i, j = i+1, j-1 {
			m.Versions[i], m.Versions[j] = m.Versions[j], m.Versions[i]
		}
	}

	// now that we're sorted in the appropriate direction,
	// populate next and previous for each migration
	//
	// work around http://code.google.com/p/go/issues/detail?id=3117
	previousV := -1
	for _, v := range m.Versions {
		cur := m.Migrations[v]
		cur.Previous = previousV

		// if a migration exists at prev, its next is now v
		if prev, ok := m.Migrations[previousV]; ok {
			prev.Next = v
			m.Migrations[previousV] = prev
		}

		previousV = v
	}
}

// look for migration scripts with names in the form:
//  XXX_descriptivename.ext
// where XXX specifies the version number
// and ext specifies the type of migration
func numericComponent(path string) (int, error) {
	idx := strings.Index(path, "_")
	if idx < 0 {
		return 0, errors.New("no separator found")
	}
	return strconv.Atoi(path[:idx])
}

// retrieve the current version for this DB.
// Create and initialize the DB version table if it doesn't exist.
func ensureDBVersion(db *sql.DB) (int, error) {

	dbversion := int(0)
	row := db.QueryRow("SELECT version_id from goose_db_version ORDER BY tstamp DESC LIMIT 1;")

	if err := row.Scan(&dbversion); err == nil {
		return dbversion, nil
	}

	// if we failed, assume that the table didn't exist, and try to create it
	txn, err := db.Begin()
	if err != nil {
		return 0, err
	}

	// create the table and insert an initial value of 0
	create := `CREATE TABLE goose_db_version (
                version_id int NOT NULL,
                tstamp timestamp NULL default now(),
                PRIMARY KEY(tstamp)
              );`
	insert := "INSERT INTO goose_db_version (version_id) VALUES (0);"

	for _, str := range []string{create, insert} {
		if _, err := txn.Exec(str); err != nil {
			txn.Rollback()
			return 0, err
		}
	}

	return 0, txn.Commit()
}
<commit_msg>previous test caught an error<commit_after>package main

import (
	"database/sql"
	"errors"
	"fmt"
	_ "github.com/bmizerany/pq"
	"log"
	"os"
	"path"
	"sort"
	"strconv"
	"strings"
	"time"
)

type DBVersion struct {
	VersionId int
	TStamp    time.Time
}

type Migration struct {
	Next     int    // next version, or -1 if none
	Previous int    // previous version, -1 if none
	Source   string // .go or .sql script
}

type MigrationMap struct {
	Versions   []int             // sorted slice of version keys
	Migrations map[int]Migration // sources (.sql or .go) keyed by version
	Direction  bool              // sort direction: true -> Up, false -> Down
}

func runMigrations(conf *DBConf, migrationsDir string, target int) {

	db, err := sql.Open(conf.Driver, conf.OpenStr)
	if err != nil {
		log.Fatal("couldn't open DB:", err)
	}
	defer db.Close()

	current, e := ensureDBVersion(db)
	if e != nil {
		log.Fatal("couldn't get/set DB version")
	}

	mm, err := collectMigrations(migrationsDir, current, target)
	if err != nil {
		log.Fatal(err)
	}

	if len(mm.Versions) == 0 {
		fmt.Printf("goose: no migrations to run. current version: %d\n", current)
		return
	}

	fmt.Printf("goose: migrating db configuration '%v', current version: %d, target: %d\n",
		conf.Name, mm.Versions[0], mm.Versions[len(mm.Versions)-1])

	for _, v := range mm.Versions {

		var numStatements int
		var e error

		filepath := mm.Migrations[v].Source

		switch path.Ext(filepath) {
		case ".go":
			numStatements, e = runGoMigration(conf, filepath, v, mm.Direction)
		case ".sql":
			numStatements, e = runSQLMigration(db, filepath, v, mm.Direction)
		}

		if e != nil {
			log.Fatalf("FAIL %v, quitting migration", e)
		}

		fmt.Printf("OK   %s (%d statements)\n", path.Base(filepath), numStatements)
	}
}

// collect all the valid looking migration scripts in the
// migrations folder, and key them by version
func collectMigrations(dirpath string, current, target int) (mm *MigrationMap, err error) {

	dir, err := os.Open(dirpath)
	if err != nil {
		log.Fatal(err)
	}

	names, err := dir.Readdirnames(0)
	if err != nil {
		log.Fatal(err)
	}

	mm = &MigrationMap{
		Migrations: make(map[int]Migration),
	}

	// if target is the default -1,
	// we need to find the most recent possible version to target
	if target < 0 {
		target = mostRecentVersionAvailable(names)
	}

	// extract the numeric component of each migration,
	// filter out any uninteresting files,
	// and ensure we only have one file per migration version.
	for _, name := range names {

		if ext := path.Ext(name); ext != ".go" && ext != ".sql" {
			continue
		}

		v, e := numericComponent(name)
		if e != nil {
			continue
		}

		if _, ok := mm.Migrations[v]; ok {
			log.Fatalf("more than one file specifies the migration for version %d (%s and %s)",
				v, mm.Versions[v], path.Join(dirpath, name))
		}

		if versionFilter(v, current, target) {
			mm.Append(v, path.Join(dirpath, name))
		}
	}

	if len(mm.Versions) > 0 {
		mm.Sort(current < target)
	}

	return mm, nil
}

// helper to identify the most recent possible version
// within a folder of migration scripts
func mostRecentVersionAvailable(names []string) int {

	mostRecent := -1

	for _, name := range names {

		if ext := path.Ext(name); ext != ".go" && ext != ".sql" {
			continue
		}

		v, e := numericComponent(name)
		if e != nil {
			continue
		}

		if v > mostRecent {
			mostRecent = v
		}
	}

	return mostRecent
}

func versionFilter(v, current, target int) bool {

	// special case - default target value
	if target < 0 {
		return v > current
	}

	if target > current {
		return v > current && v <= target
	}

	if target < current {
		return v <= current && v >= target
	}

	return false
}

func (m *MigrationMap) Append(v int, source string) {
	m.Versions = append(m.Versions, v)
	m.Migrations[v] = Migration{
		Next:     -1,
		Previous: -1,
		Source:   source,
	}
}

func (m *MigrationMap) Sort(direction bool) {
	sort.Ints(m.Versions)

	// set direction, and reverse order if need be
	m.Direction = direction
	if m.Direction == false {
		for i, j := 0, len(m.Versions)-1; i < j; i, j = i+1, j-1 {
			m.Versions[i], m.Versions[j] = m.Versions[j], m.Versions[i]
		}
	}

	// now that we're sorted in the appropriate direction,
	// populate next and previous for each migration
	//
	// work around http://code.google.com/p/go/issues/detail?id=3117
	previousV := -1
	for _, v := range m.Versions {
		cur := m.Migrations[v]
		cur.Previous = previousV
		m.Migrations[v] = cur

		// if a migration exists at prev, its next is now v
		if prev, ok := m.Migrations[previousV]; ok {
			prev.Next = v
			m.Migrations[previousV] = prev
		}

		previousV = v
	}
}

// look for migration scripts with names in the form:
//  XXX_descriptivename.ext
// where XXX specifies the version number
// and ext specifies the type of migration
func numericComponent(path string) (int, error) {
	idx := strings.Index(path, "_")
	if idx < 0 {
		return 0, errors.New("no separator found")
	}
	return strconv.Atoi(path[:idx])
}

// retrieve the current version for this DB.
// Create and initialize the DB version table if it doesn't exist.
func ensureDBVersion(db *sql.DB) (int, error) {

	dbversion := int(0)
	row := db.QueryRow("SELECT version_id from goose_db_version ORDER BY tstamp DESC LIMIT 1;")

	if err := row.Scan(&dbversion); err == nil {
		return dbversion, nil
	}

	// if we failed, assume that the table didn't exist, and try to create it
	txn, err := db.Begin()
	if err != nil {
		return 0, err
	}

	// create the table and insert an initial value of 0
	create := `CREATE TABLE goose_db_version (
                version_id int NOT NULL,
                tstamp timestamp NULL default now(),
                PRIMARY KEY(tstamp)
              );`
	insert := "INSERT INTO goose_db_version (version_id) VALUES (0);"

	for _, str := range []string{create, insert} {
		if _, err := txn.Exec(str); err != nil {
			txn.Rollback()
			return 0, err
		}
	}

	return 0, txn.Commit()
}
<|endoftext|>


<commit_before>package cmd

import (
	"fmt"
	"log"

	"github.com/rsteube/carapace"
	"github.com/spf13/cobra"
	gitlab "github.com/xanzy/go-gitlab"
	"github.com/zaquestion/lab/internal/action"
	"github.com/zaquestion/lab/internal/config"
	lab "github.com/zaquestion/lab/internal/gitlab"
)

var (
	mrLabels       []string
	mrState        string
	mrTargetBranch string
	mrNumRet       int
	mrAll          bool
	mrMine         bool
	mrDraft        bool
	mrReady        bool
	assigneeID     *int
	mrAssignee     string
	order          string
	sortedBy       string
)

// listCmd represents the list command
var listCmd = &cobra.Command{
	Use:              "list [remote]",
	Aliases:          []string{"ls"},
	Short:            "List merge requests",
	Long:             ``,
	Args:             cobra.MaximumNArgs(1),
	PersistentPreRun: LabPersistentPreRun,
	Run: func(cmd *cobra.Command, args []string) {
		mrs, err := mrList(args)
		if err != nil {
			log.Print(err)
			config.UserConfigError()
		}
		for _, mr := range mrs {
			fmt.Printf("!%d %s\n", mr.IID, mr.Title)
		}
	},
}

func mrList(args []string) ([]*gitlab.MergeRequest, error) {
	rn, _, err := parseArgsRemoteAndID(args)
	if err != nil {
		return nil, err
	}

	num := mrNumRet
	if mrAll {
		num = -1
	}

	if mrAssignee != "" {
		_assigneeID, err := lab.UserIDByUserName(mrAssignee)
		if err != nil {
			log.Fatal(err)
		}
		assigneeID = &_assigneeID
	} else if mrMine {
		_assigneeID, err := lab.UserID()
		if err != nil {
			log.Fatal(err)
		}
		assigneeID = &_assigneeID
	}

	orderBy := gitlab.String(order)

	sort := gitlab.String(sortedBy)

	opts := gitlab.ListProjectMergeRequestsOptions{
		ListOptions: gitlab.ListOptions{
			PerPage: mrNumRet,
		},
		Labels:       mrLabels,
		State:        &mrState,
		TargetBranch: &mrTargetBranch,
		OrderBy:      orderBy,
		Sort:         sort,
		AssigneeID:   assigneeID,
	}

	if mrDraft && !mrReady {
		opts.WIP = gitlab.String("yes")
	} else if mrReady && !mrDraft {
		opts.WIP = gitlab.String("no")
	}

	return lab.MRList(rn, opts, num)
}

func init() {
	listCmd.Flags().StringSliceVarP(
		&mrLabels, "label", "l", []string{}, "filter merge requests by label")
	listCmd.Flags().StringVarP(
		&mrState, "state", "s", "opened",
		"filter merge requests by state (opened/closed/merged)")
	listCmd.Flags().IntVarP(
		&mrNumRet, "number", "n", 10,
		"number of merge requests to return")
	listCmd.Flags().StringVarP(
		&mrTargetBranch, "target-branch", "t", "",
		"filter merge requests by target branch")
	listCmd.Flags().BoolVarP(&mrAll, "all", "a", false, "list all MRs on the project")
	listCmd.Flags().BoolVarP(&mrMine, "mine", "m", false, "list only MRs assigned to me")
	listCmd.Flags().StringVar(
		&mrAssignee, "assignee", "", "list only MRs assigned to $username")
	listCmd.Flags().StringVar(&order, "order", "updated_at", "display order, updated_at(default) or created_at")
	listCmd.Flags().StringVar(&sortedBy, "sort", "desc", "sort order, desc(default) or asc")
	listCmd.Flags().BoolVarP(&mrDraft, "draft", "", false, "list MRs marked as draft")
	listCmd.Flags().BoolVarP(&mrReady, "ready", "", false, "list MRs not marked as draft")
	listCmd.Flags().SortFlags = false

	mrCmd.AddCommand(listCmd)
	carapace.Gen(listCmd).FlagCompletion(carapace.ActionMap{
		"state": carapace.ActionValues("opened", "closed", "merged"),
	})

	carapace.Gen(listCmd).PositionalCompletion(
		action.Remotes(),
	)
}
<commit_msg>mr_list: fix order and sort flags description<commit_after>package cmd

import (
	"fmt"
	"log"

	"github.com/rsteube/carapace"
	"github.com/spf13/cobra"
	gitlab "github.com/xanzy/go-gitlab"
	"github.com/zaquestion/lab/internal/action"
	"github.com/zaquestion/lab/internal/config"
	lab "github.com/zaquestion/lab/internal/gitlab"
)

var (
	mrLabels       []string
	mrState        string
	mrTargetBranch string
	mrNumRet       int
	mrAll          bool
	mrMine         bool
	mrDraft        bool
	mrReady        bool
	assigneeID     *int
	mrAssignee     string
	order          string
	sortedBy       string
)

// listCmd represents the list command
var listCmd = &cobra.Command{
	Use:              "list [remote]",
	Aliases:          []string{"ls"},
	Short:            "List merge requests",
	Long:             ``,
	Args:             cobra.MaximumNArgs(1),
	PersistentPreRun: LabPersistentPreRun,
	Run: func(cmd *cobra.Command, args []string) {
		mrs, err := mrList(args)
		if err != nil {
			log.Print(err)
			config.UserConfigError()
		}
		for _, mr := range mrs {
			fmt.Printf("!%d %s\n", mr.IID, mr.Title)
		}
	},
}

func mrList(args []string) ([]*gitlab.MergeRequest, error) {
	rn, _, err := parseArgsRemoteAndID(args)
	if err != nil {
		return nil, err
	}

	num := mrNumRet
	if mrAll {
		num = -1
	}

	if mrAssignee != "" {
		_assigneeID, err := lab.UserIDByUserName(mrAssignee)
		if err != nil {
			log.Fatal(err)
		}
		assigneeID = &_assigneeID
	} else if mrMine {
		_assigneeID, err := lab.UserID()
		if err != nil {
			log.Fatal(err)
		}
		assigneeID = &_assigneeID
	}

	orderBy := gitlab.String(order)

	sort := gitlab.String(sortedBy)

	opts := gitlab.ListProjectMergeRequestsOptions{
		ListOptions: gitlab.ListOptions{
			PerPage: mrNumRet,
		},
		Labels:       mrLabels,
		State:        &mrState,
		TargetBranch: &mrTargetBranch,
		OrderBy:      orderBy,
		Sort:         sort,
		AssigneeID:   assigneeID,
	}

	if mrDraft && !mrReady {
		opts.WIP = gitlab.String("yes")
	} else if mrReady && !mrDraft {
		opts.WIP = gitlab.String("no")
	}

	return lab.MRList(rn, opts, num)
}

func init() {
	listCmd.Flags().StringSliceVarP(
		&mrLabels, "label", "l", []string{}, "filter merge requests by label")
	listCmd.Flags().StringVarP(
		&mrState, "state", "s", "opened",
		"filter merge requests by state (opened/closed/merged)")
	listCmd.Flags().IntVarP(
		&mrNumRet, "number", "n", 10,
		"number of merge requests to return")
	listCmd.Flags().StringVarP(
		&mrTargetBranch, "target-branch", "t", "",
		"filter merge requests by target branch")
	listCmd.Flags().BoolVarP(&mrAll, "all", "a", false, "list all MRs on the project")
	listCmd.Flags().BoolVarP(&mrMine, "mine", "m", false, "list only MRs assigned to me")
	listCmd.Flags().StringVar(
		&mrAssignee, "assignee", "", "list only MRs assigned to $username")
	listCmd.Flags().StringVar(&order, "order", "updated_at", "display order (updated_at/created_at)")
	listCmd.Flags().StringVar(&sortedBy, "sort", "desc", "sort order (desc/asc)")
	listCmd.Flags().BoolVarP(&mrDraft, "draft", "", false, "list MRs marked as draft")
	listCmd.Flags().BoolVarP(&mrReady, "ready", "", false, "list MRs not marked as draft")
	listCmd.Flags().SortFlags = false

	mrCmd.AddCommand(listCmd)
	carapace.Gen(listCmd).FlagCompletion(carapace.ActionMap{
		"state": carapace.ActionValues("opened", "closed", "merged"),
	})

	carapace.Gen(listCmd).PositionalCompletion(
		action.Remotes(),
	)
}
<|endoftext|>


<commit_before>// Copyright © 2017 Aidan Steele <aidan.steele@glassechidna.com.au>
//
// 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 cmd

import (
	"bytes"
	"context"
	"fmt"
	"github.com/aws/aws-sdk-go/service/cloudformation"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/aws/aws-sdk-go/service/sts"
	"github.com/glassechidna/stackit/pkg/stackit"
	"github.com/olekukonko/tablewriter"
	"github.com/pkg/errors"
	"github.com/spf13/cobra"
	"io"
	"os"
	"path/filepath"
	"strings"
	"text/template"
)

func packageTemplate(ctx context.Context, region, profile, stackName, templatePath string, tags, parameters map[string]string, writer io.Writer) error {
	absPath, err := filepath.Abs(templatePath)
	if err != nil {
		return errors.Wrapf(err, "determining absolute path of '%s'", templatePath)
	}
	if _, err := os.Stat(absPath); os.IsNotExist(err) {
		return errors.Errorf("no file exists at %s", absPath)
	}

	sess := awsSession(profile, region)
	sit := stackit.NewStackit(cloudformation.New(sess), sts.New(sess))
	packager := stackit.NewPackager(s3.New(sess), sts.New(sess), region)

	events := make(chan stackit.TailStackEvent)
	printer := stackit.NewTailPrinterWithOptions(true, true, writer)

	printerCtx, printerCancel := context.WithCancel(ctx)
	defer printerCancel()

	go func() {
		for {
			select {
			case <-printerCtx.Done():
				return
			case tailEvent := <-events:
				printer.PrintTailEvent(tailEvent)
			}
		}
	}()

	upInput, err := packager.Package(stackName, absPath, tags, parameters)
	if err != nil {
		return errors.Wrap(err, "packaging template")
	}

	prepared, err := sit.Prepare(ctx, *upInput, events)
	if err != nil {
		panic(err)
	}

	if prepared == nil {
		return nil // no-op change set
	}

	io.WriteString(writer, userFriendlyChangesOutput(prepared))

	err = savePreparedOutput(prepared)
	return errors.Wrap(err, "saving packaged output")
}

func userFriendlyChangesOutput(output *stackit.PrepareOutput) string {
	sbuf := &strings.Builder{}
	tbl := tablewriter.NewWriter(sbuf)
	tbl.SetHeader([]string{"Action", "Resource", "Type"})

	for _, change := range output.Changes {
		tbl.Append([]string{
			*change.ResourceChange.Action,
			*change.ResourceChange.LogicalResourceId,
			*change.ResourceChange.ResourceType,
		})
	}

	tbl.Render()

	buf := &bytes.Buffer{}

	tmpl, err := template.New("").Parse(`
Stack ID: {{ .StackId }}
Change Set ID: {{ .ChangeSetId }}
Changes:

{{ .Changes }}`)
	if err != nil {
		panic(err)
	}

	err = tmpl.Execute(buf, map[string]interface{}{
		"StackId":     *output.Output.StackId,
		"ChangeSetId": *output.Output.Id,
		"Changes":     sbuf.String(),
	})

	return buf.String()
}

func init() {
	cmd := &cobra.Command{
		Use:   "package",
		Short: "Package template and create change set",
		Long: `
package will:

* Upload any local paths referenced in the template (complete list[1]) to S3
* Create a changeset from the transformed template
* Print out a list of the estimated changes that executing the template will cause
* Save to disk the change set ID for later execution of 'stackit execute'

[1]: https://docs.aws.amazon.com/cli/latest/reference/cloudformation/package.html
`,
		Run: func(cmd *cobra.Command, args []string) {
			region, _ := cmd.PersistentFlags().GetString("region")
			profile, _ := cmd.PersistentFlags().GetString("profile")
			stackName, _ := cmd.PersistentFlags().GetString("stack-name")
			templatePath, _ := cmd.PersistentFlags().GetString("template")
			tagKvps, _ := cmd.PersistentFlags().GetStringSlice("tags")
			tags := keyvalSliceToMap(tagKvps)
			params := keyvalSliceToMap(args)

			err := packageTemplate(context.Background(), region, profile, stackName, templatePath, tags, params, cmd.OutOrStderr())
			if err != nil {
				fmt.Fprintf(cmd.OutOrStderr(), "%+v\n", err)
			}
		},
	}

	cmd.PersistentFlags().String("stack-name", "", "")
	cmd.PersistentFlags().String("template", "", "")
	cmd.PersistentFlags().StringSlice("tags", []string{}, "")
	RootCmd.AddCommand(cmd)
}
<commit_msg>Rely on SDK-decided region (closes #31)<commit_after>// Copyright © 2017 Aidan Steele <aidan.steele@glassechidna.com.au>
//
// 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 cmd

import (
	"bytes"
	"context"
	"fmt"
	"github.com/aws/aws-sdk-go/service/cloudformation"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/aws/aws-sdk-go/service/sts"
	"github.com/glassechidna/stackit/pkg/stackit"
	"github.com/olekukonko/tablewriter"
	"github.com/pkg/errors"
	"github.com/spf13/cobra"
	"io"
	"os"
	"path/filepath"
	"strings"
	"text/template"
)

func packageTemplate(ctx context.Context, region, profile, stackName, templatePath string, tags, parameters map[string]string, writer io.Writer) error {
	absPath, err := filepath.Abs(templatePath)
	if err != nil {
		return errors.Wrapf(err, "determining absolute path of '%s'", templatePath)
	}
	if _, err := os.Stat(absPath); os.IsNotExist(err) {
		return errors.Errorf("no file exists at %s", absPath)
	}

	sess := awsSession(profile, region)
	sit := stackit.NewStackit(cloudformation.New(sess), sts.New(sess))

	s3api := s3.New(sess)
	if region == "" && s3api.Config.Region != nil {
		region = *s3api.Config.Region
	}

	packager := stackit.NewPackager(s3api, sts.New(sess), region)

	events := make(chan stackit.TailStackEvent)
	printer := stackit.NewTailPrinterWithOptions(true, true, writer)

	printerCtx, printerCancel := context.WithCancel(ctx)
	defer printerCancel()

	go func() {
		for {
			select {
			case <-printerCtx.Done():
				return
			case tailEvent := <-events:
				printer.PrintTailEvent(tailEvent)
			}
		}
	}()

	upInput, err := packager.Package(stackName, absPath, tags, parameters)
	if err != nil {
		return errors.Wrap(err, "packaging template")
	}

	prepared, err := sit.Prepare(ctx, *upInput, events)
	if err != nil {
		panic(err)
	}

	if prepared == nil {
		return nil // no-op change set
	}

	io.WriteString(writer, userFriendlyChangesOutput(prepared))

	err = savePreparedOutput(prepared)
	return errors.Wrap(err, "saving packaged output")
}

func userFriendlyChangesOutput(output *stackit.PrepareOutput) string {
	sbuf := &strings.Builder{}
	tbl := tablewriter.NewWriter(sbuf)
	tbl.SetHeader([]string{"Action", "Resource", "Type"})

	for _, change := range output.Changes {
		tbl.Append([]string{
			*change.ResourceChange.Action,
			*change.ResourceChange.LogicalResourceId,
			*change.ResourceChange.ResourceType,
		})
	}

	tbl.Render()

	buf := &bytes.Buffer{}

	tmpl, err := template.New("").Parse(`
Stack ID: {{ .StackId }}
Change Set ID: {{ .ChangeSetId }}
Changes:

{{ .Changes }}`)
	if err != nil {
		panic(err)
	}

	err = tmpl.Execute(buf, map[string]interface{}{
		"StackId":     *output.Output.StackId,
		"ChangeSetId": *output.Output.Id,
		"Changes":     sbuf.String(),
	})

	return buf.String()
}

func init() {
	cmd := &cobra.Command{
		Use:   "package",
		Short: "Package template and create change set",
		Long: `
package will:

* Upload any local paths referenced in the template (complete list[1]) to S3
* Create a changeset from the transformed template
* Print out a list of the estimated changes that executing the template will cause
* Save to disk the change set ID for later execution of 'stackit execute'

[1]: https://docs.aws.amazon.com/cli/latest/reference/cloudformation/package.html
`,
		Run: func(cmd *cobra.Command, args []string) {
			region, _ := cmd.PersistentFlags().GetString("region")
			profile, _ := cmd.PersistentFlags().GetString("profile")
			stackName, _ := cmd.PersistentFlags().GetString("stack-name")
			templatePath, _ := cmd.PersistentFlags().GetString("template")
			tagKvps, _ := cmd.PersistentFlags().GetStringSlice("tags")
			tags := keyvalSliceToMap(tagKvps)
			params := keyvalSliceToMap(args)

			err := packageTemplate(context.Background(), region, profile, stackName, templatePath, tags, params, cmd.OutOrStderr())
			if err != nil {
				fmt.Fprintf(cmd.OutOrStderr(), "%+v\n", err)
			}
		},
	}

	cmd.PersistentFlags().String("stack-name", "", "")
	cmd.PersistentFlags().String("template", "", "")
	cmd.PersistentFlags().StringSlice("tags", []string{}, "")
	RootCmd.AddCommand(cmd)
}
<|endoftext|>


<commit_before>package cmd

import (
	"errors"
	"fmt"
	"net/http"

	"github.com/exercism/cli/api"
	"github.com/exercism/cli/config"
	"github.com/spf13/cobra"
)

// prepareCmd does necessary setup for Exercism and its tracks.
var prepareCmd = &cobra.Command{
	Use:     "prepare",
	Aliases: []string{"p"},
	Short:   "Prepare does setup for Exercism and its tracks.",
	Long: `Prepare downloads settings and dependencies for Exercism and the language tracks.

When called without any arguments, this downloads all the copy for the CLI so we
know what to say in all the various situations. It also provides an up-to-date list
of the API endpoints to use.

When called with a track ID, it will do specific setup for that track. This
might include downloading the files that the track maintainers have said are
necessary for the track in general. Any files that are only necessary for a specific
exercise will be downloaded along with the exercise.

To customize the CLI to suit your own preferences, use the configure command.
	`,
	RunE: func(cmd *cobra.Command, args []string) error {
		track, err := cmd.Flags().GetString("track")
		if err != nil {
			return err
		}

		if track == "" {
			fmt.Println("prepare called")
			return nil
		}
		return prepareTrack(track)
	},
}

func prepareTrack(id string) error {
	usrCfg, err := config.NewUserConfig()
	if err != nil {
		return err
	}

	client, err := api.NewClient(usrCfg.Token, usrCfg.APIBaseURL)
	if err != nil {
		return err
	}
	url := fmt.Sprintf("%s/tracks/%s", usrCfg.APIBaseURL, id)

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

	payload := &prepareTrackPayload{}
	res, err := client.Do(req, payload)
	if err != nil {
		return err
	}

	if res.StatusCode != http.StatusOK {
		return errors.New("api call failed")
	}

	cliCfg, err := config.NewCLIConfig()
	if err != nil {
		return err
	}

	t, ok := cliCfg.Tracks[id]
	if !ok {
		t = config.NewTrack(id)
	}
	if payload.Track.TestPattern != "" {
		t.IgnorePatterns = append(t.IgnorePatterns, payload.Track.TestPattern)
	}
	cliCfg.Tracks[id] = t

	return cliCfg.Write()
}

type prepareTrackPayload struct {
	Track struct {
		ID          string `json:"id"`
		Language    string `json:"language"`
		TestPattern string `json:"test_pattern"`
	} `json:"track"`
}

func initPrepareCmd() {
	prepareCmd.Flags().StringP("track", "t", "", "the track you want to prepare")
}

func init() {
	RootCmd.AddCommand(prepareCmd)
	initPrepareCmd()
}
<commit_msg>Inline http error handling into prepare command<commit_after>package cmd

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"

	"github.com/exercism/cli/api"
	"github.com/exercism/cli/config"
	"github.com/spf13/cobra"
)

// prepareCmd does necessary setup for Exercism and its tracks.
var prepareCmd = &cobra.Command{
	Use:     "prepare",
	Aliases: []string{"p"},
	Short:   "Prepare does setup for Exercism and its tracks.",
	Long: `Prepare downloads settings and dependencies for Exercism and the language tracks.

When called without any arguments, this downloads all the copy for the CLI so we
know what to say in all the various situations. It also provides an up-to-date list
of the API endpoints to use.

When called with a track ID, it will do specific setup for that track. This
might include downloading the files that the track maintainers have said are
necessary for the track in general. Any files that are only necessary for a specific
exercise will be downloaded along with the exercise.

To customize the CLI to suit your own preferences, use the configure command.
	`,
	RunE: func(cmd *cobra.Command, args []string) error {
		track, err := cmd.Flags().GetString("track")
		if err != nil {
			return err
		}

		if track == "" {
			fmt.Println("prepare called")
			return nil
		}
		return prepareTrack(track)
	},
}

func prepareTrack(id string) error {
	usrCfg, err := config.NewUserConfig()
	if err != nil {
		return err
	}

	client, err := api.NewClient(usrCfg.Token, usrCfg.APIBaseURL)
	if err != nil {
		return err
	}
	url := fmt.Sprintf("%s/tracks/%s", usrCfg.APIBaseURL, id)

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

	res, err := client.Do(req, nil)
	if err != nil {
		return err
	}
	defer res.Body.Close()

	payload := &prepareTrackPayload{}

	if err := json.NewDecoder(res.Body).Decode(payload); err != nil {
		return fmt.Errorf("unable to parse API response - %s", err)
	}

	if res.StatusCode != http.StatusOK {
		return errors.New(payload.Error.Message)
	}

	cliCfg, err := config.NewCLIConfig()
	if err != nil {
		return err
	}

	t, ok := cliCfg.Tracks[id]
	if !ok {
		t = config.NewTrack(id)
	}
	if payload.Track.TestPattern != "" {
		t.IgnorePatterns = append(t.IgnorePatterns, payload.Track.TestPattern)
	}
	cliCfg.Tracks[id] = t

	return cliCfg.Write()
}

type prepareTrackPayload struct {
	Track struct {
		ID          string `json:"id"`
		Language    string `json:"language"`
		TestPattern string `json:"test_pattern"`
	} `json:"track"`
	Error struct {
		Type    string `json:"type"`
		Message string `json:"message"`
	} `json:"error,omitempty"`
}

func initPrepareCmd() {
	prepareCmd.Flags().StringP("track", "t", "", "the track you want to prepare")
}

func init() {
	RootCmd.AddCommand(prepareCmd)
	initPrepareCmd()
}
<|endoftext|>


<commit_before>/*
 * Minio Client (C) 2014, 2015 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 cmd

import (
	"bufio"
	"encoding/json"
	"fmt"
	"os"
	"time"

	"github.com/fatih/color"
	"github.com/minio/cli"
	"github.com/minio/mc/pkg/console"
	"github.com/minio/minio/pkg/probe"
)

// Day time.Duration for day.
const Day = 24 * time.Hour

// rm specific flags.
var (
	rmFlags = []cli.Flag{
		cli.BoolFlag{
			Name:  "help, h",
			Usage: "Show this help.",
		},
		cli.BoolFlag{
			Name:  "recursive, r",
			Usage: "Remove recursively.",
		},
		cli.BoolFlag{
			Name:  "force",
			Usage: "Force a dangerous remove operation.",
		},
		cli.BoolFlag{
			Name:  "incomplete, I",
			Usage: "Remove an incomplete objects.",
		},
		cli.BoolFlag{
			Name:  "fake",
			Usage: "Perform a fake remove operation.",
		},
		cli.BoolFlag{
			Name:  "stdin",
			Usage: "Read object names from STDIN.",
		},
		cli.IntFlag{
			Name:  "older-than",
			Usage: "Remove objects older than N days.",
		},
	}
)

// remove a file or folder.
var rmCmd = cli.Command{
	Name:   "rm",
	Usage:  "Remove files and objects.",
	Action: mainRm,
	Flags:  append(rmFlags, globalFlags...),
	CustomHelpTemplate: `NAME:
   mc {{.Name}} - {{.Usage}}

USAGE:
   mc {{.Name}} [FLAGS] TARGET [TARGET ...]

FLAGS:
  {{range .Flags}}{{.}}
  {{end}}
EXAMPLES:
   1. Remove a file.
      $ mc {{.Name}} 1999/old-backup.tgz

   2. Remove all objects recursively.
      $ mc {{.Name}} --recursive s3/jazz-songs/louis/

   3. Remove all objects older than '90' days.
      $ mc {{.Name}} --recursive --older-than=90 s3/jazz-songs/louis/

   4. Remove all objects read from STDIN.
      $ mc {{.Name}} --force --stdin

   5. Drop all incomplete uploads on 'jazz-songs' bucket.
      $ mc {{.Name}} --incomplete --recursive s3/jazz-songs/
`,
}

// Structured message depending on the type of console.
type rmMessage struct {
	Key string `json:"key"`
}

// Colorized message for console printing.
func (r rmMessage) String() string {
	return console.Colorize("Remove", fmt.Sprintf("Removing ‘%s’.", r.Key))
}

// JSON'ified message for scripting.
func (r rmMessage) JSON() string {
	msgBytes, e := json.Marshal(r)
	fatalIf(probe.NewError(e), "Unable to marshal into JSON.")
	return string(msgBytes)
}

// Validate command line arguments.
func checkRmSyntax(ctx *cli.Context) {
	// Set command flags from context.
	isForce := ctx.Bool("force")
	isRecursive := ctx.Bool("recursive")
	isStdin := ctx.Bool("stdin")

	if !ctx.Args().Present() && !isStdin {
		exitCode := 1
		cli.ShowCommandHelpAndExit(ctx, "rm", exitCode)
	}

	// For all recursive operations make sure to check for 'force' flag.
	if (isRecursive || isStdin) && !isForce {
		fatalIf(errDummy().Trace(),
			"Removal requires --force option. This operational is *IRREVERSIBLE*. Please review carefully before performing this *DANGEROUS* operation.")
	}
}

func removeSingle(url string, isIncomplete bool, isFake bool, older int) {
	targetAlias, targetURL, _ := mustExpandAlias(url)
	clnt, pErr := newClientFromAlias(targetAlias, targetURL)
	if pErr != nil {
		errorIf(pErr.Trace(url), "Invalid argument ‘"+url+"’.")
		return // End of journey.
	}

	if older > 0 {
		content, pErr := clnt.Stat(isIncomplete)
		if pErr != nil {
			errorIf(pErr.Trace(url), "Failed to remove ‘"+url+"’.")
			return
		}

		// Check whether object is created older than given time.
		now := time.Now().UTC()
		timeDiff := now.Sub(content.Time)
		if timeDiff < (time.Duration(older) * Day) {
			// time difference of info.Time with current time is less than older time.
			return
		}
	}

	printMsg(rmMessage{Key: url})
	if !isFake {
		contentCh := make(chan *clientContent, 1)
		contentCh <- &clientContent{URL: *newClientURL(targetURL)}
		close(contentCh)

		errorCh := clnt.Remove(isIncomplete, contentCh)
		for pErr := range errorCh {
			if pErr != nil {
				errorIf(pErr.Trace(url), "Failed to remove ‘"+url+"’.")
				switch pErr.ToGoError().(type) {
				case PathInsufficientPermission:
					// Ignore Permission error.
					continue
				}
				return
			}
		}
	}
}

func removeRecursive(url string, isIncomplete bool, isFake bool, older int) {
	targetAlias, targetURL, _ := mustExpandAlias(url)
	clnt, pErr := newClientFromAlias(targetAlias, targetURL)
	if pErr != nil {
		errorIf(pErr.Trace(url), "Failed to remove ‘"+url+"’ recursively.")
		return // End of journey.
	}

	contentCh := make(chan *clientContent)
	errorCh := clnt.Remove(isIncomplete, contentCh)

	isEmpty := true
	isRecursive := true
	for content := range clnt.List(isRecursive, isIncomplete, DirLast) {
		isEmpty = false
		pErr := content.Err
		if pErr != nil {
			errorIf(pErr.Trace(url), "Failed to remove ‘"+url+"’ recursively.")
			switch pErr.ToGoError().(type) {
			case PathInsufficientPermission:
				// Ignore Permission error.
				continue
			}

			close(contentCh)
			return
		}

		if older > 0 {
			// Check whether object is created older than given time.
			now := time.Now().UTC()
			timeDiff := now.Sub(content.Time)
			if timeDiff < (time.Duration(older) * Day) {
				// time difference of info.Time with current time is less than older time.
				continue
			}
		}

		urlString := content.URL.Path
		printMsg(rmMessage{Key: targetAlias + urlString})
		if !isFake {
			sent := false
			for sent == false {
				select {
				case contentCh <- content:
					sent = true
				case pErr := <-errorCh:
					errorIf(pErr.Trace(urlString), "Failed to remove ‘"+urlString+"’.")
					switch pErr.ToGoError().(type) {
					case PathInsufficientPermission:
						// Ignore Permission error.
						continue
					}

					close(contentCh)
					return
				}
			}
		}
	}

	close(contentCh)
	for pErr := range errorCh {
		errorIf(pErr.Trace(url), "Failed to remove ‘"+url+"’ recursively.")
		switch pErr.ToGoError().(type) {
		case PathInsufficientPermission:
			// Ignore Permission error.
			continue
		}
		return
	}

	// As clnt.List() returns empty, we just send dummy value to behave like non-recursive.
	if isEmpty {
		printMsg(rmMessage{Key: url})
	}
}

// main for rm command.
func mainRm(ctx *cli.Context) {
	// Set global flags from context.
	setGlobalsFromContext(ctx)

	// check 'rm' cli arguments.
	checkRmSyntax(ctx)

	// rm specific flags.
	isIncomplete := ctx.Bool("incomplete")
	isRecursive := ctx.Bool("recursive")
	isFake := ctx.Bool("fake")
	isStdin := ctx.Bool("stdin")
	older := ctx.Int("older-than")

	// Set color.
	console.SetColor("Remove", color.New(color.FgGreen, color.Bold))

	// Support multiple targets.
	for _, url := range ctx.Args() {
		if isRecursive {
			removeRecursive(url, isIncomplete, isFake, older)
		} else {
			removeSingle(url, isIncomplete, isFake, older)
		}
	}

	if !isStdin {
		return
	}

	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		url := scanner.Text()
		if isRecursive {
			removeRecursive(url, isIncomplete, isFake, older)
		} else {
			removeSingle(url, isIncomplete, isFake, older)
		}
	}
}
<commit_msg>Adjust a help text line to match its example description (#1869)<commit_after>/*
 * Minio Client (C) 2014, 2015 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 cmd

import (
	"bufio"
	"encoding/json"
	"fmt"
	"os"
	"time"

	"github.com/fatih/color"
	"github.com/minio/cli"
	"github.com/minio/mc/pkg/console"
	"github.com/minio/minio/pkg/probe"
)

// Day time.Duration for day.
const Day = 24 * time.Hour

// rm specific flags.
var (
	rmFlags = []cli.Flag{
		cli.BoolFlag{
			Name:  "help, h",
			Usage: "Show this help.",
		},
		cli.BoolFlag{
			Name:  "recursive, r",
			Usage: "Remove recursively.",
		},
		cli.BoolFlag{
			Name:  "force",
			Usage: "Force a dangerous remove operation.",
		},
		cli.BoolFlag{
			Name:  "incomplete, I",
			Usage: "Remove incomplete uploads.",
		},
		cli.BoolFlag{
			Name:  "fake",
			Usage: "Perform a fake remove operation.",
		},
		cli.BoolFlag{
			Name:  "stdin",
			Usage: "Read object names from STDIN.",
		},
		cli.IntFlag{
			Name:  "older-than",
			Usage: "Remove objects older than N days.",
		},
	}
)

// remove a file or folder.
var rmCmd = cli.Command{
	Name:   "rm",
	Usage:  "Remove files and objects.",
	Action: mainRm,
	Flags:  append(rmFlags, globalFlags...),
	CustomHelpTemplate: `NAME:
   mc {{.Name}} - {{.Usage}}

USAGE:
   mc {{.Name}} [FLAGS] TARGET [TARGET ...]

FLAGS:
  {{range .Flags}}{{.}}
  {{end}}
EXAMPLES:
   1. Remove a file.
      $ mc {{.Name}} 1999/old-backup.tgz

   2. Remove all objects recursively.
      $ mc {{.Name}} --recursive s3/jazz-songs/louis/

   3. Remove all objects older than '90' days.
      $ mc {{.Name}} --recursive --older-than=90 s3/jazz-songs/louis/

   4. Remove all objects read from STDIN.
      $ mc {{.Name}} --force --stdin

   5. Drop all incomplete uploads on 'jazz-songs' bucket.
      $ mc {{.Name}} --incomplete --recursive s3/jazz-songs/
`,
}

// Structured message depending on the type of console.
type rmMessage struct {
	Key string `json:"key"`
}

// Colorized message for console printing.
func (r rmMessage) String() string {
	return console.Colorize("Remove", fmt.Sprintf("Removing ‘%s’.", r.Key))
}

// JSON'ified message for scripting.
func (r rmMessage) JSON() string {
	msgBytes, e := json.Marshal(r)
	fatalIf(probe.NewError(e), "Unable to marshal into JSON.")
	return string(msgBytes)
}

// Validate command line arguments.
func checkRmSyntax(ctx *cli.Context) {
	// Set command flags from context.
	isForce := ctx.Bool("force")
	isRecursive := ctx.Bool("recursive")
	isStdin := ctx.Bool("stdin")

	if !ctx.Args().Present() && !isStdin {
		exitCode := 1
		cli.ShowCommandHelpAndExit(ctx, "rm", exitCode)
	}

	// For all recursive operations make sure to check for 'force' flag.
	if (isRecursive || isStdin) && !isForce {
		fatalIf(errDummy().Trace(),
			"Removal requires --force option. This operational is *IRREVERSIBLE*. Please review carefully before performing this *DANGEROUS* operation.")
	}
}

func removeSingle(url string, isIncomplete bool, isFake bool, older int) {
	targetAlias, targetURL, _ := mustExpandAlias(url)
	clnt, pErr := newClientFromAlias(targetAlias, targetURL)
	if pErr != nil {
		errorIf(pErr.Trace(url), "Invalid argument ‘"+url+"’.")
		return // End of journey.
	}

	if older > 0 {
		content, pErr := clnt.Stat(isIncomplete)
		if pErr != nil {
			errorIf(pErr.Trace(url), "Failed to remove ‘"+url+"’.")
			return
		}

		// Check whether object is created older than given time.
		now := time.Now().UTC()
		timeDiff := now.Sub(content.Time)
		if timeDiff < (time.Duration(older) * Day) {
			// time difference of info.Time with current time is less than older time.
			return
		}
	}

	printMsg(rmMessage{Key: url})
	if !isFake {
		contentCh := make(chan *clientContent, 1)
		contentCh <- &clientContent{URL: *newClientURL(targetURL)}
		close(contentCh)

		errorCh := clnt.Remove(isIncomplete, contentCh)
		for pErr := range errorCh {
			if pErr != nil {
				errorIf(pErr.Trace(url), "Failed to remove ‘"+url+"’.")
				switch pErr.ToGoError().(type) {
				case PathInsufficientPermission:
					// Ignore Permission error.
					continue
				}
				return
			}
		}
	}
}

func removeRecursive(url string, isIncomplete bool, isFake bool, older int) {
	targetAlias, targetURL, _ := mustExpandAlias(url)
	clnt, pErr := newClientFromAlias(targetAlias, targetURL)
	if pErr != nil {
		errorIf(pErr.Trace(url), "Failed to remove ‘"+url+"’ recursively.")
		return // End of journey.
	}

	contentCh := make(chan *clientContent)
	errorCh := clnt.Remove(isIncomplete, contentCh)

	isEmpty := true
	isRecursive := true
	for content := range clnt.List(isRecursive, isIncomplete, DirLast) {
		isEmpty = false
		pErr := content.Err
		if pErr != nil {
			errorIf(pErr.Trace(url), "Failed to remove ‘"+url+"’ recursively.")
			switch pErr.ToGoError().(type) {
			case PathInsufficientPermission:
				// Ignore Permission error.
				continue
			}

			close(contentCh)
			return
		}

		if older > 0 {
			// Check whether object is created older than given time.
			now := time.Now().UTC()
			timeDiff := now.Sub(content.Time)
			if timeDiff < (time.Duration(older) * Day) {
				// time difference of info.Time with current time is less than older time.
				continue
			}
		}

		urlString := content.URL.Path
		printMsg(rmMessage{Key: targetAlias + urlString})
		if !isFake {
			sent := false
			for sent == false {
				select {
				case contentCh <- content:
					sent = true
				case pErr := <-errorCh:
					errorIf(pErr.Trace(urlString), "Failed to remove ‘"+urlString+"’.")
					switch pErr.ToGoError().(type) {
					case PathInsufficientPermission:
						// Ignore Permission error.
						continue
					}

					close(contentCh)
					return
				}
			}
		}
	}

	close(contentCh)
	for pErr := range errorCh {
		errorIf(pErr.Trace(url), "Failed to remove ‘"+url+"’ recursively.")
		switch pErr.ToGoError().(type) {
		case PathInsufficientPermission:
			// Ignore Permission error.
			continue
		}
		return
	}

	// As clnt.List() returns empty, we just send dummy value to behave like non-recursive.
	if isEmpty {
		printMsg(rmMessage{Key: url})
	}
}

// main for rm command.
func mainRm(ctx *cli.Context) {
	// Set global flags from context.
	setGlobalsFromContext(ctx)

	// check 'rm' cli arguments.
	checkRmSyntax(ctx)

	// rm specific flags.
	isIncomplete := ctx.Bool("incomplete")
	isRecursive := ctx.Bool("recursive")
	isFake := ctx.Bool("fake")
	isStdin := ctx.Bool("stdin")
	older := ctx.Int("older-than")

	// Set color.
	console.SetColor("Remove", color.New(color.FgGreen, color.Bold))

	// Support multiple targets.
	for _, url := range ctx.Args() {
		if isRecursive {
			removeRecursive(url, isIncomplete, isFake, older)
		} else {
			removeSingle(url, isIncomplete, isFake, older)
		}
	}

	if !isStdin {
		return
	}

	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		url := scanner.Text()
		if isRecursive {
			removeRecursive(url, isIncomplete, isFake, older)
		} else {
			removeSingle(url, isIncomplete, isFake, older)
		}
	}
}
<|endoftext|>