<commit_before>package main

import (
	"bytes"
	"encoding/hex"
	"flag"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/aws/aws-sdk-go/service/s3/s3manager"

	"golang.org/x/net/context"
	"google.golang.org/cloud/storage"

	"github.com/pivotal-golang/bytefmt"
)

var (
	awsProfile   = flag.String("awsProfile", "", "aws profile")
	s3Bucket     = flag.String("s3Bucket", "", "s3 bucket")
	s3Prefix     = flag.String("s3Prefix", "", "s3 prefix")
	localDir     = flag.String("localDir", "", "local directory")
	gcpProjectId = flag.String("gcpProjectId", "", "gcp project id")
	gsBucket     = flag.String("gsBucket", "", "gs bucket")
	dryRun       = flag.Bool("dryRun", false, "dry run")
)

type Exit struct{ Code int }

// exit code handler
// http://stackoverflow.com/a/27630092/1881379
func handleExit() {
	if e := recover(); e != nil {
		if exit, ok := e.(Exit); ok == true {
			os.Exit(exit.Code)
		}
		panic(e) // not an Exit, bubble up
	}
	os.Exit(0)
}

// timing
// https://coderwall.com/p/cp5fya/measuring-execution-time-in-go
func timeTrack(start time.Time, name string) {
	elapsed := time.Since(start)
	log.Printf("%s took %s", name, elapsed)
}

func writeToGS(file *os.File, w *storage.Writer) error {
	var content io.Reader
	content, err := os.Open(file.Name())
	if err != nil {
		log.Fatal(err)
		return err
	}
	const maxSlurp = 1 << 20
	var buf bytes.Buffer
	n, err := io.CopyN(&buf, content, maxSlurp)
	if err != nil && err != io.EOF {
		log.Fatalf("Error reading from stdin: %v, %v", n, err)
		return err
	}
	w.ContentType = http.DetectContentType(buf.Bytes())
	_, err = io.Copy(w, io.MultiReader(&buf, content))
	if cerr := w.Close(); cerr != nil && err == nil {
		err = cerr
	}
	if err != nil {
		log.Fatalf("Write error: %v", err)
		return err
	}
	return nil
}

func main() {
	defer handleExit()
	defer timeTrack(time.Now(), "S3toGS")

	flag.Parse()

	// Set up AWS clients
	awsSession := session.New(&aws.Config{
		Region:      aws.String("us-east-1"),
		Credentials: credentials.NewSharedCredentials("", *awsProfile),
	})
	s3Client := s3.New(awsSession)
	s3Downloader := s3manager.NewDownloader(awsSession)

	// Set up GCP clients
	gcpContext := context.Background()
	gsClient, err := storage.NewClient(gcpContext)
	if err != nil {
		log.Fatal(err)
		panic(Exit{1})
	}
	defer gsClient.Close()

	// S3 List
	s3List, err := s3Client.ListObjectsV2(&s3.ListObjectsV2Input{
		Bucket: aws.String(*s3Bucket),
		Prefix: aws.String(*s3Prefix),
	})
	if err != nil {
		log.Fatal(err)
		panic(Exit{1})
	}

	amtTransferred := uint64(0)

	for _, key := range s3List.Contents {
		gsAttrs, gsErr := gsClient.Bucket(*gsBucket).Object(*key.Key).Attrs(gcpContext)

		s3MD5 := strings.Replace(*key.ETag, "\"", "", -1)
		s3Size := *key.Size

		localFilepath := filepath.Join(*localDir, filepath.Base(*key.Key))

		if gsErr != nil || // doesn't exist in GS
			!strings.EqualFold(s3MD5, hex.EncodeToString(gsAttrs.MD5)) ||
			s3Size != gsAttrs.Size {

			if gsErr == nil && strings.EqualFold(s3MD5, hex.EncodeToString(gsAttrs.MD5)) {
				fmt.Println("Hash matches, skipping", *key.Key)
			} else if gsErr == nil && s3Size == gsAttrs.Size {
				fmt.Println("Size matches, skipping", *key.Key)
			} else if *dryRun {
				fmt.Println("Would download/upload", *key.Key)
			} else {
				amtTransferred += uint64(s3Size)

				// Create local file path and file
				err := os.MkdirAll(filepath.Dir(localFilepath), 0777)
				if err != nil {
					log.Fatal("Failed to create dirs", err)
					panic(Exit{1})
				}
				file, err := os.Create(localFilepath)
				if err != nil {
					log.Fatal("Failed to create file", err)
					panic(Exit{1})
				}
				defer file.Close()

				// Download from S3
				fmt.Println("Downloading from S3", *key.Key, "to", localFilepath)
				s3Downloader.Download(file,
					&s3.GetObjectInput{
						Bucket: aws.String(*s3Bucket),
						Key:    aws.String(*key.Key),
					})

				// Upload to GS
				// https://github.com/golang/build/blob/master/cmd/upload/upload.go
				fmt.Println("Uploading", localFilepath, "to GS at", *key.Key)
				w := gsClient.Bucket(*gsBucket).Object(*key.Key).NewWriter(gcpContext)
				writeToGS(file, w)

				// Delete local file
				fmt.Println("Removing", file.Name())
				os.Remove(file.Name())
			}
		} else {
			fmt.Println("Already in GS, skipping", *key.Key)
		}
	}

	fmt.Println("Amount transferred", bytefmt.ByteSize(amtTransferred))
}
<commit_msg>Check size after upload<commit_after>package main

import (
	"bytes"
	"encoding/hex"
	"flag"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/aws/aws-sdk-go/service/s3/s3manager"

	"golang.org/x/net/context"
	"google.golang.org/cloud/storage"

	"github.com/pivotal-golang/bytefmt"
)

var (
	awsProfile   = flag.String("awsProfile", "", "aws profile")
	s3Bucket     = flag.String("s3Bucket", "", "s3 bucket")
	s3Prefix     = flag.String("s3Prefix", "", "s3 prefix")
	localDir     = flag.String("localDir", "", "local directory")
	gcpProjectId = flag.String("gcpProjectId", "", "gcp project id")
	gsBucket     = flag.String("gsBucket", "", "gs bucket")
	dryRun       = flag.Bool("dryRun", false, "dry run")
)

type Exit struct{ Code int }

// exit code handler
// http://stackoverflow.com/a/27630092/1881379
func handleExit() {
	if e := recover(); e != nil {
		if exit, ok := e.(Exit); ok == true {
			os.Exit(exit.Code)
		}
		panic(e) // not an Exit, bubble up
	}
	os.Exit(0)
}

// timing
// https://coderwall.com/p/cp5fya/measuring-execution-time-in-go
func timeTrack(start time.Time, name string) {
	elapsed := time.Since(start)
	log.Printf("%s took %s", name, elapsed)
}

func writeToGS(file *os.File, w *storage.Writer) error {
	var content io.Reader
	content, err := os.Open(file.Name())
	if err != nil {
		log.Fatal(err)
		return err
	}
	const maxSlurp = 1 << 20
	var buf bytes.Buffer
	n, err := io.CopyN(&buf, content, maxSlurp)
	if err != nil && err != io.EOF {
		log.Fatalf("Error reading from stdin: %v, %v", n, err)
		return err
	}
	w.ContentType = http.DetectContentType(buf.Bytes())
	_, err = io.Copy(w, io.MultiReader(&buf, content))
	if cerr := w.Close(); cerr != nil && err == nil {
		err = cerr
	}
	if err != nil {
		log.Fatalf("Write error: %v", err)
		return err
	}
	return nil
}

func main() {
	defer handleExit()
	defer timeTrack(time.Now(), "S3toGS")

	flag.Parse()

	// Set up AWS clients
	awsSession := session.New(&aws.Config{
		Region:      aws.String("us-east-1"),
		Credentials: credentials.NewSharedCredentials("", *awsProfile),
	})
	s3Client := s3.New(awsSession)
	s3Downloader := s3manager.NewDownloader(awsSession)

	// Set up GCP clients
	gcpContext := context.Background()
	gsClient, err := storage.NewClient(gcpContext)
	if err != nil {
		log.Fatal(err)
		panic(Exit{1})
	}
	defer gsClient.Close()

	// S3 List
	s3List, err := s3Client.ListObjectsV2(&s3.ListObjectsV2Input{
		Bucket: aws.String(*s3Bucket),
		Prefix: aws.String(*s3Prefix),
	})
	if err != nil {
		log.Fatal(err)
		panic(Exit{1})
	}

	amtTransferred := uint64(0)

	for _, key := range s3List.Contents {
		gsAttrs, gsErr := gsClient.Bucket(*gsBucket).Object(*key.Key).Attrs(gcpContext)

		s3MD5 := strings.Replace(*key.ETag, "\"", "", -1)
		s3Size := *key.Size

		localFilepath := filepath.Join(*localDir, filepath.Base(*key.Key))

		if gsErr != nil || // doesn't exist in GS
			!strings.EqualFold(s3MD5, hex.EncodeToString(gsAttrs.MD5)) ||
			s3Size != gsAttrs.Size {

			if gsErr == nil && strings.EqualFold(s3MD5, hex.EncodeToString(gsAttrs.MD5)) {
				fmt.Println("Hash matches, skipping", *key.Key)
			} else if gsErr == nil && s3Size == gsAttrs.Size {
				fmt.Println("Size matches, skipping", *key.Key)
			} else if *dryRun {
				fmt.Println("Would download/upload", *key.Key)
			} else {
				amtTransferred += uint64(s3Size)

				// Create local file path and file
				err := os.MkdirAll(filepath.Dir(localFilepath), 0777)
				if err != nil {
					log.Fatal("Failed to create dirs", err)
					panic(Exit{1})
				}
				file, err := os.Create(localFilepath)
				if err != nil {
					log.Fatal("Failed to create file", err)
					panic(Exit{1})
				}
				defer file.Close()

				// Download from S3
				fmt.Println("Downloading from S3", *key.Key, "to", localFilepath)
				s3Downloader.Download(file,
					&s3.GetObjectInput{
						Bucket: aws.String(*s3Bucket),
						Key:    aws.String(*key.Key),
					})

				// Upload to GS
				// https://github.com/golang/build/blob/master/cmd/upload/upload.go
				fmt.Println("Uploading", localFilepath, "to GS at", *key.Key)
				w := gsClient.Bucket(*gsBucket).Object(*key.Key).NewWriter(gcpContext)
				writeToGS(file, w)

				// Delete local file
				fmt.Println("Removing", file.Name())
				os.Remove(file.Name())

				gsAttrs, gsErr := gsClient.Bucket(*gsBucket).Object(*key.Key).Attrs(gcpContext)
				if gsErr != nil || s3Size != gsAttrs.Size {
					log.Fatal("Upload failed")
					panic(Exit{1})
				}
			}
		} else {
			fmt.Println("Already in GS, skipping", *key.Key)
		}
	}

	fmt.Println("Amount transferred", bytefmt.ByteSize(amtTransferred))
}
<|endoftext|>


<commit_before>package cmd

import (
	"fmt"
	ap "github.com/hivdb/nucamino/alignmentprofile"
	"github.com/hivdb/nucamino/alignmentprofile/builtin"
	"github.com/hivdb/nucamino/cli"
	"github.com/pkg/profile"
	"github.com/spf13/cobra"
	"strings"
)

// The cobra cli library will populate these variables with values
// provided as command line flags.
var alignInputFilename, alignOutputFilename, alignOutputFormat string
var alignQuiet, alignPprof bool
var alignGoroutines int

