<commit_before>package main

import (
	"fmt"
	"io"
	"os"
	"os/exec"
	"path/filepath"

	"github.com/sbinet/go-commander"
	"github.com/sbinet/go-flag"
)

func hwaf_make_cmd_init() *commander.Command {
	cmd := &commander.Command{
		Run:       hwaf_run_cmd_init,
		UsageLine: "init [options] <workarea>",
		Short:     "initialize a new workarea",
		Long: `
init initializes a new workarea.

ex:
 $ hwaf init
 $ hwaf init .
 $ hwaf init my-work-area
`,
		Flag: *flag.NewFlagSet("hwaf-init", flag.ExitOnError),
	}
	cmd.Flag.Bool("quiet", false, "only print error and warning messages, all other output will be suppressed")

	return cmd
}

func hwaf_run_cmd_init(cmd *commander.Command, args []string) {
	var err error
	n := "hwaf-" + cmd.Name()
	dirname := ""

	switch len(args) {
	case 0:
		dirname = "."
	case 1:
		dirname = args[0]
	default:
		err = fmt.Errorf("%s: you need to give a directory name", n)
		handle_err(err)
	}

	dirname = os.ExpandEnv(dirname)
	dirname = filepath.Clean(dirname)

	quiet := cmd.Flag.Lookup("quiet").Value.Get().(bool)

	if !quiet {
		fmt.Printf("%s: creating workarea [%s]...\n", n, dirname)
	}

	if !path_exists(dirname) {
		err = os.MkdirAll(dirname, 0700)
		handle_err(err)
	}

	pwd, err := os.Getwd()
	handle_err(err)
	defer os.Chdir(pwd)

	err = os.Chdir(dirname)
	handle_err(err)

	// init a git repository in dirname
	if !quiet {
		fmt.Printf("%s: initialize git workarea repository...\n", n)
	}
	git := exec.Command("git", "init", ".")
	if !quiet {
		git.Stdout = os.Stdout
		git.Stderr = os.Stderr
	}
	err = git.Run()
	handle_err(err)

	// add hep-waf-tools
	if !quiet {
		fmt.Printf("%s: add .hwaf/tools submodule...\n", n)
	}
	git = exec.Command("git", "submodule", "add",
		//"git://github.com/mana-fwk/hep-waftools",
		"file:///Users/binet/dev/mana/git/hep-waftools",
		".hwaf/tools",
		)
	if !quiet {
		git.Stdout = os.Stdout
		git.Stderr = os.Stderr
	}
	err = git.Run()
	handle_err(err)

	// init submodules
	if !quiet {
		fmt.Printf("%s: initialize submodule(s)...\n", n)
	}
	git = exec.Command("git", "submodule", "update",
		"--recursive", "--init",
		)
	if !quiet {
		git.Stdout = os.Stdout
		git.Stderr = os.Stderr
	}
	err = git.Run()
	handle_err(err)

	// add template wscript
	if !quiet {
		fmt.Printf("%s: add top-level wscript...\n", n)
	}

	wscript, err := os.Create("wscript")
	handle_err(err)
	defer wscript.Close()

	wscript_tmpl, err := os.Open(".hwaf/tools/hwaf-wscript")
	handle_err(err)
	defer wscript_tmpl.Close()

	_, err = io.Copy(wscript, wscript_tmpl)
	handle_err(err)
	handle_err(wscript.Sync())
	handle_err(wscript.Close())

	git = exec.Command("git", "add", "wscript")
	if !quiet {
		git.Stdout = os.Stdout
		git.Stderr = os.Stderr
	}
	err = git.Run()
	handle_err(err)

	// commit
	if !quiet {
		fmt.Printf("%s: commit workarea...\n", n)
	}
	git = exec.Command("git", "commit", "-m", `"init hwaf workarea"`)
	if !quiet {
		git.Stdout = os.Stdout
		git.Stderr = os.Stderr
	}
	err = git.Run()
	handle_err(err)

	if !quiet {
		fmt.Printf("%s: creating workarea [%s]... [ok]\n", n, dirname)
	}
}

// EOF
<commit_msg>take hwaf-tools from github<commit_after>package main

import (
	"fmt"
	"io"
	"os"
	"os/exec"
	"path/filepath"

	"github.com/sbinet/go-commander"
	"github.com/sbinet/go-flag"
)

func hwaf_make_cmd_init() *commander.Command {
	cmd := &commander.Command{
		Run:       hwaf_run_cmd_init,
		UsageLine: "init [options] <workarea>",
		Short:     "initialize a new workarea",
		Long: `
init initializes a new workarea.

ex:
 $ hwaf init
 $ hwaf init .
 $ hwaf init my-work-area
`,
		Flag: *flag.NewFlagSet("hwaf-init", flag.ExitOnError),
	}
	cmd.Flag.Bool("quiet", false, "only print error and warning messages, all other output will be suppressed")

	return cmd
}

func hwaf_run_cmd_init(cmd *commander.Command, args []string) {
	var err error
	n := "hwaf-" + cmd.Name()
	dirname := ""

	switch len(args) {
	case 0:
		dirname = "."
	case 1:
		dirname = args[0]
	default:
		err = fmt.Errorf("%s: you need to give a directory name", n)
		handle_err(err)
	}

	dirname = os.ExpandEnv(dirname)
	dirname = filepath.Clean(dirname)

	quiet := cmd.Flag.Lookup("quiet").Value.Get().(bool)

	if !quiet {
		fmt.Printf("%s: creating workarea [%s]...\n", n, dirname)
	}

	if !path_exists(dirname) {
		err = os.MkdirAll(dirname, 0700)
		handle_err(err)
	}

	pwd, err := os.Getwd()
	handle_err(err)
	defer os.Chdir(pwd)

	err = os.Chdir(dirname)
	handle_err(err)

	// init a git repository in dirname
	if !quiet {
		fmt.Printf("%s: initialize git workarea repository...\n", n)
	}
	git := exec.Command("git", "init", ".")
	if !quiet {
		git.Stdout = os.Stdout
		git.Stderr = os.Stderr
	}
	err = git.Run()
	handle_err(err)

	// add hep-waf-tools
	if !quiet {
		fmt.Printf("%s: add .hwaf/tools submodule...\n", n)
	}
	git = exec.Command("git", "submodule", "add",
		"git://github.com/mana-fwk/hep-waftools",
		//"file:///Users/binet/dev/mana/git/hep-waftools",
		".hwaf/tools",
		)
	if !quiet {
		git.Stdout = os.Stdout
		git.Stderr = os.Stderr
	}
	err = git.Run()
	handle_err(err)

	// init submodules
	if !quiet {
		fmt.Printf("%s: initialize submodule(s)...\n", n)
	}
	git = exec.Command("git", "submodule", "update",
		"--recursive", "--init",
		)
	if !quiet {
		git.Stdout = os.Stdout
		git.Stderr = os.Stderr
	}
	err = git.Run()
	handle_err(err)

	// add template wscript
	if !quiet {
		fmt.Printf("%s: add top-level wscript...\n", n)
	}

	wscript, err := os.Create("wscript")
	handle_err(err)
	defer wscript.Close()

	wscript_tmpl, err := os.Open(".hwaf/tools/hwaf-wscript")
	handle_err(err)
	defer wscript_tmpl.Close()

	_, err = io.Copy(wscript, wscript_tmpl)
	handle_err(err)
	handle_err(wscript.Sync())
	handle_err(wscript.Close())

	git = exec.Command("git", "add", "wscript")
	if !quiet {
		git.Stdout = os.Stdout
		git.Stderr = os.Stderr
	}
	err = git.Run()
	handle_err(err)

	// commit
	if !quiet {
		fmt.Printf("%s: commit workarea...\n", n)
	}
	git = exec.Command("git", "commit", "-m", `"init hwaf workarea"`)
	if !quiet {
		git.Stdout = os.Stdout
		git.Stderr = os.Stderr
	}
	err = git.Run()
	handle_err(err)

	if !quiet {
		fmt.Printf("%s: creating workarea [%s]... [ok]\n", n, dirname)
	}
}

// EOF
<|endoftext|>


<commit_before>package main

import (
	"context"
	"fmt"
	"os"
	"time"

	platform "github.com/influxdata/influxdb"
	"github.com/influxdata/influxdb/cmd/influx/internal"
	"github.com/influxdata/influxdb/http"
	"github.com/spf13/cobra"
)

// Bucket Command
var bucketCmd = &cobra.Command{
	Use:   "bucket",
	Short: "Bucket management commands",
	Run:   bucketF,
}

func bucketF(cmd *cobra.Command, args []string) {
	cmd.Usage()
}

// BucketCreateFlags define the Create Command
type BucketCreateFlags struct {
	name      string
	orgID     string
	retention time.Duration
}

var bucketCreateFlags BucketCreateFlags

func init() {
	bucketCreateCmd := &cobra.Command{
		Use:   "create",
		Short: "Create bucket",
		RunE:  wrapCheckSetup(bucketCreateF),
	}

	bucketCreateCmd.Flags().StringVarP(&bucketCreateFlags.name, "name", "n", "", "Name of bucket that will be created")
	bucketCreateCmd.Flags().DurationVarP(&bucketCreateFlags.retention, "retention", "r", 0, "Duration in nanoseconds data will live in bucket")
	bucketCreateCmd.Flags().StringVarP(&bucketCreateFlags.orgID, "org-id", "", "", "The ID of the organization that owns the bucket")
	bucketCreateCmd.MarkFlagRequired("name")

	bucketCmd.AddCommand(bucketCreateCmd)
}

func newBucketService(f Flags) (platform.BucketService, error) {
	if f.local {
		return newLocalKVService()
	}
	return &http.BucketService{
		Addr:               f.host,
		Token:              f.token,
		InsecureSkipVerify: f.skipVerify,
	}, nil
}

func bucketCreateF(cmd *cobra.Command, args []string) error {
	if bucketCreateFlags.orgID == "" {
		return fmt.Errorf("must specify org-id")
	}

	s, err := newBucketService(flags)
	if err != nil {
		return fmt.Errorf("failed to initialize bucket service client: %v", err)
	}

	b := &platform.Bucket{
		Name:            bucketCreateFlags.name,
		RetentionPeriod: bucketCreateFlags.retention,
	}

	if bucketCreateFlags.orgID != "" {
		id, err := platform.IDFromString(bucketCreateFlags.orgID)
		if err != nil {
			return fmt.Errorf("failed to decode org id %q: %v", bucketCreateFlags.orgID, err)
		}
		b.OrgID = *id
	}

	if err := s.CreateBucket(context.Background(), b); err != nil {
		return fmt.Errorf("failed to create bucket: %v", err)
	}

	w := internal.NewTabWriter(os.Stdout)
	w.WriteHeaders(
		"ID",
		"Name",
		"Retention",
		"Org",
		"OrganizationID",
	)
	w.Write(map[string]interface{}{
		"ID":        b.ID.String(),
		"Name":      b.Name,
		"Retention": b.RetentionPeriod,
		"OrgID":     b.OrgID.String(),
	})
	w.Flush()

	return nil
}

// BucketFindFlags define the Find Command
type BucketFindFlags struct {
	name  string
	id    string
	org   string
	orgID string
}

var bucketFindFlags BucketFindFlags

func init() {
	bucketFindCmd := &cobra.Command{
		Use:   "find",
		Short: "Find buckets",
		RunE:  wrapCheckSetup(bucketFindF),
	}

	bucketFindCmd.Flags().StringVarP(&bucketFindFlags.name, "name", "n", "", "The bucket name")
	bucketFindCmd.Flags().StringVarP(&bucketFindFlags.id, "id", "i", "", "The bucket ID")
	bucketFindCmd.Flags().StringVarP(&bucketFindFlags.orgID, "org-id", "", "", "The bucket organization ID")
	bucketFindCmd.Flags().StringVarP(&bucketFindFlags.org, "org", "o", "", "The bucket organization name")

	bucketCmd.AddCommand(bucketFindCmd)
}

func bucketFindF(cmd *cobra.Command, args []string) error {
	s, err := newBucketService(flags)
	if err != nil {
		return fmt.Errorf("failed to initialize bucket service client: %v", err)
	}

	filter := platform.BucketFilter{}
	if bucketFindFlags.name != "" {
		filter.Name = &bucketFindFlags.name
	}

	if bucketFindFlags.id != "" {
		id, err := platform.IDFromString(bucketFindFlags.id)
		if err != nil {
			return fmt.Errorf("failed to decode bucket id %q: %v", bucketFindFlags.id, err)
		}
		filter.ID = id
	}

	if bucketFindFlags.orgID != "" && bucketFindFlags.org != "" {
		return fmt.Errorf("must specify at exactly one of org and org-id")
	}

	if bucketFindFlags.orgID != "" {
		orgID, err := platform.IDFromString(bucketFindFlags.orgID)
		if err != nil {
			return fmt.Errorf("failed to decode org id %q: %v", bucketFindFlags.orgID, err)
		}
		filter.OrganizationID = orgID
	}

	if bucketFindFlags.org != "" {
		filter.Org = &bucketFindFlags.org
	}

	buckets, _, err := s.FindBuckets(context.Background(), filter)
	if err != nil {
		return fmt.Errorf("failed to retrieve buckets: %s", err)
	}

	w := internal.NewTabWriter(os.Stdout)
	w.WriteHeaders(
		"ID",
		"Name",
		"Retention",
		"Org",
		"OrganizationID",
	)
	for _, b := range buckets {
		w.Write(map[string]interface{}{
			"ID":        b.ID.String(),
			"Name":      b.Name,
			"Retention": b.RetentionPeriod,
			"OrgID":     b.OrgID.String(),
		})
	}
	w.Flush()

	return nil
}