func init() {
	rootCmd.AddCommand(alignCmd)

	alignCmd.Flags().StringVarP(
		&alignInputFilename,
		"input-file",
		"i",
		"-",
		"input file",
	)
	alignCmd.Flags().StringVarP(
		&alignOutputFilename,
		"output-file",
		"o",
		"-",
		"output File",
	)
	alignCmd.Flags().StringVarP(
		&alignOutputFormat,
		"output-format",
		"f",
		"tsv",
		"output format. (options: \"tsv\", \"json\")",
	)
	alignCmd.Flags().BoolVarP(
		&alignQuiet,
		"quiet",
		"q",
		false,
		"hide non-error output message",
	)
	alignCmd.Flags().BoolVarP(
		&alignPprof,
		"pprof",
		"p",
		false,
		"save profiling information",
	)
	alignCmd.Flags().IntVar(
		&alignGoroutines,
		"goroutines",
		0,
		"number of goroutines the aligner will use. (default: number of CPUs)",
	)
}

// Check that a gene-name is in a list of GEnes
func geneInGenes(geneArg string, genes []ap.Gene) bool {
	for _, g := range genes {
		if g.Matches(geneArg) {
			return true
		}
	}
	return false
}

func alignGetParameters(args []string) (*ap.AlignmentProfile, []string, error) {

	profileName := args[0]
	profile, found := builtin.Get(profileName)
	if !found {
		tmpl := `Unknown profile name: '%v'

See 'nucamino profile list' for a list of available profiles`
		err := fmt.Errorf(tmpl, profileName)
		return nil, nil, err
	}

	genes := strings.Split(args[1], ",")
	profileGenes := profile.Genes()
	for _, gene := range genes {
		if !geneInGenes(gene, profileGenes) {
			tmpl := "%v is not an available gene in the profile %v (available genes: %v)"
			err := fmt.Errorf(tmpl, gene, profileName, profileGenes)
			return nil, nil, err
		}
	}

	return profile, genes, nil
}

func alignRun(cmd *cobra.Command, args []string) error {
	if alignPprof {
		defer profile.Start(profile.CPUProfile).Stop()
	}
	profile, genes, err := alignGetParameters(args)
	if err != nil {
		return err
	}
	return cli.PerformAlignment(
		alignInputFilename,
		alignOutputFilename,
		alignOutputFormat,
		genes,
		alignGoroutines,
		alignQuiet,
		*profile,
	)
}

var alignLongMsg = `
Loads nucleotide sequences from a FASTA file and aligns them using a
built-in profile. The first argument is the name of the built-in
profile to use for the alignment. The second argument is a comma
separated list of genes to align against. (This list should either be
surrounded by quote marks or contain no spaces).

Examples:

	nucamino align hiv1b pol
	nucamino align hcv1a NS3,NS5B
	nucamino align hiv1b 'gag, pol'

See 'nucamino profile list' for the available alignment profiles.

Use 'nucamino align-with' to use a custom alignment profile.`

var alignCmd = &cobra.Command{
	Use:   "align <profile name> <genes> [flags]",
	Short: "align sequences in a FASTA file using a built-in alignment profile",
	Long:  alignLongMsg,
	Args:  cobra.ExactArgs(2),
	RunE:  alignRun,
}
<commit_msg>Accept lowercase genes as command line args<commit_after>package cmd

import (
	"fmt"
	ap "github.com/hivdb/nucamino/alignmentprofile"
	"github.com/hivdb/nucamino/alignmentprofile/builtin"
	"github.com/hivdb/nucamino/cli"
	"github.com/pkg/profile"
	"github.com/spf13/cobra"
	"strings"
)

// The cobra cli library will populate these variables with values
// provided as command line flags.
var alignInputFilename, alignOutputFilename, alignOutputFormat string
var alignQuiet, alignPprof bool
var alignGoroutines int

func init() {
	rootCmd.AddCommand(alignCmd)

	alignCmd.Flags().StringVarP(
		&alignInputFilename,
		"input-file",
		"i",
		"-",
		"input file",
	)
	alignCmd.Flags().StringVarP(
		&alignOutputFilename,
		"output-file",
		"o",
		"-",
		"output File",
	)
	alignCmd.Flags().StringVarP(
		&alignOutputFormat,
		"output-format",
		"f",
		"tsv",
		"output format. (options: \"tsv\", \"json\")",
	)
	alignCmd.Flags().BoolVarP(
		&alignQuiet,
		"quiet",
		"q",
		false,
		"hide non-error output message",
	)
	alignCmd.Flags().BoolVarP(
		&alignPprof,
		"pprof",
		"p",
		false,
		"save profiling information",
	)
	alignCmd.Flags().IntVar(
		&alignGoroutines,
		"goroutines",
		0,
		"number of goroutines the aligner will use. (default: number of CPUs)",
	)
}

// Check that a gene-name is in a list of GEnes
func geneInGenes(geneArg string, genes []ap.Gene) bool {
	for _, g := range genes {
		if g.Matches(geneArg) {
			return true
		}
	}
	return false
}

func alignGetParameters(args []string) (*ap.AlignmentProfile, []string, error) {

	profileName := args[0]
	profile, found := builtin.Get(profileName)
	if !found {
		tmpl := `Unknown profile name: '%v'

See 'nucamino profile list' for a list of available profiles`
		err := fmt.Errorf(tmpl, profileName)
		return nil, nil, err
	}

	genes := strings.Split(args[1], ",")
	for idx, gene := range genes {
		genes[idx] = strings.ToUpper(gene)
	}
	profileGenes := profile.Genes()
	for _, gene := range genes {
		if !geneInGenes(gene, profileGenes) {
			tmpl := "%v is not an available gene in the profile %v (available genes: %v)"
			err := fmt.Errorf(tmpl, gene, profileName, profileGenes)
			return nil, nil, err
		}
	}

	return profile, genes, nil
}

func alignRun(cmd *cobra.Command, args []string) error {
	if alignPprof {
		defer profile.Start(profile.CPUProfile).Stop()
	}
	profile, genes, err := alignGetParameters(args)
	if err != nil {
		return err
	}
	return cli.PerformAlignment(
		alignInputFilename,
		alignOutputFilename,
		alignOutputFormat,
		genes,
		alignGoroutines,
		alignQuiet,
		*profile,
	)
}

var alignLongMsg = `
Loads nucleotide sequences from a FASTA file and aligns them using a
built-in profile. The first argument is the name of the built-in
profile to use for the alignment. The second argument is a comma
separated list of genes to align against. (This list should either be
surrounded by quote marks or contain no spaces).

Examples:

	nucamino align hiv1b pol
	nucamino align hcv1a NS3,NS5B
	nucamino align hiv1b 'gag, pol'

See 'nucamino profile list' for the available alignment profiles.

Use 'nucamino align-with' to use a custom alignment profile.`

var alignCmd = &cobra.Command{
	Use:   "align <profile name> <genes> [flags]",
	Short: "align sequences in a FASTA file using a built-in alignment profile",
	Long:  alignLongMsg,
	Args:  cobra.ExactArgs(2),
	RunE:  alignRun,
}
<|endoftext|>


<commit_before>package cmd

import (
	"errors"
	"os"

	"github.com/sirupsen/logrus"

	"github.com/buddyspike/mbt/lib"
	"github.com/spf13/cobra"
)

func init() {
	buildPr.Flags().StringVar(&src, "src", "", "source branch")
	buildPr.Flags().StringVar(&dst, "dst", "", "destination branch")

	buildCommand.AddCommand(buildBranch)
	buildCommand.AddCommand(buildPr)
	RootCmd.AddCommand(buildCommand)
}

var buildBranch = &cobra.Command{
	Use:   "branch <branch>",
	Short: "Builds the specific branch",
	RunE: func(cmd *cobra.Command, args []string) error {
		branch := "master"
		if len(args) > 0 {
			branch = args[0]
		}

		m, err := lib.ManifestByBranch(in, branch)
		if err != nil {
			return err
		}

		return build(m)
	},
}

var buildPr = &cobra.Command{
	Use:   "pr --src <branch> --dst <branch>",
	Short: "Builds a pr from src branch to dst branch",
	RunE: func(cmd *cobra.Command, args []string) error {
		if src == "" {
			return errors.New("requires source")
		}

		if dst == "" {
			return errors.New("requires dest")
		}

		m, err := lib.ManifestByPr(in, src, dst)
		if err != nil {
			return err
		}

		return build(m)
	},
}

func build(m *lib.Manifest) error {
	return lib.Build(m, os.Stdin, os.Stdout, os.Stderr, func(a *lib.Application, s lib.BuildStage) {
		switch s {
		case lib.BUILD_STAGE_BEFORE_BUILD:
			logrus.Infof("Building %s in %s", a.Name, a.Path)
		case lib.BUILD_STAGE_SKIP_BUILD:
			logrus.Infof("Skipping build for %s in %s", a.Name, a.Path)
		}
	})
}

var buildCommand = &cobra.Command{
	Use:   "build",
	Short: "Builds the applications in specified path",
}
<commit_msg>add version to output message.<commit_after>package cmd

import (
	"errors"
	"os"

	"github.com/sirupsen/logrus"

	"github.com/buddyspike/mbt/lib"
	"github.com/spf13/cobra"
)

func init() {
	buildPr.Flags().StringVar(&src, "src", "", "source branch")
	buildPr.Flags().StringVar(&dst, "dst", "", "destination branch")

	buildCommand.AddCommand(buildBranch)
	buildCommand.AddCommand(buildPr)
	RootCmd.AddCommand(buildCommand)
}

var buildBranch = &cobra.Command{
	Use:   "branch <branch>",
	Short: "Builds the specific branch",
	RunE: func(cmd *cobra.Command, args []string) error {
		branch := "master"
		if len(args) > 0 {
			branch = args[0]
		}

		m, err := lib.ManifestByBranch(in, branch)
		if err != nil {
			return err
		}

		return build(m)
	},
}

var buildPr = &cobra.Command{
	Use:   "pr --src <branch> --dst <branch>",
	Short: "Builds a pr from src branch to dst branch",
	RunE: func(cmd *cobra.Command, args []string) error {
		if src == "" {
			return errors.New("requires source")
		}

		if dst == "" {
			return errors.New("requires dest")
		}

		m, err := lib.ManifestByPr(in, src, dst)
		if err != nil {
			return err
		}

		return build(m)
	},
}

func build(m *lib.Manifest) error {
	return lib.Build(m, os.Stdin, os.Stdout, os.Stderr, func(a *lib.Application, s lib.BuildStage) {
		switch s {
		case lib.BUILD_STAGE_BEFORE_BUILD:
			logrus.Infof("BUILD %s in %s for %s", a.Name, a.Path, a.Version)
		case lib.BUILD_STAGE_SKIP_BUILD:
			logrus.Infof("SKIP %s in %s for %s", a.Name, a.Path, a.Version)
		}
	})
}

var buildCommand = &cobra.Command{
	Use:   "build",
	Short: "Builds the applications in specified path",
}
<|endoftext|>


<commit_before>package cmd

import (
	"log"
	"os"

	"github.com/bkittelmann/pinboard-checker/pinboard"
	"github.com/spf13/cobra"
)

var token string
var inputFile string
var outputFile string
var outputFormat string
var verbose bool
var noColor bool

func init() {
	checkCmd.Flags().StringVarP(&token, "token", "t", "", "The pinboard API token")
	checkCmd.Flags().StringVarP(&inputFile, "inputFile", "i", "", "File containing links to check")
	checkCmd.Flags().StringVarP(&outputFile, "outputFile", "o", "-", "Where the report should be written to")
	checkCmd.Flags().StringVarP(&outputFormat, "outputFormat", "f", "txt", "Allowed values are 'txt' (default) or 'json'")
	checkCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Verbose logging, will report successful link lookups")
	checkCmd.Flags().BoolVar(&noColor, "noColor", false, "Do not use colorized status output")

	RootCmd.AddCommand(checkCmd)
}