// BucketUpdateFlags define the Update Command
type BucketUpdateFlags struct {
	id        string
	name      string
	retention time.Duration
}

var bucketUpdateFlags BucketUpdateFlags

func init() {
	bucketUpdateCmd := &cobra.Command{
		Use:   "update",
		Short: "Update bucket",
		RunE:  wrapCheckSetup(bucketUpdateF),
	}

	bucketUpdateCmd.Flags().StringVarP(&bucketUpdateFlags.id, "id", "i", "", "The bucket ID (required)")
	bucketUpdateCmd.Flags().StringVarP(&bucketUpdateFlags.name, "name", "n", "", "New bucket name")
	bucketUpdateCmd.Flags().DurationVarP(&bucketUpdateFlags.retention, "retention", "r", 0, "New duration data will live in bucket")
	bucketUpdateCmd.MarkFlagRequired("id")

	bucketCmd.AddCommand(bucketUpdateCmd)
}

func bucketUpdateF(cmd *cobra.Command, args []string) error {
	s, err := newBucketService(flags)
	if err != nil {
		return fmt.Errorf("failed to initialize bucket service client: %v", err)
	}

	var id platform.ID
	if err := id.DecodeFromString(bucketUpdateFlags.id); err != nil {
		return fmt.Errorf("failed to decode bucket id %q: %v", bucketUpdateFlags.id, err)
	}

	update := platform.BucketUpdate{}
	if bucketUpdateFlags.name != "" {
		update.Name = &bucketUpdateFlags.name
	}
	if bucketUpdateFlags.retention != 0 {
		update.RetentionPeriod = &bucketUpdateFlags.retention
	}

	b, err := s.UpdateBucket(context.Background(), id, update)
	if err != nil {
		return fmt.Errorf("failed to update bucket: %v", err)
	}

	w := internal.NewTabWriter(os.Stdout)
	w.WriteHeaders(
		"ID",
		"Name",
		"Retention",
		"Organization",
		"OrganizationID",
	)
	w.Write(map[string]interface{}{
		"ID":        b.ID.String(),
		"Name":      b.Name,
		"Retention": b.RetentionPeriod,
		"OrgID":     b.OrgID.String(),
	})
	w.Flush()

	return nil
}

// BucketDeleteFlags define the Delete command
type BucketDeleteFlags struct {
	id string
}

var bucketDeleteFlags BucketDeleteFlags

func bucketDeleteF(cmd *cobra.Command, args []string) error {
	s, err := newBucketService(flags)
	if err != nil {
		return fmt.Errorf("failed to initialize bucket service client: %v", err)
	}

	var id platform.ID
	if err := id.DecodeFromString(bucketDeleteFlags.id); err != nil {
		return fmt.Errorf("failed to decode bucket id %q: %v", bucketDeleteFlags.id, err)
	}

	ctx := context.Background()
	b, err := s.FindBucketByID(ctx, id)
	if err != nil {
		return fmt.Errorf("failed to find bucket with id %q: %v", id, err)
	}

	if err = s.DeleteBucket(ctx, id); err != nil {
		return fmt.Errorf("failed to delete bucket with id %q: %v", id, err)
	}

	w := internal.NewTabWriter(os.Stdout)
	w.WriteHeaders(
		"ID",
		"Name",
		"Retention",
		"Organization",
		"OrganizationID",
		"Deleted",
	)
	w.Write(map[string]interface{}{
		"ID":        b.ID.String(),
		"Name":      b.Name,
		"Retention": b.RetentionPeriod,
		"OrgID":     b.OrgID.String(),
		"Deleted":   true,
	})
	w.Flush()

	return nil
}