func makeReporter(format string) pinboard.Reporter {
	var reporter pinboard.Reporter
	switch format {
	case "json":
		reporter = pinboard.NewJSONReporter(verbose)
	case "txt":
		reporter = pinboard.NewSimpleFailureReporter(verbose, !noColor)
	default:
		log.Fatalf("'%s' is not a valid value for 'outputFormat'", format)
	}
	return reporter
}

var checkCmd = &cobra.Command{
	Use:   "check",
	Short: "Check for stale links",
	Long:  "...",

	Run: func(cmd *cobra.Command, args []string) {
		reporter := makeReporter(outputFormat)

		var bookmarks []pinboard.Bookmark
		if len(inputFile) > 0 {
			bookmarkJson, _ := os.Open(inputFile)
			bookmarks = pinboard.ParseJSON(bookmarkJson)
		} else {
			bookmarks, _ = pinboard.GetAllBookmarks(token)
		}

		pinboard.CheckAll(bookmarks, reporter)
	},
}
<commit_msg>Adding new inputFormat flag<commit_after>package cmd

import (
	"log"
	"os"

	"github.com/bkittelmann/pinboard-checker/pinboard"
	"github.com/spf13/cobra"
)

var token string
var inputFile string
var inputFormat string
var outputFile string
var outputFormat string
var verbose bool
var noColor bool

func init() {
	checkCmd.Flags().StringVarP(&token, "token", "t", "", "The pinboard API token")
	checkCmd.Flags().StringVarP(&inputFile, "inputFile", "i", "", "File containing links to check")
	checkCmd.Flags().StringVar(&inputFormat, "inputFormat", "json", "Format of file with links. Can be either 'json' (default) or 'txt'")
	checkCmd.Flags().StringVarP(&outputFile, "outputFile", "o", "-", "Where the report should be written to")
	checkCmd.Flags().StringVar(&outputFormat, "outputFormat", "txt", "Allowed values are 'txt' (default) or 'json'")
	checkCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Verbose logging, will report successful link lookups")
	checkCmd.Flags().BoolVar(&noColor, "noColor", false, "Do not use colorized status output")

	RootCmd.AddCommand(checkCmd)
}

func makeReporter(format string) pinboard.Reporter {
	var reporter pinboard.Reporter
	switch format {
	case "json":
		reporter = pinboard.NewJSONReporter(verbose)
	case "txt":
		reporter = pinboard.NewSimpleFailureReporter(verbose, !noColor)
	default:
		log.Fatalf("'%s' is not a valid value for 'outputFormat'", format)
	}
	return reporter
}

var checkCmd = &cobra.Command{
	Use:   "check",
	Short: "Check for stale links",
	Long:  "...",

	Run: func(cmd *cobra.Command, args []string) {
		reporter := makeReporter(outputFormat)

		var bookmarks []pinboard.Bookmark
		if len(inputFile) > 0 {
			bookmarkJson, _ := os.Open(inputFile)
			bookmarks = pinboard.ParseJSON(bookmarkJson)
		} else {
			bookmarks, _ = pinboard.GetAllBookmarks(token)
		}

		pinboard.CheckAll(bookmarks, reporter)
	},
}
<|endoftext|>


<commit_before>package cmd

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"net/http"
	"os"
	"runtime"
	"strings"
	"time"

	"github.com/codegangsta/cli"
	"github.com/exercism/cli/config"
)

type release struct {
	Location string `json:"html_url"`
	TagName  string `json:"tag_name"`
}

func (r *release) Version() string {
	return strings.TrimPrefix(r.TagName, "v")
}

// Debug provides information about the user's environment and configuration.
func Debug(ctx *cli.Context) {
	defer fmt.Printf("\nIf you are having trouble and need to file a GitHub issue (https://github.com/exercism/exercism.io/issues) please include this information (except your API key. Keep that private).\n")

	client := http.Client{Timeout: 5 * time.Second}

	fmt.Printf("\n**** Debug Information ****\n")
	fmt.Printf("Exercism CLI Version: %s\n", ctx.App.Version)

	rel, err := checkLatestRelease(client)
	if err != nil {
		log.Println(err)
	} else {
		if rel.Version() != ctx.App.Version {
			defer fmt.Println("\nYour CLI is outdated. A new release can be found here:", rel.Location)
		}
		fmt.Printf("Exercism CLI Latest Release: %s\n", rel.Version())
	}

	fmt.Printf("OS/Architecture: %s/%s\n", runtime.GOOS, runtime.GOARCH)

	dir, err := config.Home()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Home Dir: %s\n", dir)

	c, err := config.New(ctx.GlobalString("config"))
	if err != nil {
		log.Fatal(err)
	}

	configured := true
	if _, err = os.Stat(c.File); err != nil {
		if os.IsNotExist(err) {
			configured = false
		} else {
			log.Fatal(err)
		}
	}

	if configured {
		fmt.Printf("Config file: %s\n", c.File)
		fmt.Printf("API Key: %s\n", c.APIKey)
	} else {
		fmt.Println("Config file: <not configured>")
		fmt.Println("API Key: <not configured>")
	}

	fmt.Printf("API: %s [%s]\n", c.API, pingUrl(client, c.API))
	fmt.Printf("XAPI: %s [%s]\n", c.XAPI, pingUrl(client, c.XAPI))
	fmt.Printf("Exercises Directory: %s\n", c.Dir)
}

func checkLatestRelease(client http.Client) (*release, error) {
	resp, err := client.Get("https://api.github.com/repos/exercism/cli/releases/latest")
	if err != nil {
		return nil, errors.New("unable to get latest CLI release: " + err.Error())
	}

	var rel release
	if err := json.NewDecoder(resp.Body).Decode(&rel); err != nil {
		return nil, errors.New("error decoding latest release response: " + err.Error())
	}

	return &rel, nil
}

func pingUrl(client http.Client, url string) string {
	res, err := client.Get(url)
	if err != nil {
		return err.Error()
	}
	defer res.Body.Close()

	return "connected"
}
<commit_msg>Change download suggestion text<commit_after>package cmd

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"net/http"
	"os"
	"runtime"
	"strings"
	"time"

	"github.com/codegangsta/cli"
	"github.com/exercism/cli/config"
)

type release struct {
	Location string `json:"html_url"`
	TagName  string `json:"tag_name"`
}

func (r *release) Version() string {
	return strings.TrimPrefix(r.TagName, "v")
}

// Debug provides information about the user's environment and configuration.
func Debug(ctx *cli.Context) {
	defer fmt.Printf("\nIf you are having trouble and need to file a GitHub issue (https://github.com/exercism/exercism.io/issues) please include this information (except your API key. Keep that private).\n")

	client := http.Client{Timeout: 5 * time.Second}

	fmt.Printf("\n**** Debug Information ****\n")
	fmt.Printf("Exercism CLI Version: %s\n", ctx.App.Version)

	rel, err := checkLatestRelease(client)
	if err != nil {
		log.Println(err)
	} else {
		if rel.Version() != ctx.App.Version {
			defer fmt.Printf("\nA newer version of the CLI (%s) can be downloaded here: %s\n", rel.TagName, rel.Location)
		}
		fmt.Printf("Exercism CLI Latest Release: %s\n", rel.Version())
	}

	fmt.Printf("OS/Architecture: %s/%s\n", runtime.GOOS, runtime.GOARCH)

	dir, err := config.Home()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Home Dir: %s\n", dir)

	c, err := config.New(ctx.GlobalString("config"))
	if err != nil {
		log.Fatal(err)
	}

	configured := true
	if _, err = os.Stat(c.File); err != nil {
		if os.IsNotExist(err) {
			configured = false
		} else {
			log.Fatal(err)
		}
	}

	if configured {
		fmt.Printf("Config file: %s\n", c.File)
		fmt.Printf("API Key: %s\n", c.APIKey)
	} else {
		fmt.Println("Config file: <not configured>")
		fmt.Println("API Key: <not configured>")
	}

	fmt.Printf("API: %s [%s]\n", c.API, pingUrl(client, c.API))
	fmt.Printf("XAPI: %s [%s]\n", c.XAPI, pingUrl(client, c.XAPI))
	fmt.Printf("Exercises Directory: %s\n", c.Dir)
}

func checkLatestRelease(client http.Client) (*release, error) {
	resp, err := client.Get("https://api.github.com/repos/exercism/cli/releases/latest")
	if err != nil {
		return nil, errors.New("unable to get latest CLI release: " + err.Error())
	}

	var rel release
	if err := json.NewDecoder(resp.Body).Decode(&rel); err != nil {
		return nil, errors.New("error decoding latest release response: " + err.Error())
	}

	return &rel, nil
}

func pingUrl(client http.Client, url string) string {
	res, err := client.Get(url)
	if err != nil {
		return err.Error()
	}
	defer res.Body.Close()

	return "connected"
}
<|endoftext|>


<commit_before>// Copyright © 2017 Marcus Franke <marcus.franke@gmail.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package cmd

import (
	"encoding/base64"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"sync"

	"github.com/julienschmidt/httprouter"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
	"gopkg.in/russross/blackfriday.v2"
)

var (
	port   string
	cmdOut []byte
	mutex  sync.Mutex
)

// serveCmd represents the serve command
var serveCmd = &cobra.Command{
	Use:   "serve",
	Short: "Starts the yummy webserver",
	Run: func(cmd *cobra.Command, args []string) {

		repoPath := viper.GetString("yum.repopath")

		router := httprouter.New()
		router.Handler("GET", "/", http.FileServer(http.Dir(repoPath)))
		router.GET("/:filename", sendFileHandler)
		router.POST("/api/upload", apiPostUploadHandler)
		//router.PUT("/api/upload/:filename", apiUploadPut)
		router.DELETE("/api/delete/:filename", apiDeleteHandler)

		log.Fatal(http.ListenAndServe(":8080", router))
	},
}

func sendFileHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	repoPath := viper.GetString("yum.repopath")
	filename := ps.ByName("filename")
	if r.URL.Path == "/help" {
		helpHandler(w, r, ps)
	} else if r.URL.Path == "/repodata" {
		http.StripPrefix("/repodata", http.FileServer(http.Dir(repoPath+filename))).ServeHTTP(w, r)
	} else {
		if _, err := os.Stat(repoPath + "/" + filename); err == nil {
			http.ServeFile(w, r, repoPath+"/"+filename)
		} else if _, err := os.Stat(repoPath + "/repodata/" + filename); err == nil {
			http.ServeFile(w, r, repoPath+"/repodata/"+filename)
		} else {
			http.Error(w, "404 page not found!", http.StatusNotFound)
		}
	}
}

func updateRepo() bool {
	mutex.Lock()
	workers := viper.GetString("yum.workers")
	repoPath := viper.GetString("yum.repopath")
	createrepoBinary := viper.GetString("yum.createrepoBinary")
	cmdOut, err := exec.Command(createrepoBinary, "--update", "--workers", workers, repoPath).CombinedOutput()
	if err != nil {
		log.Println(err, string(cmdOut))
		mutex.Unlock()
		return false
	}
	log.Println(string(cmdOut))
	mutex.Unlock()
	return true
}

func helpHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	// get helpFile path from configuration
	helpFile := viper.GetString("yum.helpFile")

	// ingest the configured helpFile
	help, err := ioutil.ReadFile(helpFile)
	if err != nil {
		log.Println("Help file could not be read!")
		http.Error(w, "Could not load the help file", http.StatusInternalServerError)
		return
	}

	// render the Markdown file to HTML using the
	// blackfriday library
	output := blackfriday.Run(help)
	log.Println("/help requested!")
	fmt.Fprintf(w, string(output))
}

func checkAuthentication(r *http.Request) bool {
	// check if authentication is enabled
	if !viper.GetBool("yum.auth.enabled") {
		return true
	}
	// check if header is set
	authString := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
	if len(authString) != 2 {
		return false
	}
	// decode auth data
	authBytes, err := base64.StdEncoding.DecodeString(authString[1])
	if err != nil {
		return false
	}
	// check user and password
	if string(authBytes) == viper.GetString("yum.auth.user")+":"+viper.GetString("yum.auth.password") {
		return true
	} else {
		return false
	}
}

func apiDeleteHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	fileName := ps.ByName("filename")
	repoPath := viper.GetString("yum.repopath")

	if !checkAuthentication(r) {
		http.Error(w, "not authorized", http.StatusUnauthorized)
		return
	}

	if _, err := os.Stat(repoPath + "/" + fileName); err == nil {
		// requested file exists
		if err := os.Remove(repoPath + "/" + fileName); err != nil {
			errText := fmt.Sprintf("%s - Could not delete file!\n", r.URL)
			log.Printf(errText)
			http.Error(w, errText, http.StatusInternalServerError)
			return
		} else {
			// file deleted
			logText := fmt.Sprintf("%s - File deleted!\n", r.URL.Path)
			log.Printf(logText)
			// update repository
			if !updateRepo() {
				http.Error(w, "Could not update repository", http.StatusInternalServerError)
			}
		}
	} else {
		// file does not exists
		errText := fmt.Sprintf("%s - File not found!\n", r.URL.Path)
		log.Println(errText)
		http.Error(w, errText, http.StatusNotFound)
	}
}

func apiPostUploadHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {

	errText := ""
	repoPath := viper.GetString("yum.repopath")

	file, handler, err := r.FormFile("fileupload")
	if err != nil {
		errText = fmt.Sprintf("%s - incorrect FormFile used, must be fileupload!\n", r.URL)
		log.Println(errText)
		http.Error(w, errText, http.StatusBadRequest)
		return
	}
	defer file.Close()

	if filepath.Ext(handler.Filename) != ".rpm" {
		errText = fmt.Sprintf("%s - %s uploaded, not an rpm package!\n", r.URL, handler.Filename)
		log.Printf(errText)
		http.Error(w, errText, http.StatusUnsupportedMediaType)
		return
	}

	// check if the uploaded file already exists
	// if the repository is configured in protected mode
	// the request will return status 403 (forbidden)
	if viper.GetBool("yum.protected") {
		if _, err := os.Stat(repoPath + "/" + handler.Filename); err == nil {
			errText = fmt.Sprintf("%s - File already exists, forbidden to overwrite!", r.URL)
			log.Println(errText)
			http.Error(w, errText, http.StatusForbidden)
			return
		}
	}

	// create file handler to write uploaded file to
	f, err := os.OpenFile(repoPath+"/"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0644)
	if err != nil {
		errText = fmt.Sprintf("%s - %s/%s could not be created!\n", r.URL, repoPath, handler.Filename)
		log.Println(errText)
		http.Error(w, errText, http.StatusInternalServerError)
		log.Fatal(err)
	}
	defer f.Close()

	// copy the file buffer into the file handle
	_, err = io.Copy(f, file)
	if err != nil {
		errText = fmt.Sprintf("%s - an error occured copying the uploaded file to servers filesystem!\n",
			r.URL)
		log.Println(errText)
		log.Println(err)
		http.Error(w, errText, http.StatusInternalServerError)
		return
	}
	// update repository
	if !updateRepo() {
		http.Error(w, "Could not update repository", http.StatusInternalServerError)
	}
}

func init() {
	RootCmd.AddCommand(serveCmd)

	// Flags for the serve command.
	serveCmd.Flags().StringVarP(&port, "port", "p", "8080", "Port to listen on")
}
<commit_msg>[serve] fix commandline flag for http port<commit_after>// Copyright © 2017 Marcus Franke <marcus.franke@gmail.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package cmd

import (
	"encoding/base64"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"sync"

	"github.com/julienschmidt/httprouter"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
	"gopkg.in/russross/blackfriday.v2"
)

var (
	port   string
	cmdOut []byte
	mutex  sync.Mutex
)

// serveCmd represents the serve command
var serveCmd = &cobra.Command{
	Use:   "serve",
	Short: "Starts the yummy webserver",
	Run: func(cmd *cobra.Command, args []string) {

		repoPath := viper.GetString("yum.repopath")

		router := httprouter.New()
		router.Handler("GET", "/", http.FileServer(http.Dir(repoPath)))
		router.GET("/:filename", sendFileHandler)
		router.POST("/api/upload", apiPostUploadHandler)
		//router.PUT("/api/upload/:filename", apiUploadPut)
		router.DELETE("/api/delete/:filename", apiDeleteHandler)

		log.Fatal(http.ListenAndServe(":"+port, router))
	},
}

func sendFileHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	repoPath := viper.GetString("yum.repopath")
	filename := ps.ByName("filename")
	if r.URL.Path == "/help" {
		helpHandler(w, r, ps)
	} else if r.URL.Path == "/repodata" {
		http.StripPrefix("/repodata", http.FileServer(http.Dir(repoPath+filename))).ServeHTTP(w, r)
	} else {
		if _, err := os.Stat(repoPath + "/" + filename); err == nil {
			http.ServeFile(w, r, repoPath+"/"+filename)
		} else if _, err := os.Stat(repoPath + "/repodata/" + filename); err == nil {
			http.ServeFile(w, r, repoPath+"/repodata/"+filename)
		} else {
			http.Error(w, "404 page not found!", http.StatusNotFound)
		}
	}
}

func updateRepo() bool {
	mutex.Lock()
	workers := viper.GetString("yum.workers")
	repoPath := viper.GetString("yum.repopath")
	createrepoBinary := viper.GetString("yum.createrepoBinary")
	cmdOut, err := exec.Command(createrepoBinary, "--update", "--workers", workers, repoPath).CombinedOutput()
	if err != nil {
		log.Println(err, string(cmdOut))
		mutex.Unlock()
		return false
	}
	log.Println(string(cmdOut))
	mutex.Unlock()
	return true
}

func helpHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	// get helpFile path from configuration
	helpFile := viper.GetString("yum.helpFile")

	// ingest the configured helpFile
	help, err := ioutil.ReadFile(helpFile)
	if err != nil {
		log.Println("Help file could not be read!")
		http.Error(w, "Could not load the help file", http.StatusInternalServerError)
		return
	}

	// render the Markdown file to HTML using the
	// blackfriday library
	output := blackfriday.Run(help)
	log.Println("/help requested!")
	fmt.Fprintf(w, string(output))
}

func checkAuthentication(r *http.Request) bool {
	// check if authentication is enabled
	if !viper.GetBool("yum.auth.enabled") {
		return true
	}
	// check if header is set
	authString := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
	if len(authString) != 2 {
		return false
	}
	// decode auth data
	authBytes, err := base64.StdEncoding.DecodeString(authString[1])
	if err != nil {
		return false
	}
	// check user and password
	if string(authBytes) == viper.GetString("yum.auth.user")+":"+viper.GetString("yum.auth.password") {
		return true
	} else {
		return false
	}
}

func apiDeleteHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	fileName := ps.ByName("filename")
	repoPath := viper.GetString("yum.repopath")

	if !checkAuthentication(r) {
		http.Error(w, "not authorized", http.StatusUnauthorized)
		return
	}

	if _, err := os.Stat(repoPath + "/" + fileName); err == nil {
		// requested file exists
		if err := os.Remove(repoPath + "/" + fileName); err != nil {
			errText := fmt.Sprintf("%s - Could not delete file!\n", r.URL)
			log.Printf(errText)
			http.Error(w, errText, http.StatusInternalServerError)
			return
		} else {
			// file deleted
			logText := fmt.Sprintf("%s - File deleted!\n", r.URL.Path)
			log.Printf(logText)
			// update repository
			if !updateRepo() {
				http.Error(w, "Could not update repository", http.StatusInternalServerError)
			}
		}
	} else {
		// file does not exists
		errText := fmt.Sprintf("%s - File not found!\n", r.URL.Path)
		log.Println(errText)
		http.Error(w, errText, http.StatusNotFound)
	}
}

func apiPostUploadHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {

	errText := ""
	repoPath := viper.GetString("yum.repopath")

	file, handler, err := r.FormFile("fileupload")
	if err != nil {
		errText = fmt.Sprintf("%s - incorrect FormFile used, must be fileupload!\n", r.URL)
		log.Println(errText)
		http.Error(w, errText, http.StatusBadRequest)
		return
	}
	defer file.Close()

	if filepath.Ext(handler.Filename) != ".rpm" {
		errText = fmt.Sprintf("%s - %s uploaded, not an rpm package!\n", r.URL, handler.Filename)
		log.Printf(errText)
		http.Error(w, errText, http.StatusUnsupportedMediaType)
		return
	}

	// check if the uploaded file already exists
	// if the repository is configured in protected mode
	// the request will return status 403 (forbidden)
	if viper.GetBool("yum.protected") {
		if _, err := os.Stat(repoPath + "/" + handler.Filename); err == nil {
			errText = fmt.Sprintf("%s - File already exists, forbidden to overwrite!", r.URL)
			log.Println(errText)
			http.Error(w, errText, http.StatusForbidden)
			return
		}
	}

	// create file handler to write uploaded file to
	f, err := os.OpenFile(repoPath+"/"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0644)
	if err != nil {
		errText = fmt.Sprintf("%s - %s/%s could not be created!\n", r.URL, repoPath, handler.Filename)
		log.Println(errText)
		http.Error(w, errText, http.StatusInternalServerError)
		log.Fatal(err)
	}
	defer f.Close()

	// copy the file buffer into the file handle
	_, err = io.Copy(f, file)
	if err != nil {
		errText = fmt.Sprintf("%s - an error occured copying the uploaded file to servers filesystem!\n",
			r.URL)
		log.Println(errText)
		log.Println(err)
		http.Error(w, errText, http.StatusInternalServerError)
		return
	}
	// update repository
	if !updateRepo() {
		http.Error(w, "Could not update repository", http.StatusInternalServerError)
	}
}

func init() {
	RootCmd.AddCommand(serveCmd)

	// Flags for the serve command.
	serveCmd.Flags().StringVarP(&port, "port", "p", "8080", "Port to listen on")
}
<|endoftext|>


<commit_before>// Copyright © 2017 NAME HERE <EMAIL ADDRESS>
// 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"
	"bytes"
	"encoding/base64"
	"fmt"
	"image"
	"image/jpeg"
	"log"
	"net/http"
	"strconv"
	"strings"
	"sync"

	"github.com/charles-d-burton/goshot/v2/utility"
	"github.com/dhowden/raspicam"
	"github.com/gin-gonic/gin"
	"github.com/spf13/cobra"
)

var (
	serverPort      int
	serverInterface string
	quality         int
	mutex           = &sync.Mutex{}
)

// serveCmd represents the serve command
var serveCmd = &cobra.Command{
	Use:   "serve",
	Short: "start serving the camera",
	Long: `Serve the application on the given port
or default to 8080`,
	Run: func(cmd *cobra.Command, args []string) {
		log.Println("Args: ", strings.Join(args, ""))
		serve()
	},
}