func init() {
	bucketDeleteCmd := &cobra.Command{
		Use:   "delete",
		Short: "Delete bucket",
		RunE:  wrapCheckSetup(bucketDeleteF),
	}

	bucketDeleteCmd.Flags().StringVarP(&bucketDeleteFlags.id, "id", "i", "", "The bucket ID (required)")
	bucketDeleteCmd.MarkFlagRequired("id")

	bucketCmd.AddCommand(bucketDeleteCmd)
}
<commit_msg>fix(cli): add organization back to bucket response (#15947)<commit_after>package main

import (
	"context"
	"fmt"
	"os"
	"time"

	platform "github.com/influxdata/influxdb"
	"github.com/influxdata/influxdb/cmd/influx/internal"
	"github.com/influxdata/influxdb/http"
	"github.com/spf13/cobra"
)

// Bucket Command
var bucketCmd = &cobra.Command{
	Use:   "bucket",
	Short: "Bucket management commands",
	Run:   bucketF,
}

func bucketF(cmd *cobra.Command, args []string) {
	cmd.Usage()
}

// BucketCreateFlags define the Create Command
type BucketCreateFlags struct {
	name      string
	orgID     string
	retention time.Duration
}

var bucketCreateFlags BucketCreateFlags

func init() {
	bucketCreateCmd := &cobra.Command{
		Use:   "create",
		Short: "Create bucket",
		RunE:  wrapCheckSetup(bucketCreateF),
	}

	bucketCreateCmd.Flags().StringVarP(&bucketCreateFlags.name, "name", "n", "", "Name of bucket that will be created")
	bucketCreateCmd.Flags().DurationVarP(&bucketCreateFlags.retention, "retention", "r", 0, "Duration in nanoseconds data will live in bucket")
	bucketCreateCmd.Flags().StringVarP(&bucketCreateFlags.orgID, "org-id", "", "", "The ID of the organization that owns the bucket")
	bucketCreateCmd.MarkFlagRequired("name")

	bucketCmd.AddCommand(bucketCreateCmd)
}

func newBucketService(f Flags) (platform.BucketService, error) {
	if f.local {
		return newLocalKVService()
	}
	return &http.BucketService{
		Addr:               f.host,
		Token:              f.token,
		InsecureSkipVerify: f.skipVerify,
	}, nil
}

func bucketCreateF(cmd *cobra.Command, args []string) error {
	if bucketCreateFlags.orgID == "" {
		return fmt.Errorf("must specify org-id")
	}

	s, err := newBucketService(flags)
	if err != nil {
		return fmt.Errorf("failed to initialize bucket service client: %v", err)
	}

	b := &platform.Bucket{
		Name:            bucketCreateFlags.name,
		RetentionPeriod: bucketCreateFlags.retention,
	}

	if bucketCreateFlags.orgID != "" {
		id, err := platform.IDFromString(bucketCreateFlags.orgID)
		if err != nil {
			return fmt.Errorf("failed to decode org id %q: %v", bucketCreateFlags.orgID, err)
		}
		b.OrgID = *id
	}

	if err := s.CreateBucket(context.Background(), b); err != nil {
		return fmt.Errorf("failed to create bucket: %v", err)
	}

	w := internal.NewTabWriter(os.Stdout)
	w.WriteHeaders(
		"ID",
		"Name",
		"Retention",
		"OrganizationID",
	)
	w.Write(map[string]interface{}{
		"ID":             b.ID.String(),
		"Name":           b.Name,
		"Retention":      b.RetentionPeriod,
		"OrganizationID": b.OrgID.String(),
	})
	w.Flush()

	return nil
}

// BucketFindFlags define the Find Command
type BucketFindFlags struct {
	name  string
	id    string
	org   string
	orgID string
}

var bucketFindFlags BucketFindFlags

func init() {
	bucketFindCmd := &cobra.Command{
		Use:   "find",
		Short: "Find buckets",
		RunE:  wrapCheckSetup(bucketFindF),
	}

	bucketFindCmd.Flags().StringVarP(&bucketFindFlags.name, "name", "n", "", "The bucket name")
	bucketFindCmd.Flags().StringVarP(&bucketFindFlags.id, "id", "i", "", "The bucket ID")
	bucketFindCmd.Flags().StringVarP(&bucketFindFlags.orgID, "org-id", "", "", "The bucket organization ID")
	bucketFindCmd.Flags().StringVarP(&bucketFindFlags.org, "org", "o", "", "The bucket organization name")

	bucketCmd.AddCommand(bucketFindCmd)
}

func bucketFindF(cmd *cobra.Command, args []string) error {
	s, err := newBucketService(flags)
	if err != nil {
		return fmt.Errorf("failed to initialize bucket service client: %v", err)
	}

	filter := platform.BucketFilter{}
	if bucketFindFlags.name != "" {
		filter.Name = &bucketFindFlags.name
	}

	if bucketFindFlags.id != "" {
		id, err := platform.IDFromString(bucketFindFlags.id)
		if err != nil {
			return fmt.Errorf("failed to decode bucket id %q: %v", bucketFindFlags.id, err)
		}
		filter.ID = id
	}

	if bucketFindFlags.orgID != "" && bucketFindFlags.org != "" {
		return fmt.Errorf("must specify at exactly one of org and org-id")
	}

	if bucketFindFlags.orgID != "" {
		orgID, err := platform.IDFromString(bucketFindFlags.orgID)
		if err != nil {
			return fmt.Errorf("failed to decode org id %q: %v", bucketFindFlags.orgID, err)
		}
		filter.OrganizationID = orgID
	}

	if bucketFindFlags.org != "" {
		filter.Org = &bucketFindFlags.org
	}

	buckets, _, err := s.FindBuckets(context.Background(), filter)
	if err != nil {
		return fmt.Errorf("failed to retrieve buckets: %s", err)
	}

	w := internal.NewTabWriter(os.Stdout)
	w.WriteHeaders(
		"ID",
		"Name",
		"Retention",
		"OrganizationID",
	)
	for _, b := range buckets {
		w.Write(map[string]interface{}{
			"ID":             b.ID.String(),
			"Name":           b.Name,
			"Retention":      b.RetentionPeriod,
			"OrganizationID": b.OrgID.String(),
		})
	}
	w.Flush()

	return nil
}

// BucketUpdateFlags define the Update Command
type BucketUpdateFlags struct {
	id        string
	name      string
	retention time.Duration
}

var bucketUpdateFlags BucketUpdateFlags

func init() {
	bucketUpdateCmd := &cobra.Command{
		Use:   "update",
		Short: "Update bucket",
		RunE:  wrapCheckSetup(bucketUpdateF),
	}

	bucketUpdateCmd.Flags().StringVarP(&bucketUpdateFlags.id, "id", "i", "", "The bucket ID (required)")
	bucketUpdateCmd.Flags().StringVarP(&bucketUpdateFlags.name, "name", "n", "", "New bucket name")
	bucketUpdateCmd.Flags().DurationVarP(&bucketUpdateFlags.retention, "retention", "r", 0, "New duration data will live in bucket")
	bucketUpdateCmd.MarkFlagRequired("id")

	bucketCmd.AddCommand(bucketUpdateCmd)
}

func bucketUpdateF(cmd *cobra.Command, args []string) error {
	s, err := newBucketService(flags)
	if err != nil {
		return fmt.Errorf("failed to initialize bucket service client: %v", err)
	}

	var id platform.ID
	if err := id.DecodeFromString(bucketUpdateFlags.id); err != nil {
		return fmt.Errorf("failed to decode bucket id %q: %v", bucketUpdateFlags.id, err)
	}

	update := platform.BucketUpdate{}
	if bucketUpdateFlags.name != "" {
		update.Name = &bucketUpdateFlags.name
	}
	if bucketUpdateFlags.retention != 0 {
		update.RetentionPeriod = &bucketUpdateFlags.retention
	}

	b, err := s.UpdateBucket(context.Background(), id, update)
	if err != nil {
		return fmt.Errorf("failed to update bucket: %v", err)
	}

	w := internal.NewTabWriter(os.Stdout)
	w.WriteHeaders(
		"ID",
		"Name",
		"Retention",
		"OrganizationID",
	)
	w.Write(map[string]interface{}{
		"ID":             b.ID.String(),
		"Name":           b.Name,
		"Retention":      b.RetentionPeriod,
		"OrganizationID": b.OrgID.String(),
	})
	w.Flush()

	return nil
}

// BucketDeleteFlags define the Delete command
type BucketDeleteFlags struct {
	id string
}

var bucketDeleteFlags BucketDeleteFlags

func bucketDeleteF(cmd *cobra.Command, args []string) error {
	s, err := newBucketService(flags)
	if err != nil {
		return fmt.Errorf("failed to initialize bucket service client: %v", err)
	}

	var id platform.ID
	if err := id.DecodeFromString(bucketDeleteFlags.id); err != nil {
		return fmt.Errorf("failed to decode bucket id %q: %v", bucketDeleteFlags.id, err)
	}

	ctx := context.Background()
	b, err := s.FindBucketByID(ctx, id)
	if err != nil {
		return fmt.Errorf("failed to find bucket with id %q: %v", id, err)
	}

	if err = s.DeleteBucket(ctx, id); err != nil {
		return fmt.Errorf("failed to delete bucket with id %q: %v", id, err)
	}

	w := internal.NewTabWriter(os.Stdout)
	w.WriteHeaders(
		"ID",
		"Name",
		"Retention",
		"OrganizationID",
		"Deleted",
	)
	w.Write(map[string]interface{}{
		"ID":             b.ID.String(),
		"Name":           b.Name,
		"Retention":      b.RetentionPeriod,
		"OrganizationID": b.OrgID.String(),
		"Deleted":        true,
	})
	w.Flush()

	return nil
}

func init() {
	bucketDeleteCmd := &cobra.Command{
		Use:   "delete",
		Short: "Delete bucket",
		RunE:  wrapCheckSetup(bucketDeleteF),
	}

	bucketDeleteCmd.Flags().StringVarP(&bucketDeleteFlags.id, "id", "i", "", "The bucket ID (required)")
	bucketDeleteCmd.MarkFlagRequired("id")

	bucketCmd.AddCommand(bucketDeleteCmd)
}
<|endoftext|>


<commit_before>/*
 *  Copyright (c) 2011 NeuStar, Inc.
 *  All rights reserved.
 *
 *  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.
 *
 *  NeuStar, the Neustar logo and related names and logos are registered
 *  trademarks, service marks or tradenames of NeuStar, Inc. All other
 *  product names, company names, marks, logos and symbols may be trademarks
 *  of their respective owners.
 */

package kafka

import (
	"encoding/binary"
	"io"
	"log"
	"net"
	"time"
)

// BrokerConsumer holds a Kafka broker instance and the consumer settings
type BrokerConsumer struct {
	broker  *Broker
	offset  uint64
	maxSize uint32
	codecs  map[byte]PayloadCodec
}

// NewBrokerConsumer creates a new broker consumer
// * hostname - host and optionally port, delimited by ':'
// * topic to consume
// * partition to consume from
// * offset to start consuming from
// * maxSize (in bytes) of the message to consume (this should be at least as big as the biggest message to be published)
func NewBrokerConsumer(hostname string, topic string, partition int, offset uint64, maxSize uint32) *BrokerConsumer {
	return &BrokerConsumer{broker: newBroker(hostname, topic, partition),
		offset:  offset,
		maxSize: maxSize,
		codecs:  DefaultCodecsMap}
}

// NewBrokerOffsetConsumer creates a simplified consumer that defaults the offset and maxSize to 0.
// * hostname - host and optionally port, delimited by ':'
// * topic to consume
// * partition to consume from
func NewBrokerOffsetConsumer(hostname string, topic string, partition int) *BrokerConsumer {
	return &BrokerConsumer{broker: newBroker(hostname, topic, partition),
		offset:  0,
		maxSize: 0,
		codecs:  DefaultCodecsMap}
}

// AddCodecs is a utility method to add Custom Payload Codecs for Consumer Decoding
// payloadCodecs - an array of PayloadCodec implementations
func (consumer *BrokerConsumer) AddCodecs(payloadCodecs []PayloadCodec) {
	// merge to the default map, so one 'could' override the default codecs..
	for k, v := range codecsMap(payloadCodecs) {
		consumer.codecs[k] = v
	}
}

// ConsumeOnChannel fetches messages from kafka and enqueues them in a channel
func (consumer *BrokerConsumer) ConsumeOnChannel(msgChan chan *Message, pollTimeoutMs int64, quit chan struct{}) (int, error) {
	conn, err := consumer.broker.connect()
	if err != nil {
		return -1, err
	}

	forceQuit := make(chan struct{})

	num := 0
	done := make(chan bool, 1)
	go func() {
	Loop:
		for {
			_, err := consumer.consumeWithConn(conn, func(msg *Message) {
				//msg.Print()
				msgChan <- msg
				num++
			}, quit)

			if err != nil {
				if err != io.EOF && err.Error() != "use of closed network connection" { //
					log.Println("Fatal Error: ", err)
					panic(err)
				}
				close(forceQuit)
				break
			}

			// time.Sleep(time.Millisecond * time.Duration(pollTimeoutMs))
			select {
			case <-quit:
				log.Println("Kafka consumer - received request to stop")
				break Loop
			case <-time.After(time.Millisecond * time.Duration(pollTimeoutMs)):
				// carry on after the polling interval
			}
		}
		done <- true
	}()

	// wait to be told to stop...
	select {
	case <-forceQuit:
		// stopping from within this function
	case <-quit:
		// we were told to stop from outside
	}

	conn.Close()
	<-done
	close(msgChan)
	return num, err
}

// MessageHandlerFunc defines the interface for message handlers accepted by Consume()
type MessageHandlerFunc func(msg *Message)

// Consume makes a single fetch request and sends the messages in the message set to a handler function
func (consumer *BrokerConsumer) Consume(handlerFunc MessageHandlerFunc, stop <-chan struct{}) (int, error) {
	conn, err := consumer.broker.connect()
	if err != nil {
		return -1, err
	}
	defer conn.Close()

	num, err := consumer.consumeWithConn(conn, handlerFunc, stop)

	if err != nil {
		log.Println("Fatal Error: ", err)
	}

	return num, err
}

func (consumer *BrokerConsumer) consumeWithConn(conn *net.TCPConn, handlerFunc MessageHandlerFunc, stop <-chan struct{}) (int, error) {
	_, err := conn.Write(consumer.broker.EncodeConsumeRequest(consumer.offset, consumer.maxSize))
	if err != nil {
		log.Println("Failed kafka fetch request:", err.Error())
		return -1, err
	}

	length, payload, err := consumer.broker.readResponse(conn)
	//log.Println("kafka fetch request of", length, "bytes starting from offset", consumer.offset)

	if err != nil {
		return -1, err
	}

	num := 0
	if length > 2 {
		// parse out the messages
		currentOffset := uint64(0)
		for currentOffset <= uint64(length-4) {
			totalLength, msgs, err1 := Decode(payload[currentOffset:], consumer.codecs)
			if ErrIncompletePacket == err1 {
				// Reached the end of the current packet and the last message is incomplete.
				// Need a new Fetch Request from a newer offset, or a larger packet.
				break
			}
			msgOffset := consumer.offset + currentOffset
			for _, msg := range msgs {
				// do a non-blocking select to see whether we received a request to stop reading
				select {
				case <-stop:
					//fmt.Println("received request to stop whilst iterating message set")
					return num, err
				default:
					// update the offset of whole message set
					// multiple messages can be at the same offset (compressed for example)
					msg.offset = msgOffset
					handlerFunc(&msg)
					num++
				}

			}
			currentOffset += uint64(4 + totalLength)
		}
		// update the broker's offset for next consumption
		consumer.offset += currentOffset
	}

	return num, err
}

// GetOffsets returns a list of valid offsets (up to maxNumOffsets) before the given time, where
// time is in milliseconds (-1, from the latest offset available, -2 from the smallest offset available)
// The result is a list of offsets, in descending order.
func (consumer *BrokerConsumer) GetOffsets(time int64, maxNumOffsets uint32) ([]uint64, error) {
	var offsets []uint64

	conn, err := consumer.broker.connect()
	if err != nil {
		return offsets, err
	}

	defer conn.Close()

	_, err = conn.Write(consumer.broker.EncodeOffsetRequest(time, maxNumOffsets))
	if err != nil {
		return offsets, err
	}

	length, payload, err := consumer.broker.readResponse(conn)
	if err != nil {
		return offsets, err
	}

	if length > 4 {
		// get the number of offsets
		numOffsets := binary.BigEndian.Uint32(payload[0:])
		var currentOffset uint64 = 4
		for currentOffset < uint64(length-4) && uint32(len(offsets)) < numOffsets {
			offset := binary.BigEndian.Uint64(payload[currentOffset:])
			offsets = append(offsets, offset)
			currentOffset += 8 // offset size
		}
	}

	return offsets, err
}

// GetOffset returns the current offset for a broker.
func (consumer *BrokerConsumer) GetOffset() uint64 {
	return consumer.offset
}
<commit_msg>debug logging: print latest valid offset<commit_after>/*
 *  Copyright (c) 2011 NeuStar, Inc.
 *  All rights reserved.
 *
 *  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.
 *
 *  NeuStar, the Neustar logo and related names and logos are registered
 *  trademarks, service marks or tradenames of NeuStar, Inc. All other
 *  product names, company names, marks, logos and symbols may be trademarks
 *  of their respective owners.
 */

package kafka

import (
	"encoding/binary"
	"io"
	"log"
	"net"
	"time"
)

// BrokerConsumer holds a Kafka broker instance and the consumer settings
type BrokerConsumer struct {
	broker  *Broker
	offset  uint64
	maxSize uint32
	codecs  map[byte]PayloadCodec
}

// NewBrokerConsumer creates a new broker consumer
// * hostname - host and optionally port, delimited by ':'
// * topic to consume
// * partition to consume from
// * offset to start consuming from
// * maxSize (in bytes) of the message to consume (this should be at least as big as the biggest message to be published)
func NewBrokerConsumer(hostname string, topic string, partition int, offset uint64, maxSize uint32) *BrokerConsumer {
	return &BrokerConsumer{broker: newBroker(hostname, topic, partition),
		offset:  offset,
		maxSize: maxSize,
		codecs:  DefaultCodecsMap}
}

// NewBrokerOffsetConsumer creates a simplified consumer that defaults the offset and maxSize to 0.
// * hostname - host and optionally port, delimited by ':'
// * topic to consume
// * partition to consume from
func NewBrokerOffsetConsumer(hostname string, topic string, partition int) *BrokerConsumer {
	return &BrokerConsumer{broker: newBroker(hostname, topic, partition),
		offset:  0,
		maxSize: 0,
		codecs:  DefaultCodecsMap}
}

// AddCodecs is a utility method to add Custom Payload Codecs for Consumer Decoding
// payloadCodecs - an array of PayloadCodec implementations
func (consumer *BrokerConsumer) AddCodecs(payloadCodecs []PayloadCodec) {
	// merge to the default map, so one 'could' override the default codecs..
	for k, v := range codecsMap(payloadCodecs) {
		consumer.codecs[k] = v
	}
}

// ConsumeOnChannel fetches messages from kafka and enqueues them in a channel
func (consumer *BrokerConsumer) ConsumeOnChannel(msgChan chan *Message, pollTimeoutMs int64, quit chan struct{}) (int, error) {
	conn, err := consumer.broker.connect()
	if err != nil {
		return -1, err
	}

	forceQuit := make(chan struct{})

	num := 0
	done := make(chan bool, 1)
	go func() {
	Loop:
		for {
			_, err := consumer.consumeWithConn(conn, func(msg *Message) {
				//msg.Print()
				msgChan <- msg
				num++
			}, quit)

			if err != nil {
				if err != io.EOF && err.Error() != "use of closed network connection" { //
					log.Println("Fatal Error: ", err)
					panic(err)
				}
				close(forceQuit)
				break
			}

			// time.Sleep(time.Millisecond * time.Duration(pollTimeoutMs))
			select {
			case <-quit:
				log.Println("Kafka consumer - received request to stop")
				break Loop
			case <-time.After(time.Millisecond * time.Duration(pollTimeoutMs)):
				// carry on after the polling interval
			}
		}
		done <- true
	}()

	// wait to be told to stop...
	select {
	case <-forceQuit:
		// stopping from within this function
	case <-quit:
		// we were told to stop from outside
	}

	conn.Close()
	<-done
	close(msgChan)
	return num, err
}

// MessageHandlerFunc defines the interface for message handlers accepted by Consume()
type MessageHandlerFunc func(msg *Message)

// Consume makes a single fetch request and sends the messages in the message set to a handler function
func (consumer *BrokerConsumer) Consume(handlerFunc MessageHandlerFunc, stop <-chan struct{}) (int, error) {
	conn, err := consumer.broker.connect()
	if err != nil {
		return -1, err
	}
	defer conn.Close()

	num, err := consumer.consumeWithConn(conn, handlerFunc, stop)

	if err != nil {
		log.Println("Fatal Error: ", err)
	}

	return num, err
}

func (consumer *BrokerConsumer) consumeWithConn(conn *net.TCPConn, handlerFunc MessageHandlerFunc, stop <-chan struct{}) (int, error) {
	_, err := conn.Write(consumer.broker.EncodeConsumeRequest(consumer.offset, consumer.maxSize))
	if err != nil {
		log.Println("Failed kafka fetch request:", err.Error())
		return -1, err
	}

	length, payload, err := consumer.broker.readResponse(conn)
	//log.Println("kafka fetch request of", length, "bytes starting from offset", consumer.offset)

	if err != nil {
		return -1, err
	}

	num := 0
	if length > 2 {
		// parse out the messages
		currentOffset := uint64(0)
		for currentOffset <= uint64(length-4) {
			totalLength, msgs, err1 := Decode(payload[currentOffset:], consumer.codecs)
			if ErrIncompletePacket == err1 {
				// Reached the end of the current packet and the last message is incomplete.
				// Need a new Fetch Request from a newer offset, or a larger packet.
				log.Println("Incomplete message at offset %d", currentOffset)
				break
			}
			msgOffset := consumer.offset + currentOffset
			for _, msg := range msgs {
				// do a non-blocking select to see whether we received a request to stop reading
				select {
				case <-stop:
					//fmt.Println("received request to stop whilst iterating message set")
					return num, err
				default:
					// update the offset of whole message set
					// multiple messages can be at the same offset (compressed for example)
					msg.offset = msgOffset
					handlerFunc(&msg)
					num++
				}

			}
			currentOffset += uint64(4 + totalLength)
		}
		// update the broker's offset for next consumption
		consumer.offset += currentOffset
	}

	return num, err
}

// GetOffsets returns a list of valid offsets (up to maxNumOffsets) before the given time, where
// time is in milliseconds (-1, from the latest offset available, -2 from the smallest offset available)
// The result is a list of offsets, in descending order.
func (consumer *BrokerConsumer) GetOffsets(time int64, maxNumOffsets uint32) ([]uint64, error) {
	var offsets []uint64

	conn, err := consumer.broker.connect()
	if err != nil {
		return offsets, err
	}

	defer conn.Close()

	_, err = conn.Write(consumer.broker.EncodeOffsetRequest(time, maxNumOffsets))
	if err != nil {
		return offsets, err
	}

	length, payload, err := consumer.broker.readResponse(conn)
	if err != nil {
		return offsets, err
	}

	if length > 4 {
		// get the number of offsets
		numOffsets := binary.BigEndian.Uint32(payload[0:])
		var currentOffset uint64 = 4
		for currentOffset < uint64(length-4) && uint32(len(offsets)) < numOffsets {
			offset := binary.BigEndian.Uint64(payload[currentOffset:])
			offsets = append(offsets, offset)
			currentOffset += 8 // offset size
		}
	}

	return offsets, err
}

// GetOffset returns the current offset for a broker.
func (consumer *BrokerConsumer) GetOffset() uint64 {
	return consumer.offset
}
<|endoftext|>


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

//go:build loong64

package cpu

const CacheLinePadSize = 32

func doinit() {}
<commit_msg>internal/cpu: fix cpu cacheLineSize for loong64<commit_after>// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build loong64

package cpu

// CacheLinePadSize is used to prevent false sharing of cache lines.
// We choose 64 because Loongson 3A5000 the L1 Dcache is 4-way 256-line 64-byte-per-line.
const CacheLinePadSize = 64

func doinit() {}
<|endoftext|>


<commit_before>package metrics

import (
	"fmt"
	"time"

	"github.com/pachyderm/pachyderm/v2/src/client"
	"github.com/pachyderm/pachyderm/v2/src/enterprise"
	"github.com/pachyderm/pachyderm/v2/src/internal/config"
	"github.com/pachyderm/pachyderm/v2/src/internal/errors"
	"github.com/pachyderm/pachyderm/v2/src/internal/serviceenv"
	"github.com/pachyderm/pachyderm/v2/src/internal/uuid"
	"github.com/pachyderm/pachyderm/v2/src/pps"
	"github.com/pachyderm/pachyderm/v2/src/version"

	"golang.org/x/net/context"
	"google.golang.org/grpc/metadata"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	kube "k8s.io/client-go/kubernetes"
)

//Reporter is used to submit user & cluster metrics to segment
type Reporter struct {
	router    *router
	clusterID string
	env       *serviceenv.ServiceEnv
}

// NewReporter creates a new reporter and kicks off the loop to report cluster
// metrics
func NewReporter(clusterID string, env *serviceenv.ServiceEnv) *Reporter {
	var r *router
	if env.MetricsEndpoint != "" {
		r = newRouter(env.MetricsEndpoint)
	} else {
		r = newRouter()
	}
	reporter := &Reporter{
		router:    r,
		clusterID: clusterID,
		env:       env,
	}
	go reporter.reportClusterMetrics()
	return reporter
}

//ReportUserAction pushes the action into a queue for reporting,
// and reports the start, finish, and error conditions
func ReportUserAction(ctx context.Context, r *Reporter, action string) func(time.Time, error) {
	if r == nil {
		// This happens when stubbing out metrics for testing, e.g. src/server/pfs/server/server_test.go
		return func(time.Time, error) {}
	}
	// If we report nil, segment sees it, but mixpanel omits the field
	r.reportUserAction(ctx, fmt.Sprintf("%vStarted", action), 1)
	return func(start time.Time, err error) {
		if err == nil {
			r.reportUserAction(ctx, fmt.Sprintf("%vFinished", action), time.Since(start).Seconds())
		} else {
			r.reportUserAction(ctx, fmt.Sprintf("%vErrored", action), err.Error())
		}
	}
}

func getKeyFromMD(md metadata.MD, key string) (string, error) {
	if md[key] != nil && len(md[key]) > 0 {
		return md[key][0], nil
	}
	return "", errors.Errorf("error extracting userid from metadata. userid is empty")
}

func (r *Reporter) reportUserAction(ctx context.Context, action string, value interface{}) {
	md, ok := metadata.FromIncomingContext(ctx)
	if ok {
		// metadata API downcases all the key names
		userID, err := getKeyFromMD(md, "userid")
		if err != nil {
			// The FUSE client will never have a userID, so normal usage will produce a lot of these errors
			return
		}
		prefix, err := getKeyFromMD(md, "prefix")
		if err != nil {
			// metrics errors are non fatal
			return
		}
		r.router.reportUserMetricsToSegment(
			userID,
			prefix,
			action,
			value,
			r.clusterID,
		)
	}
}

// Helper method called by (Start|Finish)ReportAndFlushUserAction. Like those
// functions, it is used by the pachctl binary and runs on users' machines
func reportAndFlushUserAction(action string, value interface{}) func() {
	metricsDone := make(chan struct{})
	go func() {
		client := newSegmentClient()
		defer client.Close()
		cfg, _ := config.Read(false, false)
		if cfg == nil || cfg.UserID == "" || !cfg.V2.Metrics {
			return
		}
		reportUserMetricsToSegment(client, cfg.UserID, "user", action, value, "")
		close(metricsDone)
	}()
	return func() {
		select {
		case <-metricsDone:
			return
		case <-time.After(time.Second * 5):
			return
		}
	}
}

// StartReportAndFlushUserAction immediately reports the metric but does
// not block execution. It returns a wait function which waits or times
// out after 5s.
// It is used by the pachctl binary and runs on users' machines
func StartReportAndFlushUserAction(action string, value interface{}) func() {
	return reportAndFlushUserAction(fmt.Sprintf("%vStarted", action), value)
}

// FinishReportAndFlushUserAction immediately reports the metric but does
// not block execution. It returns a wait function which waits or times
// out after 5s.
// It is used by the pachctl binary and runs on users' machines
func FinishReportAndFlushUserAction(action string, err error, start time.Time) func() {
	var wait func()
	if err != nil {
		wait = reportAndFlushUserAction(fmt.Sprintf("%vErrored", action), err)
	} else {
		wait = reportAndFlushUserAction(fmt.Sprintf("%vFinished", action), time.Since(start).Seconds())
	}
	return wait
}

func (r *Reporter) reportClusterMetrics() {
	for {
		time.Sleep(reportingInterval)
		metrics := &Metrics{}
		internalMetrics(r.env.GetPachClient(context.Background()), metrics)
		externalMetrics(r.env.GetKubeClient(), metrics)
		metrics.ClusterID = r.clusterID
		metrics.PodID = uuid.NewWithoutDashes()
		metrics.Version = version.PrettyPrintVersion(version.Version)
		r.router.reportClusterMetricsToSegment(metrics)
	}
}

func externalMetrics(kubeClient *kube.Clientset, metrics *Metrics) error {
	nodeList, err := kubeClient.CoreV1().Nodes().List(metav1.ListOptions{})
	if err != nil {
		return errors.Wrapf(err, "externalMetrics: unable to retrieve node list from k8s")
	}
	metrics.Nodes = int64(len(nodeList.Items))
	return nil
}

func internalMetrics(pachClient *client.APIClient, metrics *Metrics) {

	// We should not return due to an error

	// Activation code
	enterpriseState, err := pachClient.Enterprise.GetState(pachClient.Ctx(), &enterprise.GetStateRequest{})
	if err == nil {
		metrics.ActivationCode = enterpriseState.ActivationCode
	}

	// Pipeline info
	resp, err := pachClient.PpsAPIClient.ListPipeline(pachClient.Ctx(), &pps.ListPipelineRequest{AllowIncomplete: true})
	if err == nil {
		metrics.Pipelines = int64(len(resp.PipelineInfo)) // Number of pipelines
		for _, pi := range resp.PipelineInfo {
			if pi.ParallelismSpec != nil {
				if metrics.MaxParallelism < pi.ParallelismSpec.Constant {
					metrics.MaxParallelism = pi.ParallelismSpec.Constant
				}
				if metrics.MinParallelism > pi.ParallelismSpec.Constant {
					metrics.MinParallelism = pi.ParallelismSpec.Constant
				}
				metrics.NumParallelism += 1
			}
			if pi.Egress != nil {
				metrics.CfgEgress += 1
			}
			if pi.JobCounts != nil {
				var cnt int64 = 0
				for _, c := range pi.JobCounts {
					cnt += int64(c)
				}
				if metrics.Jobs < cnt {
					metrics.Jobs = cnt
				}
			}
			if pi.ResourceRequests != nil {
				if pi.ResourceRequests.Cpu != 0 {
					metrics.ResourceCpuReq += pi.ResourceRequests.Cpu
					if metrics.ResourceCpuReqMax < pi.ResourceRequests.Cpu {
						metrics.ResourceCpuReqMax = pi.ResourceRequests.Cpu
					}
				}
				if pi.ResourceRequests.Memory != "" {
					metrics.ResourceMemReq += (pi.ResourceRequests.Memory + " ")
				}
				if pi.ResourceRequests.Gpu != nil {
					metrics.ResourceGpuReq += pi.ResourceRequests.Gpu.Number
					if metrics.ResourceGpuReqMax < pi.ResourceRequests.Gpu.Number {
						metrics.ResourceGpuReqMax = pi.ResourceRequests.Gpu.Number
					}
				}
				if pi.ResourceRequests.Disk != "" {
					metrics.ResourceDiskReq += (pi.ResourceRequests.Disk + " ")
				}
			}
			if pi.ResourceLimits != nil {
				if pi.ResourceLimits.Cpu != 0 {
					metrics.ResourceCpuLimit += pi.ResourceLimits.Cpu
					if metrics.ResourceCpuLimitMax < pi.ResourceLimits.Cpu {
						metrics.ResourceCpuLimitMax = pi.ResourceLimits.Cpu
					}
				}
				if pi.ResourceLimits.Memory != "" {
					metrics.ResourceMemLimit += (pi.ResourceLimits.Memory + " ")
				}
				if pi.ResourceLimits.Gpu != nil {
					metrics.ResourceGpuLimit += pi.ResourceLimits.Gpu.Number
					if metrics.ResourceGpuLimitMax < pi.ResourceLimits.Gpu.Number {
						metrics.ResourceGpuLimitMax = pi.ResourceLimits.Gpu.Number
					}
				}
				if pi.ResourceLimits.Disk != "" {
					metrics.ResourceDiskLimit += (pi.ResourceLimits.Disk + " ")
				}
			}
			if pi.Input != nil {
				if pi.Input.Pfs.OuterJoin {
					metrics.InputOuterJoin += 1
				}
				if pi.Input.Pfs.Lazy {
					metrics.InputLazy += 1
				}
				if pi.Input.Pfs.EmptyFiles {
					metrics.InputEmptyFiles += 1
				}
				if pi.Input.Pfs.S3 {
					metrics.InputS3 += 1
				}
				if pi.Input.Pfs.Trigger != nil {
					metrics.InputTrigger += 1
				}
				if pi.Input.Join != nil {
					metrics.InputJoin += 1
				}
				if pi.Input.Group != nil {
					metrics.InputGroup += 1
				}
				if pi.Input.Cross != nil {
					metrics.InputCross += 1
				}
				if pi.Input.Union != nil {
					metrics.InputUnion += 1
				}
				if pi.Input.Cron != nil {
					metrics.InputCron += 1
				}
				if pi.Input.Git != nil {
					metrics.InputGit += 1
				}
			}
			if pi.EnableStats {
				metrics.CfgStats += 1
			}
			if pi.Service != nil {
				metrics.CfgServices += 1
			}
			if pi.Spout != nil {
				metrics.PpsSpout += 1
				if pi.Spout.Service != nil {
					metrics.PpsSpoutService += 1
				}
			}
			if pi.Standby {
				metrics.CfgStandby += 1
			}
			if pi.S3Out {
				metrics.CfgS3Gateway += 1
			}
			if pi.Transform != nil {
				if pi.Transform.ErrCmd != nil {
					metrics.CfgErrcmd += 1
				}
				if pi.Transform.Build != nil {
					metrics.PpsBuild += 1
				}
			}
			if pi.TFJob != nil {
				metrics.CfgTfjob += 1
			}
		}
	}

	ris, err := pachClient.ListRepo()
	if err == nil {
		var sz, mbranch uint64 = 0, 0
		for _, ri := range ris {
			if (sz + ri.SizeBytes) < sz {
				sz = 0xFFFFFFFFFFFFFFFF
			} else {
				sz += ri.SizeBytes
			}
			if mbranch < uint64(len(ri.Branches)) {
				mbranch = uint64(len(ri.Branches))
			}
		}
		metrics.Repos = int64(len(ris))
		metrics.Bytes = sz
		metrics.MaxBranches = mbranch
	}
}
<commit_msg>Fix lint<commit_after>package metrics

import (
	"fmt"
	"time"

	"github.com/pachyderm/pachyderm/v2/src/client"
	"github.com/pachyderm/pachyderm/v2/src/enterprise"
	"github.com/pachyderm/pachyderm/v2/src/internal/config"
	"github.com/pachyderm/pachyderm/v2/src/internal/errors"
	"github.com/pachyderm/pachyderm/v2/src/internal/serviceenv"
	"github.com/pachyderm/pachyderm/v2/src/internal/uuid"
	"github.com/pachyderm/pachyderm/v2/src/pps"
	"github.com/pachyderm/pachyderm/v2/src/version"

	"golang.org/x/net/context"
	"google.golang.org/grpc/metadata"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	kube "k8s.io/client-go/kubernetes"
)

//Reporter is used to submit user & cluster metrics to segment
type Reporter struct {
	router    *router
	clusterID string
	env       *serviceenv.ServiceEnv
}

// NewReporter creates a new reporter and kicks off the loop to report cluster
// metrics
func NewReporter(clusterID string, env *serviceenv.ServiceEnv) *Reporter {
	var r *router
	if env.MetricsEndpoint != "" {
		r = newRouter(env.MetricsEndpoint)
	} else {
		r = newRouter()
	}
	reporter := &Reporter{
		router:    r,
		clusterID: clusterID,
		env:       env,
	}
	go reporter.reportClusterMetrics()
	return reporter
}

//ReportUserAction pushes the action into a queue for reporting,
// and reports the start, finish, and error conditions
func ReportUserAction(ctx context.Context, r *Reporter, action string) func(time.Time, error) {
	if r == nil {
		// This happens when stubbing out metrics for testing, e.g. src/server/pfs/server/server_test.go
		return func(time.Time, error) {}
	}
	// If we report nil, segment sees it, but mixpanel omits the field
	r.reportUserAction(ctx, fmt.Sprintf("%vStarted", action), 1)
	return func(start time.Time, err error) {
		if err == nil {
			r.reportUserAction(ctx, fmt.Sprintf("%vFinished", action), time.Since(start).Seconds())
		} else {
			r.reportUserAction(ctx, fmt.Sprintf("%vErrored", action), err.Error())
		}
	}
}

func getKeyFromMD(md metadata.MD, key string) (string, error) {
	if md[key] != nil && len(md[key]) > 0 {
		return md[key][0], nil
	}
	return "", errors.Errorf("error extracting userid from metadata. userid is empty")
}

func (r *Reporter) reportUserAction(ctx context.Context, action string, value interface{}) {
	md, ok := metadata.FromIncomingContext(ctx)
	if ok {
		// metadata API downcases all the key names
		userID, err := getKeyFromMD(md, "userid")
		if err != nil {
			// The FUSE client will never have a userID, so normal usage will produce a lot of these errors
			return
		}
		prefix, err := getKeyFromMD(md, "prefix")
		if err != nil {
			// metrics errors are non fatal
			return
		}
		r.router.reportUserMetricsToSegment(
			userID,
			prefix,
			action,
			value,
			r.clusterID,
		)
	}
}

// Helper method called by (Start|Finish)ReportAndFlushUserAction. Like those
// functions, it is used by the pachctl binary and runs on users' machines
func reportAndFlushUserAction(action string, value interface{}) func() {
	metricsDone := make(chan struct{})
	go func() {
		client := newSegmentClient()
		defer client.Close()
		cfg, _ := config.Read(false, false)
		if cfg == nil || cfg.UserID == "" || !cfg.V2.Metrics {
			return
		}
		reportUserMetricsToSegment(client, cfg.UserID, "user", action, value, "")
		close(metricsDone)
	}()
	return func() {
		select {
		case <-metricsDone:
			return
		case <-time.After(time.Second * 5):
			return
		}
	}
}

// StartReportAndFlushUserAction immediately reports the metric but does
// not block execution. It returns a wait function which waits or times
// out after 5s.
// It is used by the pachctl binary and runs on users' machines
func StartReportAndFlushUserAction(action string, value interface{}) func() {
	return reportAndFlushUserAction(fmt.Sprintf("%vStarted", action), value)
}

// FinishReportAndFlushUserAction immediately reports the metric but does
// not block execution. It returns a wait function which waits or times
// out after 5s.
// It is used by the pachctl binary and runs on users' machines
func FinishReportAndFlushUserAction(action string, err error, start time.Time) func() {
	var wait func()
	if err != nil {
		wait = reportAndFlushUserAction(fmt.Sprintf("%vErrored", action), err)
	} else {
		wait = reportAndFlushUserAction(fmt.Sprintf("%vFinished", action), time.Since(start).Seconds())
	}
	return wait
}

func (r *Reporter) reportClusterMetrics() {
	for {
		time.Sleep(reportingInterval)
		metrics := &Metrics{}
		internalMetrics(r.env.GetPachClient(context.Background()), metrics)
		externalMetrics(r.env.GetKubeClient(), metrics)
		metrics.ClusterID = r.clusterID
		metrics.PodID = uuid.NewWithoutDashes()
		metrics.Version = version.PrettyPrintVersion(version.Version)
		r.router.reportClusterMetricsToSegment(metrics)
	}
}

func externalMetrics(kubeClient *kube.Clientset, metrics *Metrics) error {
	nodeList, err := kubeClient.CoreV1().Nodes().List(metav1.ListOptions{})
	if err != nil {
		return errors.Wrapf(err, "externalMetrics: unable to retrieve node list from k8s")
	}
	metrics.Nodes = int64(len(nodeList.Items))
	return nil
}

func internalMetrics(pachClient *client.APIClient, metrics *Metrics) {

	// We should not return due to an error

	// Activation code
	enterpriseState, err := pachClient.Enterprise.GetState(pachClient.Ctx(), &enterprise.GetStateRequest{})
	if err == nil {
		metrics.ActivationCode = enterpriseState.ActivationCode
	}

	// Pipeline info
	resp, err := pachClient.PpsAPIClient.ListPipeline(pachClient.Ctx(), &pps.ListPipelineRequest{AllowIncomplete: true})
	if err == nil {
		metrics.Pipelines = int64(len(resp.PipelineInfo)) // Number of pipelines
		for _, pi := range resp.PipelineInfo {
			if pi.ParallelismSpec != nil {
				if metrics.MaxParallelism < pi.ParallelismSpec.Constant {
					metrics.MaxParallelism = pi.ParallelismSpec.Constant
				}
				if metrics.MinParallelism > pi.ParallelismSpec.Constant {
					metrics.MinParallelism = pi.ParallelismSpec.Constant
				}
				metrics.NumParallelism++
			}
			if pi.Egress != nil {
				metrics.CfgEgress++
			}
			if pi.JobCounts != nil {
				var cnt int64 = 0
				for _, c := range pi.JobCounts {
					cnt += int64(c)
				}
				if metrics.Jobs < cnt {
					metrics.Jobs = cnt
				}
			}
			if pi.ResourceRequests != nil {
				if pi.ResourceRequests.Cpu != 0 {
					metrics.ResourceCpuReq += pi.ResourceRequests.Cpu
					if metrics.ResourceCpuReqMax < pi.ResourceRequests.Cpu {
						metrics.ResourceCpuReqMax = pi.ResourceRequests.Cpu
					}
				}
				if pi.ResourceRequests.Memory != "" {
					metrics.ResourceMemReq += (pi.ResourceRequests.Memory + " ")
				}
				if pi.ResourceRequests.Gpu != nil {
					metrics.ResourceGpuReq += pi.ResourceRequests.Gpu.Number
					if metrics.ResourceGpuReqMax < pi.ResourceRequests.Gpu.Number {
						metrics.ResourceGpuReqMax = pi.ResourceRequests.Gpu.Number
					}
				}
				if pi.ResourceRequests.Disk != "" {
					metrics.ResourceDiskReq += (pi.ResourceRequests.Disk + " ")
				}
			}
			if pi.ResourceLimits != nil {
				if pi.ResourceLimits.Cpu != 0 {
					metrics.ResourceCpuLimit += pi.ResourceLimits.Cpu
					if metrics.ResourceCpuLimitMax < pi.ResourceLimits.Cpu {
						metrics.ResourceCpuLimitMax = pi.ResourceLimits.Cpu
					}
				}
				if pi.ResourceLimits.Memory != "" {
					metrics.ResourceMemLimit += (pi.ResourceLimits.Memory + " ")
				}
				if pi.ResourceLimits.Gpu != nil {
					metrics.ResourceGpuLimit += pi.ResourceLimits.Gpu.Number
					if metrics.ResourceGpuLimitMax < pi.ResourceLimits.Gpu.Number {
						metrics.ResourceGpuLimitMax = pi.ResourceLimits.Gpu.Number
					}
				}
				if pi.ResourceLimits.Disk != "" {
					metrics.ResourceDiskLimit += (pi.ResourceLimits.Disk + " ")
				}
			}
			if pi.Input != nil {
				if pi.Input.Pfs.OuterJoin {
					metrics.InputOuterJoin++
				}
				if pi.Input.Pfs.Lazy {
					metrics.InputLazy++
				}
				if pi.Input.Pfs.EmptyFiles {
					metrics.InputEmptyFiles++
				}
				if pi.Input.Pfs.S3 {
					metrics.InputS3++
				}
				if pi.Input.Pfs.Trigger != nil {
					metrics.InputTrigger++
				}
				if pi.Input.Join != nil {
					metrics.InputJoin++
				}
				if pi.Input.Group != nil {
					metrics.InputGroup++
				}
				if pi.Input.Cross != nil {
					metrics.InputCross++
				}
				if pi.Input.Union != nil {
					metrics.InputUnion++
				}
				if pi.Input.Cron != nil {
					metrics.InputCron++
				}
				if pi.Input.Git != nil {
					metrics.InputGit++
				}
			}
			if pi.EnableStats {
				metrics.CfgStats++
			}
			if pi.Service != nil {
				metrics.CfgServices++
			}
			if pi.Spout != nil {
				metrics.PpsSpout++
				if pi.Spout.Service != nil {
					metrics.PpsSpoutService++
				}
			}
			if pi.Standby {
				metrics.CfgStandby++
			}
			if pi.S3Out {
				metrics.CfgS3Gateway++
			}
			if pi.Transform != nil {
				if pi.Transform.ErrCmd != nil {
					metrics.CfgErrcmd++
				}
				if pi.Transform.Build != nil {
					metrics.PpsBuild++
				}
			}
			if pi.TFJob != nil {
				metrics.CfgTfjob++
			}
		}
	}

	ris, err := pachClient.ListRepo()
	if err == nil {
		var sz, mbranch uint64 = 0, 0
		for _, ri := range ris {
			if (sz + ri.SizeBytes) < sz {
				sz = 0xFFFFFFFFFFFFFFFF
			} else {
				sz += ri.SizeBytes
			}
			if mbranch < uint64(len(ri.Branches)) {
				mbranch = uint64(len(ri.Branches))
			}
		}
		metrics.Repos = int64(len(ris))
		metrics.Bytes = sz
		metrics.MaxBranches = mbranch
	}
}
<|endoftext|>


<commit_before>package fastly

import (
	"fmt"
	"sort"
)

// Dictionary represents a dictionary response from the Fastly API.
type Dictionary struct {
	ServiceID string `mapstructure:"service_id"`
	Version   string `mapstructure:"version"`

	ID      string `mapstructure:"id"`
	Name    string `mapstructure:"name"`
	Address string `mapstructure:"address"`
}

// dictionariesByName is a sortable list of dictionaries.
type dictionariesByName []*Dictionary

// Len, Swap, and Less implement the sortable interface.
func (s dictionariesByName) Len() int      { return len(s) }
func (s dictionariesByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s dictionariesByName) Less(i, j int) bool {
	return s[i].Name < s[j].Name
}

// ListDictionariesInput is used as input to the ListDictionaries function.
type ListDictionariesInput struct {
	// Service is the ID of the service (required).
	Service string

	// Version is the specific configuration version (required).
	Version string
}

// ListDictionaries returns the list of dictionaries for the configuration version.
func (c *Client) ListDictionaries(i *ListDictionariesInput) ([]*Dictionary, error) {
	if i.Service == "" {
		return nil, ErrMissingService
	}

	if i.Version == "" {
		return nil, ErrMissingVersion
	}

	path := fmt.Sprintf("/service/%s/version/%s/dictionary", i.Service, i.Version)
	resp, err := c.Get(path, nil)
	if err != nil {
		return nil, err
	}

	var bs []*Dictionary
	if err := decodeJSON(&bs, resp.Body); err != nil {
		return nil, err
	}
	sort.Stable(dictionariesByName(bs))
	return bs, nil
}

// CreateDictionaryInput is used as input to the CreateDictionary function.
type CreateDictionaryInput struct {
	// Service is the ID of the service. Version is the specific configuration
	// version. Both fields are required.
	Service string
	Version string

	Name string `form:"name,omitempty"`
}

// CreateDictionary creates a new Fastly dictionary.
func (c *Client) CreateDictionary(i *CreateDictionaryInput) (*Dictionary, error) {
	if i.Service == "" {
		return nil, ErrMissingService
	}

	if i.Version == "" {
		return nil, ErrMissingVersion
	}

	path := fmt.Sprintf("/service/%s/version/%s/dictionary", i.Service, i.Version)
	resp, err := c.PostForm(path, i, nil)
	if err != nil {
		return nil, err
	}

	var b *Dictionary
	if err := decodeJSON(&b, resp.Body); err != nil {
		return nil, err
	}
	return b, nil
}

// GetDictionaryInput is used as input to the GetDictionary function.
type GetDictionaryInput struct {
	// Service is the ID of the service. Version is the specific configuration
	// version. Both fields are required.
	Service string
	Version string

	// Name is the name of the dictionary to fetch.
	Name string
}

// GetDictionary gets the dictionary configuration with the given parameters.
func (c *Client) GetDictionary(i *GetDictionaryInput) (*Dictionary, error) {
	if i.Service == "" {
		return nil, ErrMissingService
	}

	if i.Version == "" {
		return nil, ErrMissingVersion
	}

	if i.Name == "" {
		return nil, ErrMissingName
	}

	path := fmt.Sprintf("/service/%s/version/%s/dictionary/%s", i.Service, i.Version, i.Name)
	resp, err := c.Get(path, nil)
	if err != nil {
		return nil, err
	}

	var b *Dictionary
	if err := decodeJSON(&b, resp.Body); err != nil {
		return nil, err
	}
	return b, nil
}

// UpdateDictionaryInput is used as input to the UpdateDictionary function.
type UpdateDictionaryInput struct {
	// Service is the ID of the service. Version is the specific configuration
	// version. Both fields are required.
	Service string
	Version string

	// Name is the name of the dictionary to update.
	Name string

	NewName string `form:"name,omitempty"`
}

// UpdateDictionary updates a specific dictionary.
func (c *Client) UpdateDictionary(i *UpdateDictionaryInput) (*Dictionary, error) {
	if i.Service == "" {
		return nil, ErrMissingService
	}

	if i.Version == "" {
		return nil, ErrMissingVersion
	}

	if i.Name == "" {
		return nil, ErrMissingName
	}

	path := fmt.Sprintf("/service/%s/version/%s/dictionary/%s", i.Service, i.Version, i.Name)
	resp, err := c.PutForm(path, i, nil)
	if err != nil {
		return nil, err
	}

	var b *Dictionary
	if err := decodeJSON(&b, resp.Body); err != nil {
		return nil, err
	}
	return b, nil
}

// DeleteDictionaryInput is the input parameter to DeleteDictionary.
type DeleteDictionaryInput struct {
	// Service is the ID of the service. Version is the specific configuration
	// version. Both fields are required.
	Service string
	Version string

	// Name is the name of the dictionary to delete (required).
	Name string
}

// DeleteDictionary deletes the given dictionary version.
func (c *Client) DeleteDictionary(i *DeleteDictionaryInput) error {
	if i.Service == "" {
		return ErrMissingService
	}

	if i.Version == "" {
		return ErrMissingVersion
	}

	if i.Name == "" {
		return ErrMissingName
	}

	path := fmt.Sprintf("/service/%s/version/%s/dictionary/%s", i.Service, i.Version, i.Name)
	_, err := c.Delete(path, nil)
	if err != nil {
		return err
	}

	// Unlike other endpoints, the dictionary endpoint does not return a status
	// response - it just returns a 200 OK.
	return nil
}
<commit_msg>Add Created and Updated fields to Dictionary.<commit_after>package fastly

import (
	"fmt"
	"sort"
)

// Dictionary represents a dictionary response from the Fastly API.
type Dictionary struct {
	ServiceID string `mapstructure:"service_id"`
	Version   string `mapstructure:"version"`

	ID      string `mapstructure:"id"`
	Name    string `mapstructure:"name"`
	Address string `mapstructure:"address"`
	Created string `mapstructure:"created_at"`
	Updated string `mapstructure:"updated_at"`
}

// dictionariesByName is a sortable list of dictionaries.
type dictionariesByName []*Dictionary

// Len, Swap, and Less implement the sortable interface.
func (s dictionariesByName) Len() int      { return len(s) }
func (s dictionariesByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s dictionariesByName) Less(i, j int) bool {
	return s[i].Name < s[j].Name
}

// ListDictionariesInput is used as input to the ListDictionaries function.
type ListDictionariesInput struct {
	// Service is the ID of the service (required).
	Service string

	// Version is the specific configuration version (required).
	Version string
}

// ListDictionaries returns the list of dictionaries for the configuration version.
func (c *Client) ListDictionaries(i *ListDictionariesInput) ([]*Dictionary, error) {
	if i.Service == "" {
		return nil, ErrMissingService
	}

	if i.Version == "" {
		return nil, ErrMissingVersion
	}

	path := fmt.Sprintf("/service/%s/version/%s/dictionary", i.Service, i.Version)
	resp, err := c.Get(path, nil)
	if err != nil {
		return nil, err
	}

	var bs []*Dictionary
	if err := decodeJSON(&bs, resp.Body); err != nil {
		return nil, err
	}
	sort.Stable(dictionariesByName(bs))
	return bs, nil
}

// CreateDictionaryInput is used as input to the CreateDictionary function.
type CreateDictionaryInput struct {
	// Service is the ID of the service. Version is the specific configuration
	// version. Both fields are required.
	Service string
	Version string

	Name string `form:"name,omitempty"`
}

// CreateDictionary creates a new Fastly dictionary.
func (c *Client) CreateDictionary(i *CreateDictionaryInput) (*Dictionary, error) {
	if i.Service == "" {
		return nil, ErrMissingService
	}

	if i.Version == "" {
		return nil, ErrMissingVersion
	}

	path := fmt.Sprintf("/service/%s/version/%s/dictionary", i.Service, i.Version)
	resp, err := c.PostForm(path, i, nil)
	if err != nil {
		return nil, err
	}

	var b *Dictionary
	if err := decodeJSON(&b, resp.Body); err != nil {
		return nil, err
	}
	return b, nil
}

// GetDictionaryInput is used as input to the GetDictionary function.
type GetDictionaryInput struct {
	// Service is the ID of the service. Version is the specific configuration
	// version. Both fields are required.
	Service string
	Version string

	// Name is the name of the dictionary to fetch.
	Name string
}

// GetDictionary gets the dictionary configuration with the given parameters.
func (c *Client) GetDictionary(i *GetDictionaryInput) (*Dictionary, error) {
	if i.Service == "" {
		return nil, ErrMissingService
	}

	if i.Version == "" {
		return nil, ErrMissingVersion
	}

	if i.Name == "" {
		return nil, ErrMissingName
	}

	path := fmt.Sprintf("/service/%s/version/%s/dictionary/%s", i.Service, i.Version, i.Name)
	resp, err := c.Get(path, nil)
	if err != nil {
		return nil, err
	}

	var b *Dictionary
	if err := decodeJSON(&b, resp.Body); err != nil {
		return nil, err
	}
	return b, nil
}

// UpdateDictionaryInput is used as input to the UpdateDictionary function.
type UpdateDictionaryInput struct {
	// Service is the ID of the service. Version is the specific configuration
	// version. Both fields are required.
	Service string
	Version string

	// Name is the name of the dictionary to update.
	Name string

	NewName string `form:"name,omitempty"`
}

// UpdateDictionary updates a specific dictionary.
func (c *Client) UpdateDictionary(i *UpdateDictionaryInput) (*Dictionary, error) {
	if i.Service == "" {
		return nil, ErrMissingService
	}

	if i.Version == "" {
		return nil, ErrMissingVersion
	}

	if i.Name == "" {
		return nil, ErrMissingName
	}

	path := fmt.Sprintf("/service/%s/version/%s/dictionary/%s", i.Service, i.Version, i.Name)
	resp, err := c.PutForm(path, i, nil)
	if err != nil {
		return nil, err
	}

	var b *Dictionary
	if err := decodeJSON(&b, resp.Body); err != nil {
		return nil, err
	}
	return b, nil
}

// DeleteDictionaryInput is the input parameter to DeleteDictionary.
type DeleteDictionaryInput struct {
	// Service is the ID of the service. Version is the specific configuration
	// version. Both fields are required.
	Service string
	Version string

	// Name is the name of the dictionary to delete (required).
	Name string
}

// DeleteDictionary deletes the given dictionary version.
func (c *Client) DeleteDictionary(i *DeleteDictionaryInput) error {
	if i.Service == "" {
		return ErrMissingService
	}

	if i.Version == "" {
		return ErrMissingVersion
	}

	if i.Name == "" {
		return ErrMissingName
	}

	path := fmt.Sprintf("/service/%s/version/%s/dictionary/%s", i.Service, i.Version, i.Name)
	_, err := c.Delete(path, nil)
	if err != nil {
		return err
	}

	// Unlike other endpoints, the dictionary endpoint does not return a status
	// response - it just returns a 200 OK.
	return nil
}
<|endoftext|>


<commit_before>// Copyright (c) 2015 Jesse Meek <https://github.com/waigani>
// This program is Free Software see LICENSE file for details.

package diffparser

import (
	"regexp"
	"strconv"
	"strings"

	"errors"
)

type FileMode int

const (
	DELETED FileMode = iota
	MODIFIED
	NEW
)

type diffRange struct {

	// starting line number
	Start int

	// the number of lines the change diffHunk applies to
	Length int

	// Each line of the hunk range.
	Lines []*DiffLine
}

type DiffLineMode rune

const (
	ADDED DiffLineMode = iota
	REMOVED
	UNCHANGED
)

type DiffLine struct {
	Mode     DiffLineMode
	Number   int
	Content  string
	Position int // the line in the diff
}

type DiffHunk struct {
	HunkHeader string
	OrigRange  diffRange
	NewRange   diffRange
	WholeRange diffRange
}

type DiffFile struct {
	DiffHeader string
	Mode       FileMode
	OrigName   string
	NewName    string
	Hunks      []*DiffHunk
}

type Diff struct {
	Files []*DiffFile
	Raw   string `sql:"type:text"`

	PullID uint `sql:"index"`
}

func (d *Diff) addFile(file *DiffFile) {
	d.Files = append(d.Files, file)
}

// Changed returns a map of filename to lines changed in that file. Deleted
// files are ignored.
func (d *Diff) Changed() map[string][]int {
	dFiles := make(map[string][]int)

	for _, f := range d.Files {
		if f.Mode == DELETED {
			continue
		}

		for _, h := range f.Hunks {
			for _, dl := range h.NewRange.Lines {
				if dl.Mode == ADDED { // TODO(waigani) return removed
					dFiles[f.NewName] = append(dFiles[f.NewName], dl.Number)
				}
			}
		}
	}

	return dFiles
}

func regFind(s string, reg string, group int) string {
	re := regexp.MustCompile(reg)
	return re.FindStringSubmatch(s)[group]
}

func lineMode(line string) (*DiffLineMode, error) {
	var m DiffLineMode
	switch line[:1] {
	case " ":
		m = UNCHANGED
	case "+":
		m = ADDED
	case "-":
		m = REMOVED
	default:
		return nil, errors.New("could not parse line mode for line: \"" + line + "\"")
	}
	return &m, nil
}

// Parse takes a diff, such as produced by "git diff", and parses it into a
// Diff struct.
func Parse(diffString string) (*Diff, error) {
	var diff Diff
	diff.Raw = diffString
	lines := strings.Split(diffString, "\n")

	var file *DiffFile
	var hunk *DiffHunk
	var ADDEDCount int
	var REMOVEDCount int
	var inHunk bool
	oldFilePrefix := "--- a/"
	newFilePrefix := "+++ b/"

	var diffPosCount int
	var firstHunkInFile bool
	// Parse each line of diff.
	for idx, l := range lines {
		diffPosCount++
		switch {
		case strings.HasPrefix(l, "diff "):
			inHunk = false

			// Start a new file.
			file = &DiffFile{}
			header := l
			if len(lines) > idx+3 {
				rein := regexp.MustCompile(`^index .+$`)
				remp := regexp.MustCompile(`^(-|\+){3} .+$`)
				index := lines[idx+1]
				if rein.MatchString(index) {
					header = header + "\n" + index
				}
				mp1 := lines[idx+2]
				mp2 := lines[idx+3]
				if remp.MatchString(mp1) && remp.MatchString(mp2) {
					header = header + "\n" + mp1 + "\n" + mp2
				}
			}
			file.DiffHeader = header
			diff.Files = append(diff.Files, file)
			firstHunkInFile = true

			// File mode.
			file.Mode = MODIFIED
		case l == "+++ /dev/null":
			file.Mode = DELETED
		case l == "--- /dev/null":
			file.Mode = NEW
		case strings.HasPrefix(l, oldFilePrefix):
			file.OrigName = strings.TrimPrefix(l, oldFilePrefix)
		case strings.HasPrefix(l, newFilePrefix):
			file.NewName = strings.TrimPrefix(l, newFilePrefix)
		case strings.HasPrefix(l, "@@ "):
			if firstHunkInFile {
				diffPosCount = 0
				firstHunkInFile = false
			}

			inHunk = true
			// Start new hunk.
			hunk = &DiffHunk{}
			file.Hunks = append(file.Hunks, hunk)

			// Parse hunk heading for ranges
			re := regexp.MustCompile(`@@ \-(\d+),?(\d+)? \+(\d+),?(\d+)? @@ ?(.+)?`)
			m := re.FindStringSubmatch(l)
			if len(m) < 5 {
				return nil, errors.New("Error parsing line: " + l)
			}
			a, err := strconv.Atoi(m[1])
			if err != nil {
				return nil, err
			}
			b := a
			if len(m[2]) > 0 {
				b, err = strconv.Atoi(m[2])
				if err != nil {
					return nil, err
				}
			}
			c, err := strconv.Atoi(m[3])
			if err != nil {
				return nil, err
			}
			d := c
			if len(m[4]) > 0 {
				d, err = strconv.Atoi(m[4])
				if err != nil {
					return nil, err
				}
			}
			if len(m[5]) > 0 {
				hunk.HunkHeader = m[5]
			}

			// hunk orig range.
			hunk.OrigRange = diffRange{
				Start:  a,
				Length: b,
			}

			// hunk new range.
			hunk.NewRange = diffRange{
				Start:  c,
				Length: d,
			}

			// (re)set line counts
			ADDEDCount = hunk.NewRange.Start
			REMOVEDCount = hunk.OrigRange.Start
		case inHunk && isSourceLine(l):
			m, err := lineMode(l)
			if err != nil {
				return nil, err
			}
			line := DiffLine{
				Mode:     *m,
				Content:  l[1:],
				Position: diffPosCount,
			}
			newLine := line
			origLine := line

			// add lines to ranges
			switch *m {
			case ADDED:
				newLine.Number = ADDEDCount
				hunk.NewRange.Lines = append(hunk.NewRange.Lines, &newLine)
				hunk.WholeRange.Lines = append(hunk.WholeRange.Lines, &newLine)
				ADDEDCount++

			case REMOVED:
				origLine.Number = REMOVEDCount
				hunk.OrigRange.Lines = append(hunk.OrigRange.Lines, &origLine)
				hunk.WholeRange.Lines = append(hunk.WholeRange.Lines, &origLine)
				REMOVEDCount++

			case UNCHANGED:
				newLine.Number = ADDEDCount
				hunk.NewRange.Lines = append(hunk.NewRange.Lines, &newLine)
				hunk.WholeRange.Lines = append(hunk.WholeRange.Lines, &newLine)
				origLine.Number = REMOVEDCount
				hunk.OrigRange.Lines = append(hunk.OrigRange.Lines, &origLine)
				ADDEDCount++
				REMOVEDCount++
			}
		}
	}

	return &diff, nil
}

func isSourceLine(line string) bool {
	if line == `\ No newline at end of file` {
		return false
	}
	if l := len(line); l == 0 || (l >= 3 && (line[:3] == "---" || line[:3] == "+++")) {
		return false
	}
	return true
}

func (hunk *DiffHunk) Length() int {
	return len(hunk.WholeRange.Lines) + 1
}
<commit_msg>add documentation as comments<commit_after>// Copyright (c) 2015 Jesse Meek <https://github.com/waigani>
// This program is Free Software see LICENSE file for details.

package diffparser

import (
	"regexp"
	"strconv"
	"strings"

	"errors"
)

// FileMode represents the file status in a diff
type FileMode int

const (
	// DELETED if the file is deleted
	DELETED FileMode = iota
	// MODIFIED if the file is modified
	MODIFIED
	// NEW if the file is created and there is no diff
	NEW
)

type diffRange struct {

	// starting line number
	Start int

	// the number of lines the change diffHunk applies to
	Length int

	// Each line of the hunk range.
	Lines []*DiffLine
}

// DiffLineMode tells the line if added, removed or unchanged
type DiffLineMode rune

const (
	// ADDED if the line is added (shown green in diff)
	ADDED DiffLineMode = iota
	// REMOVED if the line is deleted (shown red in diff)
	REMOVED
	// UNCHANGED if the line is unchanged (not colored in diff)
	UNCHANGED
)

// DiffLine is the least part of an actual diff
type DiffLine struct {
	Mode     DiffLineMode
	Number   int
	Content  string
	Position int // the line in the diff
}

// DiffHunk is a group of difflines
type DiffHunk struct {
	HunkHeader string
	OrigRange  diffRange
	NewRange   diffRange
	WholeRange diffRange
}

// DiffFile is the sum of diffhunks and holds the changes of the file features
type DiffFile struct {
	DiffHeader string
	Mode       FileMode
	OrigName   string
	NewName    string
	Hunks      []*DiffHunk
}

//Diff is the collection of DiffFiles
type Diff struct {
	Files []*DiffFile
	Raw   string `sql:"type:text"`

	PullID uint `sql:"index"`
}

func (d *Diff) addFile(file *DiffFile) {
	d.Files = append(d.Files, file)
}

// Changed returns a map of filename to lines changed in that file. Deleted
// files are ignored.
func (d *Diff) Changed() map[string][]int {
	dFiles := make(map[string][]int)

	for _, f := range d.Files {
		if f.Mode == DELETED {
			continue
		}

		for _, h := range f.Hunks {
			for _, dl := range h.NewRange.Lines {
				if dl.Mode == ADDED { // TODO(waigani) return removed
					dFiles[f.NewName] = append(dFiles[f.NewName], dl.Number)
				}
			}
		}
	}

	return dFiles
}

func regFind(s string, reg string, group int) string {
	re := regexp.MustCompile(reg)
	return re.FindStringSubmatch(s)[group]
}

func lineMode(line string) (*DiffLineMode, error) {
	var m DiffLineMode
	switch line[:1] {
	case " ":
		m = UNCHANGED
	case "+":
		m = ADDED
	case "-":
		m = REMOVED
	default:
		return nil, errors.New("could not parse line mode for line: \"" + line + "\"")
	}
	return &m, nil
}

// Parse takes a diff, such as produced by "git diff", and parses it into a
// Diff struct.
func Parse(diffString string) (*Diff, error) {
	var diff Diff
	diff.Raw = diffString
	lines := strings.Split(diffString, "\n")

	var file *DiffFile
	var hunk *DiffHunk
	var ADDEDCount int
	var REMOVEDCount int
	var inHunk bool
	oldFilePrefix := "--- a/"
	newFilePrefix := "+++ b/"

	var diffPosCount int
	var firstHunkInFile bool
	// Parse each line of diff.
	for idx, l := range lines {
		diffPosCount++
		switch {
		case strings.HasPrefix(l, "diff "):
			inHunk = false

			// Start a new file.
			file = &DiffFile{}
			header := l
			if len(lines) > idx+3 {
				rein := regexp.MustCompile(`^index .+$`)
				remp := regexp.MustCompile(`^(-|\+){3} .+$`)
				index := lines[idx+1]
				if rein.MatchString(index) {
					header = header + "\n" + index
				}
				mp1 := lines[idx+2]
				mp2 := lines[idx+3]
				if remp.MatchString(mp1) && remp.MatchString(mp2) {
					header = header + "\n" + mp1 + "\n" + mp2
				}
			}
			file.DiffHeader = header
			diff.Files = append(diff.Files, file)
			firstHunkInFile = true

			// File mode.
			file.Mode = MODIFIED
		case l == "+++ /dev/null":
			file.Mode = DELETED
		case l == "--- /dev/null":
			file.Mode = NEW
		case strings.HasPrefix(l, oldFilePrefix):
			file.OrigName = strings.TrimPrefix(l, oldFilePrefix)
		case strings.HasPrefix(l, newFilePrefix):
			file.NewName = strings.TrimPrefix(l, newFilePrefix)
		case strings.HasPrefix(l, "@@ "):
			if firstHunkInFile {
				diffPosCount = 0
				firstHunkInFile = false
			}

			inHunk = true
			// Start new hunk.
			hunk = &DiffHunk{}
			file.Hunks = append(file.Hunks, hunk)

			// Parse hunk heading for ranges
			re := regexp.MustCompile(`@@ \-(\d+),?(\d+)? \+(\d+),?(\d+)? @@ ?(.+)?`)
			m := re.FindStringSubmatch(l)
			if len(m) < 5 {
				return nil, errors.New("Error parsing line: " + l)
			}
			a, err := strconv.Atoi(m[1])
			if err != nil {
				return nil, err
			}
			b := a
			if len(m[2]) > 0 {
				b, err = strconv.Atoi(m[2])
				if err != nil {
					return nil, err
				}
			}
			c, err := strconv.Atoi(m[3])
			if err != nil {
				return nil, err
			}
			d := c
			if len(m[4]) > 0 {
				d, err = strconv.Atoi(m[4])
				if err != nil {
					return nil, err
				}
			}
			if len(m[5]) > 0 {
				hunk.HunkHeader = m[5]
			}

			// hunk orig range.
			hunk.OrigRange = diffRange{
				Start:  a,
				Length: b,
			}

			// hunk new range.
			hunk.NewRange = diffRange{
				Start:  c,
				Length: d,
			}

			// (re)set line counts
			ADDEDCount = hunk.NewRange.Start
			REMOVEDCount = hunk.OrigRange.Start
		case inHunk && isSourceLine(l):
			m, err := lineMode(l)
			if err != nil {
				return nil, err
			}
			line := DiffLine{
				Mode:     *m,
				Content:  l[1:],
				Position: diffPosCount,
			}
			newLine := line
			origLine := line

			// add lines to ranges
			switch *m {
			case ADDED:
				newLine.Number = ADDEDCount
				hunk.NewRange.Lines = append(hunk.NewRange.Lines, &newLine)
				hunk.WholeRange.Lines = append(hunk.WholeRange.Lines, &newLine)
				ADDEDCount++

			case REMOVED:
				origLine.Number = REMOVEDCount
				hunk.OrigRange.Lines = append(hunk.OrigRange.Lines, &origLine)
				hunk.WholeRange.Lines = append(hunk.WholeRange.Lines, &origLine)
				REMOVEDCount++

			case UNCHANGED:
				newLine.Number = ADDEDCount
				hunk.NewRange.Lines = append(hunk.NewRange.Lines, &newLine)
				hunk.WholeRange.Lines = append(hunk.WholeRange.Lines, &newLine)
				origLine.Number = REMOVEDCount
				hunk.OrigRange.Lines = append(hunk.OrigRange.Lines, &origLine)
				ADDEDCount++
				REMOVEDCount++
			}
		}
	}

	return &diff, nil
}

func isSourceLine(line string) bool {
	if line == `\ No newline at end of file` {
		return false
	}
	if l := len(line); l == 0 || (l >= 3 && (line[:3] == "---" || line[:3] == "+++")) {
		return false
	}
	return true
}

// Length returns the hunks line length
func (hunk *DiffHunk) Length() int {
	return len(hunk.WholeRange.Lines) + 1
}
<|endoftext|>


<commit_before>package chef

import "fmt"

// CookbookService  is the service for interacting with chef server cookbooks endpoint
type CookbookService struct {
	client *Client
}

// CookbookItem represents a object of cookbook file data
type CookbookItem struct {
	Url         string `json:"url,omitempty"`
	Path        string `json:"path,omitempty"`
	Name        string `json:"name,omitempty"`
	Checksum    string `json:"checksum,omitempty"`
	Specificity string `json:"specificity,omitempty"`
}

// CookbookListResult is the summary info returned by chef-api when listing
// http://docs.opscode.com/api_chef_server.html#cookbooks
type CookbookListResult map[string]CookbookVersions

// CookbookVersions is the data container returned from the chef server when listing all cookbooks
type CookbookVersions struct {
	Url      string            `json:"url,omitempty"`
	Versions []CookbookVersion `json:"versions,omitempty"`
}

// CookbookVersion is the data for a specific cookbook version
type CookbookVersion struct {
	Url     string `json:"url,omitempty"`
	Version string `json:"version,omitempty"`
}

// CookbookMeta represents a Golang version of cookbook metadata
type CookbookMeta struct {
	Name            string                 `json:"cookbook_name,omitempty"`
	Version         string                 `json:"version,omitempty"`
	Description     string                 `json:"description,omitempty"`
	LongDescription string                 `json:"long_description,omitempty"`
	Maintainer      string                 `json:"maintainer,omitempty"`
	MaintainerEmail string                 `json:"maintainer_email,omitempty"`
	License         string                 `json:"license,omitempty"`
	Platforms       map[string]string      `json:"platforms,omitempty"`
	Depends         map[string]string      `json:"dependencies,omitempty"`
	Reccomends      map[string]string      `json:"recommendations,omitempty"`
	Suggests        map[string]string      `json:"suggestions,omitempty"`
	Conflicts       map[string]string      `json:"conflicting,omitempty"`
	Provides        map[string]string      `json:"providing,omitempty"`
	Replaces        map[string]string      `json:"replacing,omitempty"`
	attributes      map[string]interface{} `json:"attributes,omitempty"` // this has a format as well that could be typed, but blargh https://github.com/lob/chef/blob/master/cookbooks/apache2/metadata.json
	groupings       map[string]interface{} `json:"groupings,omitempty"`  // never actually seen this used.. looks like it should be map[string]map[string]string, but not sure http://docs.opscode.com/essentials_cookbook_metadata.html
	recipes         map[string]string      `json:"recipes,omitempty"`
}

// Cookbook represents the native Go version of the deserialized api cookbook
type Cookbook struct {
	CookbookName string         `json:"cookbook_name"`
	Name         string         `json:"name"`
	Version      string         `json:"version,omitempty"`
	ChefType     string         `json:"chef_type,omitempty"`
	Frozen       bool           `json:"frozen?,omitempty"`
	JsonClass    string         `json:"json_class,omitempty"`
	Files        []CookbookItem `json:"files,omitempty"`
	Templates    []CookbookItem `json:"Templates,omitempty"`
	Attributes   []CookbookItem `json:"attributes,omitempty"`
	Recipes      []CookbookItem `json:"recipes,omitempty"`
	Definitions  []CookbookItem `json:"definitions,omitempty"`
	Libraries    []CookbookItem `json:"libraries,omitempty"`
	Providers    []CookbookItem `json:"Providers,omitempty"`
	Resources    []CookbookItem `json:"Resources,omitempty"`
	RootFiles    []CookbookItem `json:"Templates,omitempty"`
	Metadata     CookbookMeta   `json:"Metadata,omitempty"`
}

// String makes CookbookListResult implement the string result
func (c CookbookListResult) String() (out string) {
	for k, v := range c {
		out += fmt.Sprintf("%s => %s\n", k, v.Url)
		for _, i := range v.Versions {
			out += fmt.Sprintf(" * %s\n", i.Version)
		}
	}
	return out
}

// versionParams assembles a querystring for the chef api's  num_versions
// This is used to restrict the number of versions returned in the reponse
func versionParams(path, numVersions string) string {
	if numVersions == "0" {
		numVersions = "all"
	}

	// need to optionally add numVersion args to the request
	if len(numVersions) > 0 {
		path = fmt.Sprintf("%s?num_versions=%s", path, numVersions)
	}
	return path
}

// Get retruns a CookbookVersion for a specific cookbook
//  GET /cookbooks/name
func (c *CookbookService) Get(name string) (data CookbookVersion, err error) {
	path := fmt.Sprintf("cookbooks/%s", name)
	err = c.client.magicRequestDecoder("GET", path, nil, &data)
	return
}

// GetAvailable returns the versions of a coookbook available on a server
func (c *CookbookService) GetAvailableVersions(name, numVersions string) (data CookbookListResult, err error) {
	path := versionParams(fmt.Sprintf("cookbooks/%s", name), numVersions)
	err = c.client.magicRequestDecoder("GET", path, nil, &data)
	return
}

// GetVersion fetches a specific version of a cookbooks data from the server api
//   GET /cookbook/foo/1.2.3
//   GET /cookbook/foo/_latest
//   Chef API docs: http://docs.opscode.com/api_chef_server.html#id5
func (c *CookbookService) GetVersion(name, version string) (data Cookbook, err error) {
	url := fmt.Sprintf("cookbooks/%s/%s", name, version)
	c.client.magicRequestDecoder("GET", url, nil, &data)
	return
}

// ListVersions lists the cookbooks available on the server limited to numVersions
//   Chef API docs: http://docs.opscode.com/api_chef_server.html#id2
func (c *CookbookService) ListAvailableVersions(numVersions string) (data CookbookListResult, err error) {
	path := versionParams("cookbooks", numVersions)
	err = c.client.magicRequestDecoder("GET", path, nil, &data)
	return
}

// List returns a CookbookListResult with the latest versions of cookbooks available on the server
func (c *CookbookService) List() (CookbookListResult, error) {
	return c.ListAvailableVersions("")
}

// DeleteVersion removes a version of a cook from a server
func (c *CookbookService) Delete(name, version string) (err error) {
	path := fmt.Sprintf("cookbooks/%s", name)
	err = c.client.magicRequestDecoder("DELETE", path, nil, nil)
	return
}
<commit_msg>fix non exported fields in coolbook.go<commit_after>package chef

import "fmt"

// CookbookService  is the service for interacting with chef server cookbooks endpoint
type CookbookService struct {
	client *Client
}

// CookbookItem represents a object of cookbook file data
type CookbookItem struct {
	Url         string `json:"url,omitempty"`
	Path        string `json:"path,omitempty"`
	Name        string `json:"name,omitempty"`
	Checksum    string `json:"checksum,omitempty"`
	Specificity string `json:"specificity,omitempty"`
}

// CookbookListResult is the summary info returned by chef-api when listing
// http://docs.opscode.com/api_chef_server.html#cookbooks
type CookbookListResult map[string]CookbookVersions

// CookbookVersions is the data container returned from the chef server when listing all cookbooks
type CookbookVersions struct {
	Url      string            `json:"url,omitempty"`
	Versions []CookbookVersion `json:"versions,omitempty"`
}

// CookbookVersion is the data for a specific cookbook version
type CookbookVersion struct {
	Url     string `json:"url,omitempty"`
	Version string `json:"version,omitempty"`
}

// CookbookMeta represents a Golang version of cookbook metadata
type CookbookMeta struct {
	Name            string                 `json:"cookbook_name,omitempty"`
	Version         string                 `json:"version,omitempty"`
	Description     string                 `json:"description,omitempty"`
	LongDescription string                 `json:"long_description,omitempty"`
	Maintainer      string                 `json:"maintainer,omitempty"`
	MaintainerEmail string                 `json:"maintainer_email,omitempty"`
	License         string                 `json:"license,omitempty"`
	Platforms       map[string]string      `json:"platforms,omitempty"`
	Depends         map[string]string      `json:"dependencies,omitempty"`
	Reccomends      map[string]string      `json:"recommendations,omitempty"`
	Suggests        map[string]string      `json:"suggestions,omitempty"`
	Conflicts       map[string]string      `json:"conflicting,omitempty"`
	Provides        map[string]string      `json:"providing,omitempty"`
	Replaces        map[string]string      `json:"replacing,omitempty"`
	Attributes      map[string]interface{} `json:"attributes,omitempty"` // this has a format as well that could be typed, but blargh https://github.com/lob/chef/blob/master/cookbooks/apache2/metadata.json
	Groupings       map[string]interface{} `json:"groupings,omitempty"`  // never actually seen this used.. looks like it should be map[string]map[string]string, but not sure http://docs.opscode.com/essentials_cookbook_metadata.html
	Recipes         map[string]string      `json:"recipes,omitempty"`
}

// Cookbook represents the native Go version of the deserialized api cookbook
type Cookbook struct {
	CookbookName string         `json:"cookbook_name"`
	Name         string         `json:"name"`
	Version      string         `json:"version,omitempty"`
	ChefType     string         `json:"chef_type,omitempty"`
	Frozen       bool           `json:"frozen?,omitempty"`
	JsonClass    string         `json:"json_class,omitempty"`
	Files        []CookbookItem `json:"files,omitempty"`
	Templates    []CookbookItem `json:"Templates,omitempty"`
	Attributes   []CookbookItem `json:"attributes,omitempty"`
	Recipes      []CookbookItem `json:"recipes,omitempty"`
	Definitions  []CookbookItem `json:"definitions,omitempty"`
	Libraries    []CookbookItem `json:"libraries,omitempty"`
	Providers    []CookbookItem `json:"Providers,omitempty"`
	Resources    []CookbookItem `json:"Resources,omitempty"`
	RootFiles    []CookbookItem `json:"Templates,omitempty"`
	Metadata     CookbookMeta   `json:"Metadata,omitempty"`
}

// String makes CookbookListResult implement the string result
func (c CookbookListResult) String() (out string) {
	for k, v := range c {
		out += fmt.Sprintf("%s => %s\n", k, v.Url)
		for _, i := range v.Versions {
			out += fmt.Sprintf(" * %s\n", i.Version)
		}
	}
	return out
}

// versionParams assembles a querystring for the chef api's  num_versions
// This is used to restrict the number of versions returned in the reponse
func versionParams(path, numVersions string) string {
	if numVersions == "0" {
		numVersions = "all"
	}

	// need to optionally add numVersion args to the request
	if len(numVersions) > 0 {
		path = fmt.Sprintf("%s?num_versions=%s", path, numVersions)
	}
	return path
}

// Get retruns a CookbookVersion for a specific cookbook
//  GET /cookbooks/name
func (c *CookbookService) Get(name string) (data CookbookVersion, err error) {
	path := fmt.Sprintf("cookbooks/%s", name)
	err = c.client.magicRequestDecoder("GET", path, nil, &data)
	return
}

// GetAvailable returns the versions of a coookbook available on a server
func (c *CookbookService) GetAvailableVersions(name, numVersions string) (data CookbookListResult, err error) {
	path := versionParams(fmt.Sprintf("cookbooks/%s", name), numVersions)
	err = c.client.magicRequestDecoder("GET", path, nil, &data)
	return
}

// GetVersion fetches a specific version of a cookbooks data from the server api
//   GET /cookbook/foo/1.2.3
//   GET /cookbook/foo/_latest
//   Chef API docs: http://docs.opscode.com/api_chef_server.html#id5
func (c *CookbookService) GetVersion(name, version string) (data Cookbook, err error) {
	url := fmt.Sprintf("cookbooks/%s/%s", name, version)
	c.client.magicRequestDecoder("GET", url, nil, &data)
	return
}

// ListVersions lists the cookbooks available on the server limited to numVersions
//   Chef API docs: http://docs.opscode.com/api_chef_server.html#id2
func (c *CookbookService) ListAvailableVersions(numVersions string) (data CookbookListResult, err error) {
	path := versionParams("cookbooks", numVersions)
	err = c.client.magicRequestDecoder("GET", path, nil, &data)
	return
}

// List returns a CookbookListResult with the latest versions of cookbooks available on the server
func (c *CookbookService) List() (CookbookListResult, error) {
	return c.ListAvailableVersions("")
}

// DeleteVersion removes a version of a cook from a server
func (c *CookbookService) Delete(name, version string) (err error) {
	path := fmt.Sprintf("cookbooks/%s", name)
	err = c.client.magicRequestDecoder("DELETE", path, nil, nil)
	return
}
<|endoftext|>