func init() {
	RootCmd.AddCommand(serveCmd)
	serveCmd.Flags().IntVarP(&serverPort, "port", "p", 8080, "port on which the server will listen")
	serveCmd.Flags().StringVarP(&serverInterface, "bind", "", "127.0.0.1", "interface to which the server will bind")
	serveCmd.Flags().IntVarP(&quality, "quality", "q", 100, "quality of the returned JPEG image")
	// Here you will define your flags and configuration settings.

	// Cobra supports Persistent Flags which will work for this command
	// and all subcommands, e.g.:
	// serveCmd.PersistentFlags().String("foo", "", "A help for foo")

	// Cobra supports local flags which will only run when this command
	// is called directly, e.g.:
	// serveCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

func serve() {
	mdnsServer := utility.BroadcastServer(serverPort)
	defer mdnsServer.Shutdown()

	r := gin.Default()

	//Capture an image and return the base64 encoded value
	r.GET("/shot", getShotJSON)
	r.GET("/rawshot", getRawShot)
	r.Run(":" + strconv.Itoa(serverPort))

}

//Return a Base64 encoded JSON object containing the picture
func getShotJSON(c *gin.Context) {

	data, err := snapPicture()

	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"error": err.Error(),
		})
	}
	if http.DetectContentType(data) == "image/jpeg" { //Check that we have some actual image data
		imgData := data
		if quality < 100 {
			imgData, _ = convertJPEG(data)
		}
		encodedImage := base64.StdEncoding.EncodeToString(imgData)
		c.JSON(200, gin.H{
			"image": encodedImage,
		})
	} else {
		c.JSON(http.StatusInternalServerError, gin.H{
			"error": "Unkown File type",
		})
	}
}

//Return a raw JPEG instead of Base64 encoded JSON doc
func getRawShot(c *gin.Context) {
	data, err := snapPicture()
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"error": err.Error(),
		})
	}
	if http.DetectContentType(data) == "image/jpeg" { //Check that we have some actual image data
		c.Data(200, "image/jpeg", data)
	} else {
		c.JSON(http.StatusInternalServerError, gin.H{
			"error": "Unkown File type",
		})
	}
}

/*
 * Interface with the camera and have it take a picture
 * return that photo as a byte array.
 */
func snapPicture() ([]byte, error) {
	var b bytes.Buffer
	wr := bufio.NewWriter(&b)
	s := raspicam.NewStill()
	errCh := make(chan error)
	go func() {
		for x := range errCh {
			log.Println(x)
		}
	}()
	log.Println("Capturing image...")
	raspicam.Capture(s, wr, errCh)
	if len(b.Bytes()) != 0 {
		return b.Bytes(), nil
	}
	return nil, fmt.Errorf("Could not capture imate")
}

/*
 * Convert image quality of JPEG
 */

func convertJPEG(imgByte []byte) ([]byte, error) {
	img, _, err := image.Decode(bytes.NewReader(imgByte))
	if err != nil {
		log.Println(err)
		return nil, err
	}
	var opt jpeg.Options
	opt.Quality = 90
	var b bytes.Buffer
	writer := bufio.NewWriter(&b)
	err = jpeg.Encode(writer, img, &opt)
	if err != nil {
		log.Println(err)
		return nil, err
	}
	return b.Bytes(), nil

}
<commit_msg>feat: code working with arducam<commit_after>// Copyright © 2017 NAME HERE <EMAIL ADDRESS>
// 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"
	"bytes"
	"encoding/base64"
	"fmt"
	"image"
	"image/jpeg"
	"log"
	"net/http"
	"strconv"
	"strings"
	"sync"

	"github.com/charles-d-burton/goshot/utility"
	"github.com/dhowden/raspicam"
	"github.com/gin-gonic/gin"
	"github.com/spf13/cobra"
)

var (
	serverPort      int
	serverInterface string
	quality         int
	mutex           = &sync.Mutex{}
)

// serveCmd represents the serve command
var serveCmd = &cobra.Command{
	Use:   "serve",
	Short: "start serving the camera",
	Long: `Serve the application on the given port
or default to 8080`,
	Run: func(cmd *cobra.Command, args []string) {
		log.Println("Args: ", strings.Join(args, ""))
		serve()
	},
}

func init() {
	RootCmd.AddCommand(serveCmd)
	serveCmd.Flags().IntVarP(&serverPort, "port", "p", 8080, "port on which the server will listen")
	serveCmd.Flags().StringVarP(&serverInterface, "bind", "", "127.0.0.1", "interface to which the server will bind")
	serveCmd.Flags().IntVarP(&quality, "quality", "q", 100, "quality of the returned JPEG image")
	// Here you will define your flags and configuration settings.

	// Cobra supports Persistent Flags which will work for this command
	// and all subcommands, e.g.:
	// serveCmd.PersistentFlags().String("foo", "", "A help for foo")

	// Cobra supports local flags which will only run when this command
	// is called directly, e.g.:
	// serveCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

func serve() {
	mdnsServer := utility.BroadcastServer(serverPort)
	defer mdnsServer.Shutdown()

	r := gin.Default()

	//Capture an image and return the base64 encoded value
	r.GET("/shot", getShotJSON)
	r.GET("/rawshot", getRawShot)
	r.Run(":" + strconv.Itoa(serverPort))

}

//Return a Base64 encoded JSON object containing the picture
func getShotJSON(c *gin.Context) {

	data, err := snapPicture()

	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"error": err.Error(),
		})
	}
	if http.DetectContentType(data) == "image/jpeg" { //Check that we have some actual image data
		imgData := data
		if quality < 100 {
			imgData, _ = convertJPEG(data)
		}
		encodedImage := base64.StdEncoding.EncodeToString(imgData)
		c.JSON(200, gin.H{
			"image": encodedImage,
		})
	} else {
		c.JSON(http.StatusInternalServerError, gin.H{
			"error": "Unkown File type",
		})
	}
}

//Return a raw JPEG instead of Base64 encoded JSON doc
func getRawShot(c *gin.Context) {
	data, err := snapPicture()
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"error": err.Error(),
		})
	}
	if http.DetectContentType(data) == "image/jpeg" { //Check that we have some actual image data
		c.Data(200, "image/jpeg", data)
	} else {
		c.JSON(http.StatusInternalServerError, gin.H{
			"error": "Unkown File type",
		})
	}
}

/*
 * Interface with the camera and have it take a picture
 * return that photo as a byte array.
 */
func snapPicture() ([]byte, error) {
	var b bytes.Buffer
	wr := bufio.NewWriter(&b)
	s := raspicam.NewStill()
	errCh := make(chan error)
	go func() {
		for x := range errCh {
			log.Println(x)
		}
	}()
	log.Println("Capturing image...")
	raspicam.Capture(s, wr, errCh)
	if len(b.Bytes()) != 0 {
		return b.Bytes(), nil
	}
	return nil, fmt.Errorf("Could not capture imate")
}

/*
 * Convert image quality of JPEG
 */

func convertJPEG(imgByte []byte) ([]byte, error) {
	img, _, err := image.Decode(bytes.NewReader(imgByte))
	if err != nil {
		log.Println(err)
		return nil, err
	}
	var opt jpeg.Options
	opt.Quality = 90
	var b bytes.Buffer
	writer := bufio.NewWriter(&b)
	err = jpeg.Encode(writer, img, &opt)
	if err != nil {
		log.Println(err)
		return nil, err
	}
	return b.Bytes(), nil

}
<|endoftext|>


<commit_before>// Copyright © 2017 Marcus Franke <marcus.franke@gmail.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package cmd

import (
	"encoding/base64"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"sync"

	"github.com/julienschmidt/httprouter"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
	"gopkg.in/russross/blackfriday.v2"
)

var (
	port   string
	cmdOut []byte
	mutex  sync.Mutex
)

// serveCmd represents the serve command
var serveCmd = &cobra.Command{
	Use:   "serve",
	Short: "Starts the yummy webserver",
	Run: func(cmd *cobra.Command, args []string) {

		repoPath := viper.GetString("yum.repopath")

		router := httprouter.New()
		router.Handler("GET", "/", http.FileServer(http.Dir(repoPath)))
		router.GET("/:filename", sendFileHandler)
		router.POST("/api/upload", apiPostUploadHandler)
		//router.PUT("/api/upload/:filename", apiUploadPut)
		router.DELETE("/api/delete/:filename", apiDeleteHandler)

		log.Fatal(http.ListenAndServe(":8080", router))
	},
}

func sendFileHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	// if /help is called, we need to redirect this to helpHandler
	if r.URL.Path == "/help" {
		helpHandler(w, r, ps)
	} else {
		http.ServeFile(w, r, viper.GetString("yum.repopath")+"/"+ps.ByName("filename"))
	}
}

func updateRepo() bool {
	mutex.Lock()
	workers := viper.GetString("yum.workers")
	repoPath := viper.GetString("yum.repopath")
	createrepoBinary := viper.GetString("yum.createrepoBinary")
	cmdOut, err := exec.Command(createrepoBinary, "--update", "--workers", workers, repoPath).CombinedOutput()
	if err != nil {
		log.Println(err, string(cmdOut))
		mutex.Unlock()
		return false
	}
	log.Println(string(cmdOut))
	mutex.Unlock()
	return true
}

func helpHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	// get helpFile path from configuration
	helpFile := viper.GetString("yum.helpFile")

	// ingest the configured helpFile
	help, err := ioutil.ReadFile(helpFile)
	if err != nil {
		log.Println("Help file could not be read!")
		http.Error(w, "Could not load the help file", http.StatusInternalServerError)
		return
	}

	// render the Markdown file to HTML using the
	// blackfriday library
	output := blackfriday.Run(help)
	log.Println("/help requested!")
	fmt.Fprintf(w, string(output))
}

func checkAuthentication(r *http.Request) bool {
	// check if authentication is enabled
	if !viper.GetBool("yum.auth.enabled") {
		return true
	}
	// check if header is set
	authString := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
	if len(authString) != 2 {
		return false
	}
	// decode auth data
	authBytes, err := base64.StdEncoding.DecodeString(authString[1])
	if err != nil {
		return false
	}
	// check user and password
	if string(authBytes) == viper.GetString("yum.auth.user")+":"+viper.GetString("yum.auth.password") {
		return true
	} else {
		return false
	}
}

func apiDeleteHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	fileName := ps.ByName("filename")
	repoPath := viper.GetString("yum.repopath")

	if !checkAuthentication(r) {
		http.Error(w, "not authorized", http.StatusUnauthorized)
		return
	}

	if _, err := os.Stat(repoPath + "/" + fileName); err == nil {
		// requested file exists
		if err := os.Remove(repoPath + "/" + fileName); err != nil {
			errText := fmt.Sprintf("%s - Could not delete file!\n", r.URL)
			log.Printf(errText)
			http.Error(w, errText, http.StatusInternalServerError)
			return
		} else {
			// file deleted
			logText := fmt.Sprintf("%s - File deleted!\n", r.URL.Path)
			log.Printf(logText)
			// update repository
			if !updateRepo() {
				http.Error(w, "Could not update repository", http.StatusInternalServerError)
			}
		}
	} else {
		// file does not exists
		errText := fmt.Sprintf("%s - File not found!\n", r.URL.Path)
		log.Println(errText)
		http.Error(w, errText, http.StatusNotFound)
	}
}

func apiPostUploadHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {

	errText := ""
	repoPath := viper.GetString("yum.repopath")

	file, handler, err := r.FormFile("fileupload")
	if err != nil {
		errText = fmt.Sprintf("%s - incorrect FormFile used, must be fileupload!\n", r.URL)
		log.Println(errText)
		http.Error(w, errText, http.StatusBadRequest)
		return
	}
	defer file.Close()

	if filepath.Ext(handler.Filename) != ".rpm" {
		errText = fmt.Sprintf("%s - %s uploaded, not an rpm package!\n", r.URL, handler.Filename)
		log.Printf(errText)
		http.Error(w, errText, http.StatusUnsupportedMediaType)
		return
	}

	// check if the uploaded file already exists
	// if the repository is configured in protected mode
	// the request will return status 403 (forbidden)
	if viper.GetBool("yum.protected") {
		if _, err := os.Stat(repoPath + "/" + handler.Filename); err == nil {
			errText = fmt.Sprintf("%s - File already exists, forbidden to overwrite!", r.URL)
			log.Println(errText)
			http.Error(w, errText, http.StatusForbidden)
			return
		}
	}

	// create file handler to write uploaded file to
	f, err := os.OpenFile(repoPath+"/"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0644)
	if err != nil {
		errText = fmt.Sprintf("%s - %s/%s could not be created!\n", r.URL, repoPath, handler.Filename)
		log.Println(errText)
		http.Error(w, errText, http.StatusInternalServerError)
		log.Fatal(err)
	}
	defer f.Close()

	// copy the file buffer into the file handle
	_, err = io.Copy(f, file)
	if err != nil {
		errText = fmt.Sprintf("%s - an error occured copying the uploaded file to servers filesystem!\n",
			r.URL)
		log.Println(errText)
		log.Println(err)
		http.Error(w, errText, http.StatusInternalServerError)
		return
	}
	// update repository
	if !updateRepo() {
		http.Error(w, "Could not update repository", http.StatusInternalServerError)
	}
}

func init() {
	RootCmd.AddCommand(serveCmd)

	// Flags for the serve command.
	serveCmd.Flags().StringVarP(&port, "port", "p", "8080", "Port to listen on")
}
<commit_msg>[serve] fix directory listing for /repodata<commit_after>// Copyright © 2017 Marcus Franke <marcus.franke@gmail.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package cmd

import (
	"encoding/base64"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"sync"

	"github.com/julienschmidt/httprouter"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
	"gopkg.in/russross/blackfriday.v2"
)

var (
	port   string
	cmdOut []byte
	mutex  sync.Mutex
)

// serveCmd represents the serve command
var serveCmd = &cobra.Command{
	Use:   "serve",
	Short: "Starts the yummy webserver",
	Run: func(cmd *cobra.Command, args []string) {

		repoPath := viper.GetString("yum.repopath")

		router := httprouter.New()
		router.Handler("GET", "/", http.FileServer(http.Dir(repoPath)))
		router.GET("/:tier1", sendFileHandler)
		router.GET("/:tier1/:tier2", sendFileHandler)
		router.POST("/api/upload", apiPostUploadHandler)
		//router.PUT("/api/upload/:filename", apiUploadPut)
		router.DELETE("/api/delete/:filename", apiDeleteHandler)

		log.Fatal(http.ListenAndServe(":8080", router))
	},
}

func sendFileHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	repoPath := viper.GetString("yum.repopath")
	if r.URL.Path == "/help" {
		helpHandler(w, r, ps)
	} else if r.URL.Path == "/repodata" {
		http.StripPrefix("/repodata", http.FileServer(http.Dir(repoPath+ps.ByName("tier1")))).ServeHTTP(w, r)
	} else if ps.ByName("tier2") != "" {
		http.ServeFile(w, r, repoPath+"/"+ps.ByName("tier1")+ps.ByName("tier2"))
	} else {
		http.ServeFile(w, r, repoPath+"/"+ps.ByName("tier1"))
	}
}

func updateRepo() bool {
	mutex.Lock()
	workers := viper.GetString("yum.workers")
	repoPath := viper.GetString("yum.repopath")
	createrepoBinary := viper.GetString("yum.createrepoBinary")
	cmdOut, err := exec.Command(createrepoBinary, "--update", "--workers", workers, repoPath).CombinedOutput()
	if err != nil {
		log.Println(err, string(cmdOut))
		mutex.Unlock()
		return false
	}
	log.Println(string(cmdOut))
	mutex.Unlock()
	return true
}

func helpHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	// get helpFile path from configuration
	helpFile := viper.GetString("yum.helpFile")

	// ingest the configured helpFile
	help, err := ioutil.ReadFile(helpFile)
	if err != nil {
		log.Println("Help file could not be read!")
		http.Error(w, "Could not load the help file", http.StatusInternalServerError)
		return
	}

	// render the Markdown file to HTML using the
	// blackfriday library
	output := blackfriday.Run(help)
	log.Println("/help requested!")
	fmt.Fprintf(w, string(output))
}

func checkAuthentication(r *http.Request) bool {
	// check if authentication is enabled
	if !viper.GetBool("yum.auth.enabled") {
		return true
	}
	// check if header is set
	authString := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
	if len(authString) != 2 {
		return false
	}
	// decode auth data
	authBytes, err := base64.StdEncoding.DecodeString(authString[1])
	if err != nil {
		return false
	}
	// check user and password
	if string(authBytes) == viper.GetString("yum.auth.user")+":"+viper.GetString("yum.auth.password") {
		return true
	} else {
		return false
	}
}

func apiDeleteHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	fileName := ps.ByName("filename")
	repoPath := viper.GetString("yum.repopath")

	if !checkAuthentication(r) {
		http.Error(w, "not authorized", http.StatusUnauthorized)
		return
	}

	if _, err := os.Stat(repoPath + "/" + fileName); err == nil {
		// requested file exists
		if err := os.Remove(repoPath + "/" + fileName); err != nil {
			errText := fmt.Sprintf("%s - Could not delete file!\n", r.URL)
			log.Printf(errText)
			http.Error(w, errText, http.StatusInternalServerError)
			return
		} else {
			// file deleted
			logText := fmt.Sprintf("%s - File deleted!\n", r.URL.Path)
			log.Printf(logText)
			// update repository
			if !updateRepo() {
				http.Error(w, "Could not update repository", http.StatusInternalServerError)
			}
		}
	} else {
		// file does not exists
		errText := fmt.Sprintf("%s - File not found!\n", r.URL.Path)
		log.Println(errText)
		http.Error(w, errText, http.StatusNotFound)
	}
}

func apiPostUploadHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {

	errText := ""
	repoPath := viper.GetString("yum.repopath")

	file, handler, err := r.FormFile("fileupload")
	if err != nil {
		errText = fmt.Sprintf("%s - incorrect FormFile used, must be fileupload!\n", r.URL)
		log.Println(errText)
		http.Error(w, errText, http.StatusBadRequest)
		return
	}
	defer file.Close()

	if filepath.Ext(handler.Filename) != ".rpm" {
		errText = fmt.Sprintf("%s - %s uploaded, not an rpm package!\n", r.URL, handler.Filename)
		log.Printf(errText)
		http.Error(w, errText, http.StatusUnsupportedMediaType)
		return
	}

	// check if the uploaded file already exists
	// if the repository is configured in protected mode
	// the request will return status 403 (forbidden)
	if viper.GetBool("yum.protected") {
		if _, err := os.Stat(repoPath + "/" + handler.Filename); err == nil {
			errText = fmt.Sprintf("%s - File already exists, forbidden to overwrite!", r.URL)
			log.Println(errText)
			http.Error(w, errText, http.StatusForbidden)
			return
		}
	}

	// create file handler to write uploaded file to
	f, err := os.OpenFile(repoPath+"/"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0644)
	if err != nil {
		errText = fmt.Sprintf("%s - %s/%s could not be created!\n", r.URL, repoPath, handler.Filename)
		log.Println(errText)
		http.Error(w, errText, http.StatusInternalServerError)
		log.Fatal(err)
	}
	defer f.Close()

	// copy the file buffer into the file handle
	_, err = io.Copy(f, file)
	if err != nil {
		errText = fmt.Sprintf("%s - an error occured copying the uploaded file to servers filesystem!\n",
			r.URL)
		log.Println(errText)
		log.Println(err)
		http.Error(w, errText, http.StatusInternalServerError)
		return
	}
	// update repository
	if !updateRepo() {
		http.Error(w, "Could not update repository", http.StatusInternalServerError)
	}
}

func init() {
	RootCmd.AddCommand(serveCmd)

	// Flags for the serve command.
	serveCmd.Flags().StringVarP(&port, "port", "p", "8080", "Port to listen on")
}
<|endoftext|>


<commit_before>// Copyright 2013 bee authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package main

import (
	"fmt"
	"os"
	path "path/filepath"
	"strings"
)

var cmdApiapp = &Command{
	// CustomFlags: true,
	UsageLine: "api [appname]",
	Short:     "create an api application base on beego framework",
	Long: `
create an api application base on beego framework

In the current path, will create a folder named [appname]

In the appname folder has the follow struct:

	├── conf
	│   └── app.conf
	├── controllers
	│   └── default.go
	├── routers
	│   └── router.go
	├── tests
	│   └── default_test.go
	├── main.go
	└── models
	    └── object.go

`,
}

var apiconf = `
appname = {{.Appname}}
httpport = 8080
runmode = dev
autorender = false
copyrequestbody = true
`
var apiMaingo = `package main

import (
	_ "{{.Appname}}/routers"
	"github.com/astaxie/beego"
)

//		Objects

//	URL					HTTP Verb				Functionality
//	/object				POST					Creating Objects
//	/object/<objectId>	GET						Retrieving Objects
//	/object/<objectId>	PUT						Updating Objects
//	/object				GET						Queries
//	/object/<objectId>	DELETE					Deleting Objects

func main() {
	beego.Run()
}
`
var apirouter = `package routers

import (
	"{{.Appname}}/controllers"
	"github.com/astaxie/beego"
)

func init() {
    beego.RESTRouter("/object", &controllers.ObjectController{})
}
`

var apiModels = `package models

import (
	"errors"
	"strconv"
	"time"
)

var (
	Objects map[string]*Object
)

type Object struct {
	ObjectId   string
	Score      int64
	PlayerName string
}

func init() {
	Objects = make(map[string]*Object)
	Objects["hjkhsbnmn123"] = &Object{"hjkhsbnmn123", 100, "astaxie"}
	Objects["mjjkxsxsaa23"] = &Object{"mjjkxsxsaa23", 101, "someone"}
}

func AddOne(object Object) (ObjectId string) {
	object.ObjectId = "astaxie" + strconv.FormatInt(time.Now().UnixNano(), 10)
	Objects[object.ObjectId] = &object
	return object.ObjectId
}

func GetOne(ObjectId string) (object *Object, err error) {
	if v, ok := Objects[ObjectId]; ok {
		return v, nil
	}
	return nil, errors.New("ObjectId Not Exist")
}

func GetAll() map[string]*Object {
	return Objects
}

func Update(ObjectId string, Score int64) (err error) {
	if v, ok := Objects[ObjectId]; ok {
		v.Score = Score
		return nil
	}
	return errors.New("ObjectId Not Exist")
}

func Delete(ObjectId string) {
	delete(Objects, ObjectId)
}
`

var apiControllers = `package controllers

import (
	"encoding/json"
	"github.com/astaxie/beego"
	"{{.Appname}}/models"
)

type ResponseInfo struct {
}

type ObjectController struct {
	beego.Controller
}

func (this *ObjectController) Post() {
	var ob models.Object
	json.Unmarshal(this.Ctx.Input.RequestBody, &ob)
	objectid := models.AddOne(ob)
	this.Data["json"] = map[string]string{"ObjectId": objectid}
	this.ServeJson()
}

func (this *ObjectController) Get() {
	objectId := this.Ctx.Input.Params[":objectId"]
	if objectId != "" {
		ob, err := models.GetOne(objectId)
		if err != nil {
			this.Data["json"] = err
		} else {
			this.Data["json"] = ob
		}
	} else {
		obs := models.GetAll()
		this.Data["json"] = obs
	}
	this.ServeJson()
}

func (this *ObjectController) Put() {
	objectId := this.Ctx.Input.Params[":objectId"]
	var ob models.Object
	json.Unmarshal(this.Ctx.Input.RequestBody, &ob)

	err := models.Update(objectId, ob.Score)
	if err != nil {
		this.Data["json"] = err
	} else {
		this.Data["json"] = "update success!"
	}
	this.ServeJson()
}

func (this *ObjectController) Delete() {
	objectId := this.Ctx.Input.Params[":objectId"]
	models.Delete(objectId)
	this.Data["json"] = "delete success!"
	this.ServeJson()
}

`

var apiTests = `package test

import (
	"net/http"
	"net/http/httptest"
	"testing"
	"runtime"
	"path/filepath"
	_ "{{.Appname}}/routers"

	"github.com/astaxie/beego"
	. "github.com/smartystreets/goconvey/convey"
)

func init() {
	_, file, _, _ := runtime.Caller(1)
	apppath, _ := filepath.Abs(filepath.Dir(filepath.Join(file, "../")))
	beego.TestBeegoInit(apppath)
}

// TestGet is a sample to run an endpoint test
func TestGet(t *testing.T) {
	r, _ := http.NewRequest("GET", "/object", nil)
	w := httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)

	beego.Trace("testing", "TestGet", "Code[%d]\n%s", w.Code, w.Body.String())

	Convey("Subject: Test Station Endpoint\n", t, func() {
	        Convey("Status Code Should Be 200", func() {
	                So(w.Code, ShouldEqual, 200)
	        })
	        Convey("The Result Should Not Be Empty", func() {
	                So(w.Body.Len(), ShouldBeGreaterThan, 0)
	        })
	})
}

`

func init() {
	cmdApiapp.Run = createapi
}

func createapi(cmd *Command, args []string) {
	if len(args) != 1 {
		fmt.Println("error args")
		os.Exit(2)
	}
	apppath, packpath, err := checkEnv(args[0])
	if err != nil {
		fmt.Println(err)
		os.Exit(2)
	}
	os.MkdirAll(apppath, 0755)
	fmt.Println("create app folder:", apppath)
	os.Mkdir(path.Join(apppath, "conf"), 0755)
	fmt.Println("create conf:", path.Join(apppath, "conf"))
	os.Mkdir(path.Join(apppath, "controllers"), 0755)
	fmt.Println("create controllers:", path.Join(apppath, "controllers"))
	os.Mkdir(path.Join(apppath, "models"), 0755)
	fmt.Println(path.Join(apppath, "routers") + string(path.Separator))
	os.Mkdir(path.Join(apppath, "routers"), 0755)
	fmt.Println("create models:", path.Join(apppath, "models"))
	os.Mkdir(path.Join(apppath, "tests"), 0755)
	fmt.Println("create tests:", path.Join(apppath, "tests"))

	fmt.Println("create conf app.conf:", path.Join(apppath, "conf", "app.conf"))
	writetofile(path.Join(apppath, "conf", "app.conf"),
		strings.Replace(apiconf, "{{.Appname}}", args[0], -1))

	fmt.Println("create controllers default.go:", path.Join(apppath, "controllers", "default.go"))
	writetofile(path.Join(apppath, "controllers", "default.go"),
		strings.Replace(apiControllers, "{{.Appname}}", packpath, -1))

	fmt.Println("create tests default.go:", path.Join(apppath, "tests", "default_test.go"))
	writetofile(path.Join(apppath, "tests", "default_test.go"),
		strings.Replace(apiTests, "{{.Appname}}", packpath, -1))

	fmt.Println("create routers router.go:", path.Join(apppath, "routers", "router.go"))
	writetofile(path.Join(apppath, "routers", "router.go"),
		strings.Replace(apirouter, "{{.Appname}}", packpath, -1))

	fmt.Println("create models object.go:", path.Join(apppath, "models", "object.go"))
	writetofile(path.Join(apppath, "models", "object.go"), apiModels)

	fmt.Println("create main.go:", path.Join(apppath, "main.go"))
	writetofile(path.Join(apppath, "main.go"),
		strings.Replace(apiMaingo, "{{.Appname}}", packpath, -1))
}

func checkEnv(appname string) (apppath, packpath string, err error) {
	curpath, err := os.Getwd()
	if err != nil {
		return
	}

	gopath := os.Getenv("GOPATH")
	Debugf("gopath:%s", gopath)
	if gopath == "" {
		err = fmt.Errorf("you should set GOPATH in the env")
		return
	}

	appsrcpath := ""
	haspath := false
	wgopath := path.SplitList(gopath)
	for _, wg := range wgopath {
		wg = path.Join(wg, "src")

		if path.HasPrefix(strings.ToLower(curpath), strings.ToLower(wg)) {
			haspath = true
			appsrcpath = wg
			break
		}
	}

	if !haspath {
		err = fmt.Errorf("can't create application outside of GOPATH `%s`\n"+
			"you first should `cd $GOPATH%ssrc` then use create\n", gopath, string(path.Separator))
		return
	}
	apppath = path.Join(curpath, appname)

	if _, e := os.Stat(apppath); os.IsNotExist(e) == false {
		err = fmt.Errorf("path `%s` exists, can not create app without remove it\n", apppath)
		return
	}
	packpath = strings.Join(strings.Split(apppath[len(appsrcpath)+1:], string(path.Separator)), "/")
	return
}
<commit_msg>bee fix the path.EvalSymlinks<commit_after>// Copyright 2013 bee authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package main

import (
	"fmt"
	"os"
	path "path/filepath"
	"strings"
)

var cmdApiapp = &Command{
	// CustomFlags: true,
	UsageLine: "api [appname]",
	Short:     "create an api application base on beego framework",
	Long: `
create an api application base on beego framework

In the current path, will create a folder named [appname]

In the appname folder has the follow struct:

	├── conf
	│   └── app.conf
	├── controllers
	│   └── default.go
	├── routers
	│   └── router.go
	├── tests
	│   └── default_test.go
	├── main.go
	└── models
	    └── object.go

`,
}

var apiconf = `
appname = {{.Appname}}
httpport = 8080
runmode = dev
autorender = false
copyrequestbody = true
`
var apiMaingo = `package main

import (
	_ "{{.Appname}}/routers"
	"github.com/astaxie/beego"
)

//		Objects

//	URL					HTTP Verb				Functionality
//	/object				POST					Creating Objects
//	/object/<objectId>	GET						Retrieving Objects
//	/object/<objectId>	PUT						Updating Objects
//	/object				GET						Queries
//	/object/<objectId>	DELETE					Deleting Objects

func main() {
	beego.Run()
}
`
var apirouter = `package routers

import (
	"{{.Appname}}/controllers"
	"github.com/astaxie/beego"
)

func init() {
    beego.RESTRouter("/object", &controllers.ObjectController{})
}
`

var apiModels = `package models

import (
	"errors"
	"strconv"
	"time"
)

var (
	Objects map[string]*Object
)

type Object struct {
	ObjectId   string
	Score      int64
	PlayerName string
}

func init() {
	Objects = make(map[string]*Object)
	Objects["hjkhsbnmn123"] = &Object{"hjkhsbnmn123", 100, "astaxie"}
	Objects["mjjkxsxsaa23"] = &Object{"mjjkxsxsaa23", 101, "someone"}
}

func AddOne(object Object) (ObjectId string) {
	object.ObjectId = "astaxie" + strconv.FormatInt(time.Now().UnixNano(), 10)
	Objects[object.ObjectId] = &object
	return object.ObjectId
}

func GetOne(ObjectId string) (object *Object, err error) {
	if v, ok := Objects[ObjectId]; ok {
		return v, nil
	}
	return nil, errors.New("ObjectId Not Exist")
}

func GetAll() map[string]*Object {
	return Objects
}

func Update(ObjectId string, Score int64) (err error) {
	if v, ok := Objects[ObjectId]; ok {
		v.Score = Score
		return nil
	}
	return errors.New("ObjectId Not Exist")
}

func Delete(ObjectId string) {
	delete(Objects, ObjectId)
}
`

var apiControllers = `package controllers

import (
	"encoding/json"
	"github.com/astaxie/beego"
	"{{.Appname}}/models"
)

type ResponseInfo struct {
}

type ObjectController struct {
	beego.Controller
}

func (this *ObjectController) Post() {
	var ob models.Object
	json.Unmarshal(this.Ctx.Input.RequestBody, &ob)
	objectid := models.AddOne(ob)
	this.Data["json"] = map[string]string{"ObjectId": objectid}
	this.ServeJson()
}

func (this *ObjectController) Get() {
	objectId := this.Ctx.Input.Params[":objectId"]
	if objectId != "" {
		ob, err := models.GetOne(objectId)
		if err != nil {
			this.Data["json"] = err
		} else {
			this.Data["json"] = ob
		}
	} else {
		obs := models.GetAll()
		this.Data["json"] = obs
	}
	this.ServeJson()
}

func (this *ObjectController) Put() {
	objectId := this.Ctx.Input.Params[":objectId"]
	var ob models.Object
	json.Unmarshal(this.Ctx.Input.RequestBody, &ob)

	err := models.Update(objectId, ob.Score)
	if err != nil {
		this.Data["json"] = err
	} else {
		this.Data["json"] = "update success!"
	}
	this.ServeJson()
}

func (this *ObjectController) Delete() {
	objectId := this.Ctx.Input.Params[":objectId"]
	models.Delete(objectId)
	this.Data["json"] = "delete success!"
	this.ServeJson()
}

`

var apiTests = `package test

import (
	"net/http"
	"net/http/httptest"
	"testing"
	"runtime"
	"path/filepath"
	_ "{{.Appname}}/routers"

	"github.com/astaxie/beego"
	. "github.com/smartystreets/goconvey/convey"
)

func init() {
	_, file, _, _ := runtime.Caller(1)
	apppath, _ := filepath.Abs(filepath.Dir(filepath.Join(file, "../")))
	beego.TestBeegoInit(apppath)
}

// TestGet is a sample to run an endpoint test
func TestGet(t *testing.T) {
	r, _ := http.NewRequest("GET", "/object", nil)
	w := httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)

	beego.Trace("testing", "TestGet", "Code[%d]\n%s", w.Code, w.Body.String())

	Convey("Subject: Test Station Endpoint\n", t, func() {
	        Convey("Status Code Should Be 200", func() {
	                So(w.Code, ShouldEqual, 200)
	        })
	        Convey("The Result Should Not Be Empty", func() {
	                So(w.Body.Len(), ShouldBeGreaterThan, 0)
	        })
	})
}

`

func init() {
	cmdApiapp.Run = createapi
}

func createapi(cmd *Command, args []string) {
	if len(args) != 1 {
		fmt.Println("error args")
		os.Exit(2)
	}
	apppath, packpath, err := checkEnv(args[0])
	if err != nil {
		fmt.Println(err)
		os.Exit(2)
	}
	os.MkdirAll(apppath, 0755)
	fmt.Println("create app folder:", apppath)
	os.Mkdir(path.Join(apppath, "conf"), 0755)
	fmt.Println("create conf:", path.Join(apppath, "conf"))
	os.Mkdir(path.Join(apppath, "controllers"), 0755)
	fmt.Println("create controllers:", path.Join(apppath, "controllers"))
	os.Mkdir(path.Join(apppath, "models"), 0755)
	fmt.Println(path.Join(apppath, "routers") + string(path.Separator))
	os.Mkdir(path.Join(apppath, "routers"), 0755)
	fmt.Println("create models:", path.Join(apppath, "models"))
	os.Mkdir(path.Join(apppath, "tests"), 0755)
	fmt.Println("create tests:", path.Join(apppath, "tests"))

	fmt.Println("create conf app.conf:", path.Join(apppath, "conf", "app.conf"))
	writetofile(path.Join(apppath, "conf", "app.conf"),
		strings.Replace(apiconf, "{{.Appname}}", args[0], -1))

	fmt.Println("create controllers default.go:", path.Join(apppath, "controllers", "default.go"))
	writetofile(path.Join(apppath, "controllers", "default.go"),
		strings.Replace(apiControllers, "{{.Appname}}", packpath, -1))

	fmt.Println("create tests default.go:", path.Join(apppath, "tests", "default_test.go"))
	writetofile(path.Join(apppath, "tests", "default_test.go"),
		strings.Replace(apiTests, "{{.Appname}}", packpath, -1))

	fmt.Println("create routers router.go:", path.Join(apppath, "routers", "router.go"))
	writetofile(path.Join(apppath, "routers", "router.go"),
		strings.Replace(apirouter, "{{.Appname}}", packpath, -1))

	fmt.Println("create models object.go:", path.Join(apppath, "models", "object.go"))
	writetofile(path.Join(apppath, "models", "object.go"), apiModels)

	fmt.Println("create main.go:", path.Join(apppath, "main.go"))
	writetofile(path.Join(apppath, "main.go"),
		strings.Replace(apiMaingo, "{{.Appname}}", packpath, -1))
}

func checkEnv(appname string) (apppath, packpath string, err error) {
	curpath, err := os.Getwd()
	if err != nil {
		return
	}

	gopath := os.Getenv("GOPATH")
	Debugf("gopath:%s", gopath)
	if gopath == "" {
		err = fmt.Errorf("you should set GOPATH in the env")
		return
	}

	appsrcpath := ""
	haspath := false
	wgopath := path.SplitList(gopath)
	for _, wg := range wgopath {
		wg, _ = path.EvalSymlinks(path.Join(wg, "src"))

		if path.HasPrefix(strings.ToLower(curpath), strings.ToLower(wg)) {
			haspath = true
			appsrcpath = wg
			break
		}
	}

	if !haspath {
		err = fmt.Errorf("can't create application outside of GOPATH `%s`\n"+
			"you first should `cd $GOPATH%ssrc` then use create\n", gopath, string(path.Separator))
		return
	}
	apppath = path.Join(curpath, appname)

	if _, e := os.Stat(apppath); os.IsNotExist(e) == false {
		err = fmt.Errorf("path `%s` exists, can not create app without remove it\n", apppath)
		return
	}
	packpath = strings.Join(strings.Split(apppath[len(appsrcpath)+1:], string(path.Separator)), "/")
	return
}
<|endoftext|>


<commit_before>/*
Copyright 2015 Home Office 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.
*/

package main

import (
	"errors"
	"fmt"
	"os"
	"strconv"
	"strings"
	"time"
)

// VaultResources is a collection of type resource
type VaultResources struct {
	// an array of resource to retrieve
	items []*VaultResource
}

// Set is the implementation for the parser
// secret:test:file=filename.test,fmt=yaml
func (r *VaultResources) Set(value string) error {
	rn := defaultVaultResource()

	// step: split on the ':'
	items := strings.Split(os.ExpandEnv(value), ":")
	if len(items) < 2 {
		return fmt.Errorf("invalid resource, must have at least two sections TYPE:PATH")
	}
	if len(items) > 3 {
		return fmt.Errorf("invalid resource, can only has three sections, TYPE:PATH[:OPTIONS]")
	}
	if items[0] == "" || items[1] == "" {
		return fmt.Errorf("invalid resource, neither type or path can be empty")
	}

	// step: extract the elements
	rn.resource = items[0]
	rn.path = items[1]
	rn.options = make(map[string]string, 0)

	// step: extract any options
	if len(items) > 2 {
		for _, x := range strings.Split(items[2], ",") {
			kp := strings.Split(x, "=")
			if len(kp) != 2 {
				return fmt.Errorf("invalid resource option: %s, must be KEY=VALUE", x)
			}
			if kp[1] == "" {
				return fmt.Errorf("invalid resource option: %s, must have a value", x)
			}
			// step: set the name and value
			name := strings.TrimSpace(kp[0])
			value := strings.Replace(kp[1], "|", ",", -1)

			// step: extract the control options from the path resource parameters
			switch name {
			case optionMode:
				if !strings.HasPrefix(value, "0") {
					value = "0" + value
				}
				if len(value) != 4 {
					return errors.New("the file permission invalid, should be octal 0444 or alike")
				}
				v, err := strconv.ParseUint(value, 0, 32)
				if err != nil {
					return errors.New("invalid file permissions on resource")
				}
				rn.fileMode = os.FileMode(v)
			case optionFormat:
				if matched := resourceFormatRegex.MatchString(value); !matched {
					return fmt.Errorf("unsupported output format: %s", value)
				}
				rn.format = value
			case optionUpdate:
				duration, err := time.ParseDuration(value)
				if err != nil {
					return fmt.Errorf("update option: %s is not value, should be a duration format", value)
				}
				rn.update = duration
			case optionRevoke:
				choice, err := strconv.ParseBool(value)
				if err != nil {
					return fmt.Errorf("the revoke option: %s is invalid, should be a boolean", value)
				}
				rn.revoked = choice
			case optionsRevokeDelay:
				duration, err := time.ParseDuration(value)
				if err != nil {
					return fmt.Errorf("the revoke delay option: %s is not value, should be a duration format", value)
				}
				rn.revokeDelay = duration
			case optionRenewal:
				choice, err := strconv.ParseBool(value)
				if err != nil {
					return fmt.Errorf("the renewal option: %s is invalid, should be a boolean", value)
				}
				rn.renewable = choice
			case optionCreate:
				choice, err := strconv.ParseBool(value)
				if err != nil {
					return fmt.Errorf("the create option: %s is invalid, should be a boolean", value)
				}
				if rn.resource != "secret" {
					return fmt.Errorf("the create option is only supported for 'cn=secret' at this time")
				}
				rn.create = choice
			case optionSize:
				size, err := strconv.ParseInt(value, 10, 16)
				if err != nil {
					return fmt.Errorf("the size option: %s is invalid, should be an integer", value)
				}
				rn.size = size
			case optionExec:
				rn.execPath = value
			case optionFilename:
				rn.filename = value
			case optionTemplatePath:
				rn.templateFile = value
			case optionMaxRetries:
				maxRetries, err := strconv.ParseInt(value, 10, 32)
				if err != nil {
					return fmt.Errorf("the retries option: %s is invalid, should be an integer", value)
				}
				rn.maxRetries = int(maxRetries)
			case optionMaxJitter:
				maxJitter, err := time.ParseDuration(value)
				if err != nil {
					return fmt.Errorf("the jitter option: %s is invalid, should be in duration format", value)
				}
				rn.maxJitter = maxJitter
			default:
				rn.options[name] = value
			}
		}
	}
	// step: append to the list of resources
	r.items = append(r.items, rn)

	return nil
}

// String returns a string representation of the struct
func (r VaultResources) String() string {
	return ""
}
<commit_msg>allow overriding separator string<commit_after>/*
Copyright 2015 Home Office 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.
*/

package main

import (
	"errors"
	"fmt"
	"os"
	"strconv"
	"strings"
	"time"
)

// VaultResources is a collection of type resource
type VaultResources struct {
	// an array of resource to retrieve
	items []*VaultResource
}

// Set is the implementation for the parser
// secret:test:file=filename.test,fmt=yaml
func (r *VaultResources) Set(value string) error {
	rn := defaultVaultResource()

	// step: split on the separator, default ':'
	sep := getEnv("VAULT_SIDEKICK_SEPARATOR", ":")
	items := strings.Split(os.ExpandEnv(value), sep)
	if len(items) < 2 {
		return fmt.Errorf("invalid resource, must have at least two sections TYPE:PATH")
	}
	if len(items) > 3 {
		return fmt.Errorf("invalid resource, can only has three sections, TYPE:PATH[:OPTIONS]")
	}
	if items[0] == "" || items[1] == "" {
		return fmt.Errorf("invalid resource, neither type or path can be empty")
	}

	// step: extract the elements
	rn.resource = items[0]
	rn.path = items[1]
	rn.options = make(map[string]string, 0)

	// step: extract any options
	if len(items) > 2 {
		for _, x := range strings.Split(items[2], ",") {
			kp := strings.Split(x, "=")
			if len(kp) != 2 {
				return fmt.Errorf("invalid resource option: %s, must be KEY=VALUE", x)
			}
			if kp[1] == "" {
				return fmt.Errorf("invalid resource option: %s, must have a value", x)
			}
			// step: set the name and value
			name := strings.TrimSpace(kp[0])
			value := strings.Replace(kp[1], "|", ",", -1)

			// step: extract the control options from the path resource parameters
			switch name {
			case optionMode:
				if !strings.HasPrefix(value, "0") {
					value = "0" + value
				}
				if len(value) != 4 {
					return errors.New("the file permission invalid, should be octal 0444 or alike")
				}
				v, err := strconv.ParseUint(value, 0, 32)
				if err != nil {
					return errors.New("invalid file permissions on resource")
				}
				rn.fileMode = os.FileMode(v)
			case optionFormat:
				if matched := resourceFormatRegex.MatchString(value); !matched {
					return fmt.Errorf("unsupported output format: %s", value)
				}
				rn.format = value
			case optionUpdate:
				duration, err := time.ParseDuration(value)
				if err != nil {
					return fmt.Errorf("update option: %s is not value, should be a duration format", value)
				}
				rn.update = duration
			case optionRevoke:
				choice, err := strconv.ParseBool(value)
				if err != nil {
					return fmt.Errorf("the revoke option: %s is invalid, should be a boolean", value)
				}
				rn.revoked = choice
			case optionsRevokeDelay:
				duration, err := time.ParseDuration(value)
				if err != nil {
					return fmt.Errorf("the revoke delay option: %s is not value, should be a duration format", value)
				}
				rn.revokeDelay = duration
			case optionRenewal:
				choice, err := strconv.ParseBool(value)
				if err != nil {
					return fmt.Errorf("the renewal option: %s is invalid, should be a boolean", value)
				}
				rn.renewable = choice
			case optionCreate:
				choice, err := strconv.ParseBool(value)
				if err != nil {
					return fmt.Errorf("the create option: %s is invalid, should be a boolean", value)
				}
				if rn.resource != "secret" {
					return fmt.Errorf("the create option is only supported for 'cn=secret' at this time")
				}
				rn.create = choice
			case optionSize:
				size, err := strconv.ParseInt(value, 10, 16)
				if err != nil {
					return fmt.Errorf("the size option: %s is invalid, should be an integer", value)
				}
				rn.size = size
			case optionExec:
				rn.execPath = value
			case optionFilename:
				rn.filename = value
			case optionTemplatePath:
				rn.templateFile = value
			case optionMaxRetries:
				maxRetries, err := strconv.ParseInt(value, 10, 32)
				if err != nil {
					return fmt.Errorf("the retries option: %s is invalid, should be an integer", value)
				}
				rn.maxRetries = int(maxRetries)
			case optionMaxJitter:
				maxJitter, err := time.ParseDuration(value)
				if err != nil {
					return fmt.Errorf("the jitter option: %s is invalid, should be in duration format", value)
				}
				rn.maxJitter = maxJitter
			default:
				rn.options[name] = value
			}
		}
	}
	// step: append to the list of resources
	r.items = append(r.items, rn)

	return nil
}

// String returns a string representation of the struct
func (r VaultResources) String() string {
	return ""
}
<|endoftext|>