<commit_before>package s3util

import (
	"bytes"
	"encoding/xml"
	"github.com/kr/s3"
	"io"
	"net/http"
	"net/url"
	"strconv"
	"sync"
	"syscall"
	"time"
)

// defined by amazon
const (
	minPartSize = 5 * 1024 * 1024
	maxPartSize = 1<<31 - 1 // for 32-bit use; amz max is 5GiB
	maxObjSize  = 5 * 1024 * 1024 * 1024 * 1024
	maxNPart    = 10000
)

const (
	concurrency = 5
	nTry        = 2
)

type part struct {
	r   io.ReadSeeker
	len int64

	// read by xml encoder
	PartNumber int
	ETag       string
}

type Uploader struct {
	s3       s3.Service
	keys     s3.Keys
	url      string
	client   *http.Client
	UploadId string // written by xml decoder

	bufsz           int64
	buf             []byte
	off             int
	ch              chan *part
	part            int
	closed          bool
	Err             error
	wg              sync.WaitGroup
	metricsCallback MetricsCallbackFunc

	xml struct {
		XMLName string `xml:"CompleteMultipartUpload"`
		Part    []*part
	}
}

// Create creates an S3 object at url and sends multipart upload requests as
// data is written.
//
// If h is not nil, each of its entries is added to the HTTP request header.
// If c is nil, Create uses DefaultConfig.
func Create(url string, h http.Header, c *Config) (io.WriteCloser, error) {
	if c == nil {
		c = DefaultConfig
	}
	return newUploader(url, h, c)
}

// Sends an S3 multipart upload initiation request.
// See http://docs.amazonwebservices.com/AmazonS3/latest/dev/mpuoverview.html.
// This initial request returns an UploadId that we use to identify
// subsequent PUT requests.
func newUploader(url string, h http.Header, c *Config) (u *Uploader, err error) {
	u = new(Uploader)
	u.s3 = *c.Service
	u.url = url
	u.keys = *c.Keys
	u.client = c.Client
	u.metricsCallback = c.MetricsCallback
	if u.client == nil {
		u.client = http.DefaultClient
	}
	u.bufsz = minPartSize
	r, err := http.NewRequest("POST", url+"?uploads", nil)
	if err != nil {
		return nil, err
	}
	r.Header.Set("Date", time.Now().UTC().Format(http.TimeFormat))
	for k := range h {
		for _, v := range h[k] {
			r.Header.Add(k, v)
		}
	}
	u.s3.Sign(r, u.keys)
	resp, err := u.client.Do(r)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return nil, newRespError(resp)
	}
	err = xml.NewDecoder(resp.Body).Decode(u)
	if err != nil {
		return nil, err
	}
	u.ch = make(chan *part)
	for i := 0; i < concurrency; i++ {
		go u.worker()
	}
	return u, nil
}

func (u *Uploader) Write(p []byte) (n int, err error) {
	if u.closed {
		return 0, syscall.EINVAL
	}
	if u.Err != nil {
		return 0, u.Err
	}
	for n < len(p) {
		if cap(u.buf) == 0 {
			u.buf = make([]byte, int(u.bufsz))
			// Increase part size (1.001x).
			// This lets us reach the max object size (5TiB) while
			// still doing minimal buffering for small objects.
			u.bufsz = min(u.bufsz+u.bufsz/1000, maxPartSize)
		}
		r := copy(u.buf[u.off:], p[n:])
		u.off += r
		n += r
		if u.off == len(u.buf) {
			u.flush()
		}
	}
	return n, nil
}

func (u *Uploader) flush() {
	u.wg.Add(1)
	u.part++
	p := &part{bytes.NewReader(u.buf[:u.off]), int64(u.off), u.part, ""}
	u.xml.Part = append(u.xml.Part, p)
	u.ch <- p
	u.buf, u.off = nil, 0
}

func (u *Uploader) worker() {
	for p := range u.ch {
		u.retryUploadPart(p)
	}
}

// Calls putPart up to nTry times to recover from transient errors.
func (u *Uploader) retryUploadPart(p *part) {
	defer u.wg.Done()
	defer func() { p.r = nil }() // free the large buffer
	var err error
	for i := 0; i < nTry; i++ {
		p.r.Seek(0, 0)
		err = u.putPart(p)
		if err == nil {
			return
		}
	}
	u.Err = err
}

// Uploads part p, reading its contents from p.r.
// Stores the ETag in p.ETag.
func (u *Uploader) putPart(p *part) error {
	v := url.Values{}
	v.Set("partNumber", strconv.Itoa(p.PartNumber))
	v.Set("uploadId", u.UploadId)
	req, err := http.NewRequest("PUT", u.url+"?"+v.Encode(), p.r)
	if err != nil {
		return err
	}
	req.ContentLength = p.len
	req.Header.Set("Date", time.Now().UTC().Format(http.TimeFormat))
	u.s3.Sign(req, u.keys)
	start := time.Now()
	resp, err := u.client.Do(req)
	end := time.Now()
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return newRespError(resp)
	}

	if u.metricsCallback != nil {
		u.metricsCallback(
			Metrics{
				TotalBytes: uint64(p.len),
				TotalTime:  end.Sub(start),
			})
	}

	s := resp.Header.Get("etag") // includes quote chars for some reason
	p.ETag = s[1 : len(s)-1]
	return nil
}

func (u *Uploader) prepareClose() error {
	if u.closed {
		return syscall.EINVAL
	}
	if cap(u.buf) > 0 {
		u.flush()
	}
	u.wg.Wait()
	close(u.ch)
	u.closed = true
	if u.Err != nil {
		u.abort()
		return u.Err
	}
	return nil
}

func (u *Uploader) Close() error {
	if err := u.prepareClose(); err != nil {
		return err
	}
	body, err := xml.Marshal(u.xml)
	if err != nil {
		return err
	}
	b := bytes.NewBuffer(body)
	v := url.Values{}
	v.Set("uploadId", u.UploadId)

	req, err := http.NewRequest("POST", u.url+"?"+v.Encode(), b)
	if err != nil {
		return err
	}

	var finalError error
	for retries := 0; retries < 3; retries++ {
		req.Header.Set("Date", time.Now().UTC().Format(http.TimeFormat))
		u.s3.Sign(req, u.keys)
		resp, err := u.client.Do(req)
		if err != nil {
			finalError = err
			continue
		}
		if resp.StatusCode != 200 {
			finalError = newRespError(resp)
			continue
		}
		resp.Body.Close()
		return nil
	}
	return finalError
}

func (u *Uploader) abort() {
	// TODO(kr): devise a reasonable way to report an error here in addition
	// to the error that caused the abort.
	v := url.Values{}
	v.Set("uploadId", u.UploadId)
	s := u.url + "?" + v.Encode()
	req, err := http.NewRequest("DELETE", s, nil)
	if err != nil {
		return
	}
	req.Header.Set("Date", time.Now().UTC().Format(http.TimeFormat))
	u.s3.Sign(req, u.keys)
	resp, err := u.client.Do(req)
	if err != nil {
		return
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return
	}
}

func min(a, b int64) int64 {
	if a < b {
		return a
	}
	return b
}
<commit_msg>[#26783240431314] Allow response for multipart upload to be read.<commit_after>package s3util

import (
	"bytes"
	"encoding/xml"
	"github.com/kr/s3"
	"io"
	"net/http"
	"net/url"
	"strconv"
	"sync"
	"syscall"
	"time"
)

// defined by amazon
const (
	minPartSize = 5 * 1024 * 1024
	maxPartSize = 1<<31 - 1 // for 32-bit use; amz max is 5GiB
	maxObjSize  = 5 * 1024 * 1024 * 1024 * 1024
	maxNPart    = 10000
)

const (
	concurrency = 5
	nTry        = 2
)

type part struct {
	r   io.ReadSeeker
	len int64

	// read by xml encoder
	PartNumber int
	ETag       string
}

type Uploader struct {
	s3       s3.Service
	keys     s3.Keys
	url      string
	client   *http.Client
	UploadId string // written by xml decoder

	bufsz           int64
	buf             []byte
	off             int
	ch              chan *part
	part            int
	closed          bool
	Err             error
	wg              sync.WaitGroup
	metricsCallback MetricsCallbackFunc

	xml struct {
		XMLName string `xml:"CompleteMultipartUpload"`
		Part    []*part
	}
}

// Create creates an S3 object at url and sends multipart upload requests as
// data is written.
//
// If h is not nil, each of its entries is added to the HTTP request header.
// If c is nil, Create uses DefaultConfig.
func Create(url string, h http.Header, c *Config) (io.WriteCloser, error) {
	if c == nil {
		c = DefaultConfig
	}
	return newUploader(url, h, c)
}

// Sends an S3 multipart upload initiation request.
// See http://docs.amazonwebservices.com/AmazonS3/latest/dev/mpuoverview.html.
// This initial request returns an UploadId that we use to identify
// subsequent PUT requests.
func newUploader(url string, h http.Header, c *Config) (u *Uploader, err error) {
	u = new(Uploader)
	u.s3 = *c.Service
	u.url = url
	u.keys = *c.Keys
	u.client = c.Client
	u.metricsCallback = c.MetricsCallback
	if u.client == nil {
		u.client = http.DefaultClient
	}
	u.bufsz = minPartSize
	r, err := http.NewRequest("POST", url+"?uploads", nil)
	if err != nil {
		return nil, err
	}
	r.Header.Set("Date", time.Now().UTC().Format(http.TimeFormat))
	for k := range h {
		for _, v := range h[k] {
			r.Header.Add(k, v)
		}
	}
	u.s3.Sign(r, u.keys)
	resp, err := u.client.Do(r)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return nil, newRespError(resp)
	}
	err = xml.NewDecoder(resp.Body).Decode(u)
	if err != nil {
		return nil, err
	}
	u.ch = make(chan *part)
	for i := 0; i < concurrency; i++ {
		go u.worker()
	}
	return u, nil
}

func (u *Uploader) Write(p []byte) (n int, err error) {
	if u.closed {
		return 0, syscall.EINVAL
	}
	if u.Err != nil {
		return 0, u.Err
	}
	for n < len(p) {
		if cap(u.buf) == 0 {
			u.buf = make([]byte, int(u.bufsz))
			// Increase part size (1.001x).
			// This lets us reach the max object size (5TiB) while
			// still doing minimal buffering for small objects.
			u.bufsz = min(u.bufsz+u.bufsz/1000, maxPartSize)
		}
		r := copy(u.buf[u.off:], p[n:])
		u.off += r
		n += r
		if u.off == len(u.buf) {
			u.flush()
		}
	}
	return n, nil
}

func (u *Uploader) flush() {
	u.wg.Add(1)
	u.part++
	p := &part{bytes.NewReader(u.buf[:u.off]), int64(u.off), u.part, ""}
	u.xml.Part = append(u.xml.Part, p)
	u.ch <- p
	u.buf, u.off = nil, 0
}

func (u *Uploader) worker() {
	for p := range u.ch {
		u.retryUploadPart(p)
	}
}

// Calls putPart up to nTry times to recover from transient errors.
func (u *Uploader) retryUploadPart(p *part) {
	defer u.wg.Done()
	defer func() { p.r = nil }() // free the large buffer
	var err error
	for i := 0; i < nTry; i++ {
		p.r.Seek(0, 0)
		err = u.putPart(p)
		if err == nil {
			return
		}
	}
	u.Err = err
}

// Uploads part p, reading its contents from p.r.
// Stores the ETag in p.ETag.
func (u *Uploader) putPart(p *part) error {
	v := url.Values{}
	v.Set("partNumber", strconv.Itoa(p.PartNumber))
	v.Set("uploadId", u.UploadId)
	req, err := http.NewRequest("PUT", u.url+"?"+v.Encode(), p.r)
	if err != nil {
		return err
	}
	req.ContentLength = p.len
	req.Header.Set("Date", time.Now().UTC().Format(http.TimeFormat))
	u.s3.Sign(req, u.keys)
	start := time.Now()
	resp, err := u.client.Do(req)
	end := time.Now()
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return newRespError(resp)
	}

	if u.metricsCallback != nil {
		u.metricsCallback(
			Metrics{
				TotalBytes: uint64(p.len),
				TotalTime:  end.Sub(start),
			})
	}

	s := resp.Header.Get("etag") // includes quote chars for some reason
	p.ETag = s[1 : len(s)-1]
	return nil
}

func (u *Uploader) prepareClose() error {
	if u.closed {
		return syscall.EINVAL
	}
	if cap(u.buf) > 0 {
		u.flush()
	}
	u.wg.Wait()
	close(u.ch)
	u.closed = true
	if u.Err != nil {
		u.abort()
		return u.Err
	}
	return nil
}

func (u *Uploader) Close() error {
	resp, err := u.close()
	resp.Body.Close()
	return err
}

// It's the caller's responsibility to close the response, if any.
func (u *Uploader) CloseWithResponse() (*http.Response, error) {
	return u.close()
}

func (u *Uploader) close() (*http.Response, error) {
	if err := u.prepareClose(); err != nil {
		return nil, err
	}
	body, err := xml.Marshal(u.xml)
	if err != nil {
		return nil, err
	}
	b := bytes.NewBuffer(body)
	v := url.Values{}
	v.Set("uploadId", u.UploadId)

	req, err := http.NewRequest("POST", u.url+"?"+v.Encode(), b)
	if err != nil {
		return nil, err
	}

	var finalError error
	for retries := 0; retries < 3; retries++ {
		req.Header.Set("Date", time.Now().UTC().Format(http.TimeFormat))
		u.s3.Sign(req, u.keys)
		resp, err := u.client.Do(req)
		if err != nil {
			finalError = err
			continue
		}
		if resp.StatusCode != 200 {
			finalError = newRespError(resp)
			continue
		}
		return resp, nil
	}
	return nil, finalError
}

func (u *Uploader) abort() {
	// TODO(kr): devise a reasonable way to report an error here in addition
	// to the error that caused the abort.
	v := url.Values{}
	v.Set("uploadId", u.UploadId)
	s := u.url + "?" + v.Encode()
	req, err := http.NewRequest("DELETE", s, nil)
	if err != nil {
		return
	}
	req.Header.Set("Date", time.Now().UTC().Format(http.TimeFormat))
	u.s3.Sign(req, u.keys)
	resp, err := u.client.Do(req)
	if err != nil {
		return
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return
	}
}

func min(a, b int64) int64 {
	if a < b {
		return a
	}
	return b
}
<|endoftext|>


<commit_before>package sack

import (
	"fmt"
	"github.com/codegangsta/cli"
)

func shellInit(c *cli.Context) {
	sh := `
    sack=$(which sack)

    alias S="${sack} -s"
    alias F="${sack} -e"
    `

	fmt.Println(sh)
}

func shellEval(c *cli.Context) {
	sh := "eval \"$(sack init)\""
	fmt.Println(sh)
}

/*
// TODO: Add bash and zsh autocomplete

    _cli_bash_autocomplete() {
        local cur prev opts base
        COMPREPLY=()
        cur="${COMP_WORDS[COMP_CWORD]}"
        prev="${COMP_WORDS[COMP_CWORD-1]}"
        opts=$( ${COMP_WORDS[@]:0:COMP_CWORD} --generate-bash-completion )
        COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
        return 0
    }

    complete -F _cli_bash_autocomplete $PROG
*/
<commit_msg>Change alias from F to E<commit_after>package sack

import (
	"fmt"
	"github.com/codegangsta/cli"
)

func shellInit(c *cli.Context) {
	sh := `
    sack=$(which sack)

    alias S="${sack} -s"
    alias E="${sack} -e"
    `

	fmt.Println(sh)
}

func shellEval(c *cli.Context) {
	sh := "eval \"$(sack init)\""
	fmt.Println(sh)
}

/*
// TODO: Add bash and zsh autocomplete
CREDIT: https://github.com/codegangsta/cli/blob/master/autocomplete/bash_autocomplete
    _cli_bash_autocomplete() {
        local cur prev opts base
        COMPREPLY=()
        cur="${COMP_WORDS[COMP_CWORD]}"
        prev="${COMP_WORDS[COMP_CWORD-1]}"
        opts=$( ${COMP_WORDS[@]:0:COMP_CWORD} --generate-bash-completion )
        COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
        return 0
    }

    complete -F _cli_bash_autocomplete $PROG
*/
<|endoftext|>


<commit_before>// Copyright 2017 Gitea. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package migrations

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

	"code.gitea.io/gitea/modules/setting"

	"github.com/go-xorm/xorm"
)

const (
	tplCommentPrefix = `# gitea public key`
	tplPublicKey     = tplCommentPrefix + "\n" + `command="%s serv key-%d --config='%s'",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty %s` + "\n"
)

func useNewPublickeyFormat(x *xorm.Engine) error {
	fpath := filepath.Join(setting.SSH.RootPath, "authorized_keys")
	tmpPath := fpath + ".tmp"
	f, err := os.OpenFile(tmpPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
	if err != nil {
		return err
	}
	defer func() {
		f.Close()
		os.Remove(tmpPath)
	}()

	type PublicKey struct {
		ID      int64
		Content string
	}

	err = x.Iterate(new(PublicKey), func(idx int, bean interface{}) (err error) {
		key := bean.(*PublicKey)
		_, err = f.WriteString(fmt.Sprintf(tplPublicKey, setting.AppPath, key.ID, setting.CustomConf, key.Content))
		return err
	})
	if err != nil {
		return err
	}

	f.Close()
	if err = os.Rename(tmpPath, fpath); err != nil {
		return err
	}
	return nil
}
<commit_msg>fix migration failed when authorized_keys is not exist (#1180)<commit_after>// Copyright 2017 Gitea. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package migrations

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

	"code.gitea.io/gitea/modules/setting"

	"github.com/Unknwon/com"
	"github.com/go-xorm/xorm"
)

const (
	tplCommentPrefix = `# gitea public key`
	tplPublicKey     = tplCommentPrefix + "\n" + `command="%s serv key-%d --config='%s'",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty %s` + "\n"
)

func useNewPublickeyFormat(x *xorm.Engine) error {
	fpath := filepath.Join(setting.SSH.RootPath, "authorized_keys")
	if !com.IsExist(fpath) {
		return nil
	}

	tmpPath := fpath + ".tmp"
	f, err := os.OpenFile(tmpPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
	if err != nil {
		return err
	}
	defer func() {
		f.Close()
		os.Remove(tmpPath)
	}()

	type PublicKey struct {
		ID      int64
		Content string
	}

	err = x.Iterate(new(PublicKey), func(idx int, bean interface{}) (err error) {
		key := bean.(*PublicKey)
		_, err = f.WriteString(fmt.Sprintf(tplPublicKey, setting.AppPath, key.ID, setting.CustomConf, key.Content))
		return err
	})
	if err != nil {
		return err
	}

	f.Close()
	if err = os.Rename(tmpPath, fpath); err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2020 The Jetstack cert-manager contributors.

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 certificate

import (
	"context"
	"errors"
	"fmt"
	"k8s.io/kubectl/pkg/describe"
	"text/tabwriter"
	"time"

	"github.com/spf13/cobra"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/cli-runtime/pkg/genericclioptions"
	"k8s.io/client-go/kubernetes"
	restclient "k8s.io/client-go/rest"
	"k8s.io/client-go/tools/reference"
	cmdutil "k8s.io/kubectl/pkg/cmd/util"
	"k8s.io/kubectl/pkg/util/i18n"
	"k8s.io/kubectl/pkg/util/templates"

	"github.com/jetstack/cert-manager/cmd/ctl/pkg/status/util"
	cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2"
	cmclient "github.com/jetstack/cert-manager/pkg/client/clientset/versioned"
	"github.com/jetstack/cert-manager/pkg/ctl"
	"github.com/jetstack/cert-manager/pkg/util/predicate"
)

var (
	long = templates.LongDesc(i18n.T(`
Get details about the current status of a cert-manager Certificate resource, including information on related resources like CertificateRequest.`))

	example = templates.Examples(i18n.T(`
# Query status of Certificate with name 'my-crt' in namespace 'my-namespace'
kubectl cert-manager status certificate my-crt --namespace my-namespace
`))
)

// Options is a struct to support status certificate command
type Options struct {
	CMClient   cmclient.Interface
	RESTConfig *restclient.Config
	// The Namespace that the Certificate to be queried about resides in.
	// This flag registration is handled by cmdutil.Factory
	Namespace string

	genericclioptions.IOStreams
}

// NewOptions returns initialized Options
func NewOptions(ioStreams genericclioptions.IOStreams) *Options {
	return &Options{
		IOStreams: ioStreams,
	}
}

// NewCmdStatusCert returns a cobra command for status certificate
func NewCmdStatusCert(ioStreams genericclioptions.IOStreams, factory cmdutil.Factory) *cobra.Command {
	o := NewOptions(ioStreams)
	cmd := &cobra.Command{
		Use:     "certificate",
		Short:   "Get details about the current status of a cert-manager Certificate resource",
		Long:    long,
		Example: example,
		Run: func(cmd *cobra.Command, args []string) {
			cmdutil.CheckErr(o.Validate(args))
			cmdutil.CheckErr(o.Complete(factory))
			cmdutil.CheckErr(o.Run(args))
		},
	}
	return cmd
}

// Validate validates the provided options
func (o *Options) Validate(args []string) error {
	if len(args) < 1 {
		return errors.New("the name of the Certificate has to be provided as argument")
	}
	if len(args) > 1 {
		return errors.New("only one argument can be passed in: the name of the Certificate")
	}
	return nil
}

// Complete takes the factory and infers any remaining options.
func (o *Options) Complete(f cmdutil.Factory) error {
	var err error

	o.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace()
	if err != nil {
		return err
	}

	o.RESTConfig, err = f.ToRESTConfig()
	if err != nil {
		return err
	}

	o.CMClient, err = cmclient.NewForConfig(o.RESTConfig)
	if err != nil {
		return err
	}

	return nil
}

// Run executes status certificate command
func (o *Options) Run(args []string) error {
	ctx := context.TODO()
	crtName := args[0]

	clientSet, err := kubernetes.NewForConfig(o.RESTConfig)
	if err != nil {
		return err
	}

	crt, err := o.CMClient.CertmanagerV1alpha2().Certificates(o.Namespace).Get(ctx, crtName, metav1.GetOptions{})
	if err != nil {
		return fmt.Errorf("error when getting Certificate resource: %v", err)
	}

	fmt.Fprintf(o.Out, fmt.Sprintf("Name: %s\nNamespace: %s\n", crt.Name, crt.Namespace))

	fmt.Fprintf(o.Out, fmt.Sprintf("Created at: %s\n", crt.CreationTimestamp.Time.Format(time.RFC3339)))

	// Get necessary info from Certificate
	// Output one line about each type of Condition that is set.
	// Certificate can have multiple Conditions of different types set, e.g. "Ready" or "Issuing"
	conditionMsg := ""
	for _, con := range crt.Status.Conditions {
		conditionMsg += fmt.Sprintf("  %s: %s, Reason: %s, Message: %s\n", con.Type, con.Status, con.Reason, con.Message)
	}
	if conditionMsg == "" {
		conditionMsg = "  No Conditions set\n"
	}
	fmt.Fprintf(o.Out, fmt.Sprintf("Conditions:\n%s", conditionMsg))

	dnsNames := formatStringSlice(crt.Spec.DNSNames)
	fmt.Fprintf(o.Out, fmt.Sprintf("DNS Names:\n%s", dnsNames))

	crtRef, err := reference.GetReference(ctl.Scheme, crt)
	if err != nil {
		return err
	}
	// Ignore error, since if there was an error, crtEvents would be nil and handled down the line in DescribeEvents
	crtEvents, _ := clientSet.CoreV1().Events(o.Namespace).Search(ctl.Scheme, crtRef)
	tabWriter := tabwriter.NewWriter(o.Out, 0, 8, 2, ' ', 0)
	prefixWriter := describe.NewPrefixWriter(tabWriter)
	util.DescribeEvents(crtEvents, prefixWriter, 0)
	tabWriter.Flush()

	issuerKind := crt.Spec.IssuerRef.Kind
	if issuerKind == "" {
		issuerKind = "Issuer"
	}
	// Get info on Issuer/ClusterIssuer
	if crt.Spec.IssuerRef.Group != "cert-manager.io" && crt.Spec.IssuerRef.Group != "" {
		// TODO: Support Issuers/ClusterIssuers from other groups as well
		fmt.Fprintf(o.Out, "The %s %q is not of the group cert-manager.io, this command currently does not support third party issuers.\nTo get more information about %q, try 'kubectl describe'\n",
			issuerKind, crt.Spec.IssuerRef.Name, crt.Spec.IssuerRef.Name)
	} else if issuerKind == "Issuer" {
		issuer, err := o.CMClient.CertmanagerV1alpha2().Issuers(crt.Namespace).Get(ctx, crt.Spec.IssuerRef.Name, metav1.GetOptions{})
		if err != nil {
			fmt.Fprintf(o.Out, "error when getting Issuer: %v\n", err)
		} else {
			fmt.Fprintf(o.Out, issuerInfoString(crt.Spec.IssuerRef.Name, issuerKind, issuer.Status.Conditions))
		}
	} else {
		// ClusterIssuer
		clusterIssuer, err := o.CMClient.CertmanagerV1alpha2().ClusterIssuers().Get(ctx, crt.Spec.IssuerRef.Name, metav1.GetOptions{})
		if err != nil {
			fmt.Fprintf(o.Out, "error when getting ClusterIssuer: %v\n", err)
		} else {
			fmt.Fprintf(o.Out, issuerInfoString(crt.Spec.IssuerRef.Name, issuerKind, clusterIssuer.Status.Conditions))
		}
	}

	fmt.Fprintf(o.Out, fmt.Sprintf("Secret Name: %s\n", crt.Spec.SecretName))

	fmt.Fprintf(o.Out, fmt.Sprintf("Not Before: %s\n", formatTimeString(crt.Status.NotBefore)))
	fmt.Fprintf(o.Out, fmt.Sprintf("Not After: %s\n", formatTimeString(crt.Status.NotAfter)))
	fmt.Fprintf(o.Out, fmt.Sprintf("Renewal Time: %s\n", formatTimeString(crt.Status.RenewalTime)))

	// TODO: What about timing issues? When I query condition it's not ready yet, but then looking for cr it's finished and deleted
	// Try find the CertificateRequest that is owned by crt and has the correct revision
	reqs, err := o.CMClient.CertmanagerV1alpha2().CertificateRequests(o.Namespace).List(ctx, metav1.ListOptions{})
	if err != nil {
		return err
	}
	req, err := findMatchingCR(reqs, crt)
	if err != nil {
		return err
	}
	fmt.Fprintf(o.Out, crInfoString(req))
	if req != nil {
		reqRef, err := reference.GetReference(ctl.Scheme, req)
		if err != nil {
			return err
		}
		// Ignore error, since if there was an error, reqEvents would be nil and handled down the line in DescribeEvents
		reqEvents, _ := clientSet.CoreV1().Events(o.Namespace).Search(ctl.Scheme, reqRef)

		util.DescribeEvents(reqEvents, prefixWriter, 1)
		tabWriter.Flush()
	}

	// TODO: print information about secret
	return nil
}

// formatStringSlice takes in a string slice and formats the contents of the slice
// into a single string where each element of the slice is prefixed with "- " and on a new line
func formatStringSlice(strings []string) string {
	result := ""
	for _, str := range strings {
		result += "- " + str + "\n"
	}
	return result
}

// formatTimeString returns the time as a string
// If nil, return "<none>"
func formatTimeString(t *metav1.Time) string {
	if t == nil {
		return "<none>"
	}
	return t.Time.Format(time.RFC3339)
}

// findMatchingCR tries to find a CertificateRequest that is owned by crt and has the correct revision annotated from reqs.
// If none found returns nil
// If one found returns the CR
// If multiple found returns error
func findMatchingCR(reqs *cmapi.CertificateRequestList, crt *cmapi.Certificate) (*cmapi.CertificateRequest, error) {
	possibleMatches := []*cmapi.CertificateRequest{}

	// CertificateRequest revisions begin from 1.
	// If no revision is set on the Certificate then assume the revision on the CertificateRequest should be 1.
	// If revision is set on the Certificate then revision on the CertificateRequest should be crt.Status.Revision + 1.
	nextRevision := 1
	if crt.Status.Revision != nil {
		nextRevision = *crt.Status.Revision + 1
	}
	for _, req := range reqs.Items {
		if predicate.CertificateRequestRevision(nextRevision)(&req) &&
			predicate.ResourceOwnedBy(crt)(&req) {
			possibleMatches = append(possibleMatches, req.DeepCopy())
		}
	}

	if len(possibleMatches) < 1 {
		return nil, nil
	} else if len(possibleMatches) == 1 {
		return possibleMatches[0], nil
	} else {
		return nil, errors.New("found multiple certificate requests with expected revision and owner")
	}
}

// crInfoString returns the information of a CR as a string to be printed as output
func crInfoString(cr *cmapi.CertificateRequest) string {
	if cr == nil {
		return "No CertificateRequest found for this Certificate\n"
	}

	crFormat := `
  Name: %s
  Namespace: %s
  Conditions:
  %s`
	conditionMsg := ""
	for _, con := range cr.Status.Conditions {
		conditionMsg += fmt.Sprintf("  %s: %s, Reason: %s, Message: %s\n", con.Type, con.Status, con.Reason, con.Message)
	}
	if conditionMsg == "" {
		conditionMsg = "  No Conditions set\n"
	}
	infos := fmt.Sprintf(crFormat, cr.Name, cr.Namespace, conditionMsg)
	return fmt.Sprintf("CertificateRequest:%s", infos)
}

// issuerInfoString returns the information of a issuer as a string to be printed as output
func issuerInfoString(name, kind string, conditions []cmapi.IssuerCondition) string {
	issuerFormat := `Issuer:
  Name: %s
  Kind: %s
  Conditions:
  %s`
	conditionMsg := ""
	for _, con := range conditions {
		conditionMsg += fmt.Sprintf("  %s: %s, Reason: %s, Message: %s\n", con.Type, con.Status, con.Reason, con.Message)
	}
	if conditionMsg == "" {
		conditionMsg = "  No Conditions set\n"
	}
	return fmt.Sprintf(issuerFormat, name, kind, conditionMsg)
}
<commit_msg>Use Fprintf correctly<commit_after>/*
Copyright 2020 The Jetstack cert-manager contributors.

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 certificate

import (
	"context"
	"errors"
	"fmt"
	"k8s.io/kubectl/pkg/describe"
	"text/tabwriter"
	"time"

	"github.com/spf13/cobra"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/cli-runtime/pkg/genericclioptions"
	"k8s.io/client-go/kubernetes"
	restclient "k8s.io/client-go/rest"
	"k8s.io/client-go/tools/reference"
	cmdutil "k8s.io/kubectl/pkg/cmd/util"
	"k8s.io/kubectl/pkg/util/i18n"
	"k8s.io/kubectl/pkg/util/templates"

	"github.com/jetstack/cert-manager/cmd/ctl/pkg/status/util"
	cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2"
	cmclient "github.com/jetstack/cert-manager/pkg/client/clientset/versioned"
	"github.com/jetstack/cert-manager/pkg/ctl"
	"github.com/jetstack/cert-manager/pkg/util/predicate"
)

var (
	long = templates.LongDesc(i18n.T(`
Get details about the current status of a cert-manager Certificate resource, including information on related resources like CertificateRequest.`))

	example = templates.Examples(i18n.T(`
# Query status of Certificate with name 'my-crt' in namespace 'my-namespace'
kubectl cert-manager status certificate my-crt --namespace my-namespace
`))
)

// Options is a struct to support status certificate command
type Options struct {
	CMClient   cmclient.Interface
	RESTConfig *restclient.Config
	// The Namespace that the Certificate to be queried about resides in.
	// This flag registration is handled by cmdutil.Factory
	Namespace string

	genericclioptions.IOStreams
}

// NewOptions returns initialized Options
func NewOptions(ioStreams genericclioptions.IOStreams) *Options {
	return &Options{
		IOStreams: ioStreams,
	}
}

// NewCmdStatusCert returns a cobra command for status certificate
func NewCmdStatusCert(ioStreams genericclioptions.IOStreams, factory cmdutil.Factory) *cobra.Command {
	o := NewOptions(ioStreams)
	cmd := &cobra.Command{
		Use:     "certificate",
		Short:   "Get details about the current status of a cert-manager Certificate resource",
		Long:    long,
		Example: example,
		Run: func(cmd *cobra.Command, args []string) {
			cmdutil.CheckErr(o.Validate(args))
			cmdutil.CheckErr(o.Complete(factory))
			cmdutil.CheckErr(o.Run(args))
		},
	}
	return cmd
}

// Validate validates the provided options
func (o *Options) Validate(args []string) error {
	if len(args) < 1 {
		return errors.New("the name of the Certificate has to be provided as argument")
	}
	if len(args) > 1 {
		return errors.New("only one argument can be passed in: the name of the Certificate")
	}
	return nil
}

// Complete takes the factory and infers any remaining options.
func (o *Options) Complete(f cmdutil.Factory) error {
	var err error

	o.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace()
	if err != nil {
		return err
	}

	o.RESTConfig, err = f.ToRESTConfig()
	if err != nil {
		return err
	}

	o.CMClient, err = cmclient.NewForConfig(o.RESTConfig)
	if err != nil {
		return err
	}

	return nil
}

// Run executes status certificate command
func (o *Options) Run(args []string) error {
	ctx := context.TODO()
	crtName := args[0]

	clientSet, err := kubernetes.NewForConfig(o.RESTConfig)
	if err != nil {
		return err
	}

	crt, err := o.CMClient.CertmanagerV1alpha2().Certificates(o.Namespace).Get(ctx, crtName, metav1.GetOptions{})
	if err != nil {
		return fmt.Errorf("error when getting Certificate resource: %v", err)
	}

	fmt.Fprintf(o.Out, "Name: %s\nNamespace: %s\n", crt.Name, crt.Namespace)

	fmt.Fprintf(o.Out, fmt.Sprintf("Created at: %s\n", crt.CreationTimestamp.Time.Format(time.RFC3339)))

	// Get necessary info from Certificate
	// Output one line about each type of Condition that is set.
	// Certificate can have multiple Conditions of different types set, e.g. "Ready" or "Issuing"
	conditionMsg := ""
	for _, con := range crt.Status.Conditions {
		conditionMsg += fmt.Sprintf("  %s: %s, Reason: %s, Message: %s\n", con.Type, con.Status, con.Reason, con.Message)
	}
	if conditionMsg == "" {
		conditionMsg = "  No Conditions set\n"
	}
	fmt.Fprintf(o.Out, "Conditions:\n%s", conditionMsg)

	dnsNames := formatStringSlice(crt.Spec.DNSNames)
	fmt.Fprintf(o.Out, "DNS Names:\n%s", dnsNames)

	crtRef, err := reference.GetReference(ctl.Scheme, crt)
	if err != nil {
		return err
	}
	// Ignore error, since if there was an error, crtEvents would be nil and handled down the line in DescribeEvents
	crtEvents, _ := clientSet.CoreV1().Events(o.Namespace).Search(ctl.Scheme, crtRef)
	tabWriter := tabwriter.NewWriter(o.Out, 0, 8, 2, ' ', 0)
	prefixWriter := describe.NewPrefixWriter(tabWriter)
	util.DescribeEvents(crtEvents, prefixWriter, 0)
	tabWriter.Flush()

	issuerKind := crt.Spec.IssuerRef.Kind
	if issuerKind == "" {
		issuerKind = "Issuer"
	}

	// Get info on Issuer/ClusterIssuer
	if crt.Spec.IssuerRef.Group != "cert-manager.io" && crt.Spec.IssuerRef.Group != "" {
		// TODO: Support Issuers/ClusterIssuers from other groups as well
		fmt.Fprintf(o.Out, "The %s %q is not of the group cert-manager.io, this command currently does not support third party issuers.\nTo get more information about %q, try 'kubectl describe'\n",
			issuerKind, crt.Spec.IssuerRef.Name, crt.Spec.IssuerRef.Name)
	} else if issuerKind == "Issuer" {
		issuer, err := o.CMClient.CertmanagerV1alpha2().Issuers(crt.Namespace).Get(ctx, crt.Spec.IssuerRef.Name, metav1.GetOptions{})
		if err != nil {
			fmt.Fprintf(o.Out, "error when getting Issuer: %v\n", err)
		} else {
			fmt.Fprintf(o.Out, issuerInfoString(crt.Spec.IssuerRef.Name, issuerKind, issuer.Status.Conditions))
		}
	} else {
		// ClusterIssuer
		clusterIssuer, err := o.CMClient.CertmanagerV1alpha2().ClusterIssuers().Get(ctx, crt.Spec.IssuerRef.Name, metav1.GetOptions{})
		if err != nil {
			fmt.Fprintf(o.Out, "error when getting ClusterIssuer: %v\n", err)
		} else {
			fmt.Fprintf(o.Out, issuerInfoString(crt.Spec.IssuerRef.Name, issuerKind, clusterIssuer.Status.Conditions))
		}
	}

	fmt.Fprintf(o.Out, "Not Before: %s\n", formatTimeString(crt.Status.NotBefore))
	fmt.Fprintf(o.Out, "Not After: %s\n", formatTimeString(crt.Status.NotAfter))
	fmt.Fprintf(o.Out, "Renewal Time: %s\n", formatTimeString(crt.Status.RenewalTime))

	// TODO: What about timing issues? When I query condition it's not ready yet, but then looking for cr it's finished and deleted
	// Try find the CertificateRequest that is owned by crt and has the correct revision
	reqs, err := o.CMClient.CertmanagerV1alpha2().CertificateRequests(o.Namespace).List(ctx, metav1.ListOptions{})
	if err != nil {
		return err
	}
	req, err := findMatchingCR(reqs, crt)
	if err != nil {
		return err
	}
	fmt.Fprintf(o.Out, crInfoString(req))
	if req != nil {
		reqRef, err := reference.GetReference(ctl.Scheme, req)
		if err != nil {
			return err
		}
		// Ignore error, since if there was an error, reqEvents would be nil and handled down the line in DescribeEvents
		reqEvents, _ := clientSet.CoreV1().Events(o.Namespace).Search(ctl.Scheme, reqRef)

		util.DescribeEvents(reqEvents, prefixWriter, 1)
		tabWriter.Flush()
	}

	return nil
}

// formatStringSlice takes in a string slice and formats the contents of the slice
// into a single string where each element of the slice is prefixed with "- " and on a new line
func formatStringSlice(strings []string) string {
	result := ""
	for _, str := range strings {
		result += "- " + str + "\n"
	}
	return result
}

// formatTimeString returns the time as a string
// If nil, return "<none>"
func formatTimeString(t *metav1.Time) string {
	if t == nil {
		return "<none>"
	}
	return t.Time.Format(time.RFC3339)
}

// findMatchingCR tries to find a CertificateRequest that is owned by crt and has the correct revision annotated from reqs.
// If none found returns nil
// If one found returns the CR
// If multiple found returns error
func findMatchingCR(reqs *cmapi.CertificateRequestList, crt *cmapi.Certificate) (*cmapi.CertificateRequest, error) {
	possibleMatches := []*cmapi.CertificateRequest{}

	// CertificateRequest revisions begin from 1.
	// If no revision is set on the Certificate then assume the revision on the CertificateRequest should be 1.
	// If revision is set on the Certificate then revision on the CertificateRequest should be crt.Status.Revision + 1.
	nextRevision := 1
	if crt.Status.Revision != nil {
		nextRevision = *crt.Status.Revision + 1
	}
	for _, req := range reqs.Items {
		if predicate.CertificateRequestRevision(nextRevision)(&req) &&
			predicate.ResourceOwnedBy(crt)(&req) {
			possibleMatches = append(possibleMatches, req.DeepCopy())
		}
	}

	if len(possibleMatches) < 1 {
		return nil, nil
	} else if len(possibleMatches) == 1 {
		return possibleMatches[0], nil
	} else {
		return nil, errors.New("found multiple certificate requests with expected revision and owner")
	}
}

// crInfoString returns the information of a CR as a string to be printed as output
func crInfoString(cr *cmapi.CertificateRequest) string {
	if cr == nil {
		return "No CertificateRequest found for this Certificate\n"
	}

	crFormat := `
  Name: %s
  Namespace: %s
  Conditions:
  %s`
	conditionMsg := ""
	for _, con := range cr.Status.Conditions {
		conditionMsg += fmt.Sprintf("  %s: %s, Reason: %s, Message: %s\n", con.Type, con.Status, con.Reason, con.Message)
	}
	if conditionMsg == "" {
		conditionMsg = "  No Conditions set\n"
	}
	infos := fmt.Sprintf(crFormat, cr.Name, cr.Namespace, conditionMsg)
	return fmt.Sprintf("CertificateRequest:%s", infos)
}

// issuerInfoString returns the information of a issuer as a string to be printed as output
func issuerInfoString(name, kind string, conditions []cmapi.IssuerCondition) string {
	issuerFormat := `Issuer:
  Name: %s
  Kind: %s
  Conditions:
  %s`
	conditionMsg := ""
	for _, con := range conditions {
		conditionMsg += fmt.Sprintf("  %s: %s, Reason: %s, Message: %s\n", con.Type, con.Status, con.Reason, con.Message)
	}
	if conditionMsg == "" {
		conditionMsg = "  No Conditions set\n"
	}
	return fmt.Sprintf(issuerFormat, name, kind, conditionMsg)
}
<|endoftext|>


<commit_before>package switchcase

import "github.com/qlova/ilang/src"

func init() {
	ilang.RegisterToken([]string{"switch"}, ScanSwitch)
	ilang.RegisterToken([]string{"case"}, ScanCase)
	ilang.RegisterToken([]string{"default"}, ScanDefault)

	ilang.RegisterListener(Switch, SwitchEnd)
}

var Switch = ilang.NewFlag()
var Default = ilang.NewFlag()

func SwitchEnd(ic *ilang.Compiler) {
	for i:=0; i < (ic.GetVariable("flag_nesting").Int+1)-ilang.Undefined.Int; i++ {
		ic.Assembly("END")
	}
	ic.Assembly("END")
	ic.LoseScope()
	ic.Assembly("#ACTIVATE")
}

func ScanSwitch(ic *ilang.Compiler) {
	var expression = ic.ScanExpression()
	if ic.ExpressionType != ilang.Number {
		ic.RaiseError("switch statements must have numeric conditions!")
	}
	ic.Scan('{')
	ic.Assembly("#SWITCH")
	ic.GainScope()

	ic.SetFlag(ilang.Type{Name: "flag_switch", Push: expression})

	//Find first case.
	for {
		token := ic.Scan(0)
		if token != "\n" {
			if token != "case" {
				ic.RaiseError("Expecting 'case', found ", token)
			}
			break
		}
	}
	expression = ic.ScanExpression()
	var condition = ic.Tmp("case")
	ic.Assembly("VAR ", condition)
	ic.Assembly("SEQ %v %v %v", condition, expression, ic.GetVariable("flag_switch").Push)
	ic.Assembly("IF ",condition)
	ic.GainScope()
	ic.SetFlag(Switch)
}

func ScanDefault(ic *ilang.Compiler) {
	if !ic.GetFlag(Switch) {
		ic.RaiseError("'default' must be within a 'switch' block!")
	}
	ic.UnsetFlag(Switch)
	ic.LoseScope()
	ic.Assembly("ELSE")
	ic.GainScope()
	ic.SetFlag(Switch)
	ic.SetFlag(Default)
}

func ScanCase(ic *ilang.Compiler) {
	if !ic.GetScopedFlag(Switch) {
		ic.RaiseError("a 'case' must be within a 'switch' block!")
	}

	if ic.GetScopedFlag(Default) {
		ic.RaiseError("default must be at the end of the switch statement!")
	}

	var expression = ic.ScanExpression()
	var condition = ic.Tmp("case")

	ic.UnsetFlag(Switch)

	ic.LoseScope()

	ic.UpdateVariable("flag_nesting", ilang.Type{Int: ic.GetVariable("flag_nesting").Int+1 })

	ic.Assembly("ELSE")

	ic.Assembly("VAR ", condition)
	ic.Assembly("SEQ %v %v %v", condition, expression, ic.GetVariable("flag_switch").Push)
	ic.Assembly("IF ",condition)
	ic.GainScope()
	ic.SetFlag(Switch)
}
<commit_msg>Fix large switchcases.<commit_after>package switchcase

import "github.com/qlova/ilang/src"

func init() {
	ilang.RegisterToken([]string{"switch"}, ScanSwitch)
	ilang.RegisterToken([]string{"case"}, ScanCase)
	ilang.RegisterToken([]string{"default"}, ScanDefault)

	ilang.RegisterListener(Switch, SwitchEnd)
}

var Switch = ilang.NewFlag()
var Default = ilang.NewFlag()

func SwitchEnd(ic *ilang.Compiler) {
	for i:=0; i < (ic.GetVariable("flag_nesting").Int); i++ {
		ic.Assembly("END")
	}
	ic.Assembly("END")
	ic.LoseScope()
	ic.Assembly("#ACTIVATE")
}

func ScanSwitch(ic *ilang.Compiler) {
	var expression = ic.ScanExpression()
	if ic.ExpressionType != ilang.Number {
		ic.RaiseError("switch statements must have numeric conditions!")
	}
	ic.Scan('{')
	ic.Assembly("#SWITCH")
	ic.GainScope()

	ic.SetFlag(ilang.Type{Name: "flag_switch", Push: expression})
	ic.SetFlag(ilang.Type{Name: "flag_nesting", Int: 0})

	//Find first case.
	for {
		token := ic.Scan(0)
		if token != "\n" {
			if token != "case" {
				ic.RaiseError("Expecting 'case', found ", token)
			}
			break
		}
	}
	expression = ic.ScanExpression()
	var condition = ic.Tmp("case")
	ic.Assembly("VAR ", condition)
	ic.Assembly("SEQ %v %v %v", condition, expression, ic.GetVariable("flag_switch").Push)
	ic.Assembly("IF ",condition)
	ic.GainScope()
	ic.SetFlag(Switch)
}

func ScanDefault(ic *ilang.Compiler) {
	if !ic.GetFlag(Switch) {
		ic.RaiseError("'default' must be within a 'switch' block!")
	}
	ic.UnsetFlag(Switch)
	ic.LoseScope()
	ic.Assembly("ELSE")
	ic.GainScope()
	ic.SetFlag(Switch)
	ic.SetFlag(Default)
}

func ScanCase(ic *ilang.Compiler) {
	if !ic.GetScopedFlag(Switch) {
		ic.RaiseError("a 'case' must be within a 'switch' block!")
	}

	if ic.GetScopedFlag(Default) {
		ic.RaiseError("default must be at the end of the switch statement!")
	}

	var expression = ic.ScanExpression()
	var condition = ic.Tmp("case")

	ic.UnsetFlag(Switch)

	ic.LoseScope()

	ic.UpdateVariable("flag_nesting", ilang.Type{Int: ic.GetVariable("flag_nesting").Int+1 })

	ic.Assembly("ELSE")

	ic.Assembly("VAR ", condition)
	ic.Assembly("SEQ %v %v %v", condition, expression, ic.GetVariable("flag_switch").Push)
	ic.Assembly("IF ",condition)
	ic.GainScope()
	ic.SetFlag(Switch)
}
<|endoftext|>


<commit_before>package profile_test

import (
	"bufio"
	"bytes"
	"flag"
	"fmt"
	"go/format"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/cespare/xxhash"
	"github.com/tormoder/fit/cmd/fitgen/internal/profile"
)

const (
	testdata      = "testdata"
	fileExt       = ".xlsx"
	goldenSuffix  = ".golden"
	currentSuffix = ".current"
)

var update = flag.Bool("update", false, "update .golden output files")

var currentSDK = sdks[0]

var defGenOpts = []profile.GeneratorOption{
	profile.WithGenerationTimestamp(false),
}

func relPath(sdkVersion string) string {
	return filepath.Join(testdata, sdkVersion+fileExt)
}

func writeProfile(p *profile.Profile, w io.Writer) error {
	var err error
	write := func(buf []byte) {
		if err != nil {
			return
		}
		_, err = w.Write(buf)
	}
	write([]byte("// TYPES\n"))
	write(p.TypesSource)
	write([]byte("\n// MESSAGES\n"))
	write(p.MessagesSource)
	write([]byte("\n// PROFILE\n"))
	write(p.ProfileSource)
	write([]byte("\n// FITSTRINGER TYPE INPUT\n"))
	for _, t := range p.StringerInput {
		write([]byte(t))
		write([]byte{'\n'})
	}
	write([]byte("\n// MESSAGE NUMS WITHOUT MESSAGE\n"))
	for _, mn := range p.MesgNumsWithoutMessage {
		write([]byte(mn))
		write([]byte{'\n'})
	}
	return err
}

func writeProfileToFile(p *profile.Profile, path string) error {
	buf := new(bytes.Buffer)
	err := writeProfile(p, buf)
	if err != nil {
		return err
	}
	return ioutil.WriteFile(path, buf.Bytes(), 0o644)
}

func scanLinesPreserveEOL(data []byte, atEOF bool) (advance int, token []byte, err error) {
	if atEOF && len(data) == 0 {
		return 0, nil, nil
	}
	if i := bytes.IndexByte(data, '\n'); i >= 0 {
		// We have a full newline-terminated line.
		return i + 1, data[0 : i+1], nil
	}
	// If we're at EOF, we have a final, non-terminated line. Return it.
	if atEOF {
		return len(data), data, nil
	}
	// Request more data.
	return 0, nil, nil
}

func readGoldenProfile(path string) (*profile.Profile, error) {
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer func() { _ = f.Close() }() // Sigh. To keep errcheck happy

	p := &profile.Profile{}
	headings := []string{
		"// TYPES",
		"// MESSAGES",
		"// PROFILE",
		"// FITSTRINGER TYPE INPUT",
		"// MESSAGE NUMS WITHOUT MESSAGE",
	}
	i := 0

	scanner := bufio.NewScanner(f)
	scanner.Split(scanLinesPreserveEOL)

	scanner.Scan()
	err = scanner.Err()
	if err != nil {
		return nil, err
	}

	if !strings.HasPrefix(scanner.Text(), headings[i]) {
		return nil, fmt.Errorf("first line should be '%s'", headings[i])
	}

	for scanner.Scan() {
		if strings.HasPrefix(scanner.Text(), headings[i+1]) {
			i++
			break
		}
		p.TypesSource = append(p.TypesSource, scanner.Bytes()...)
	}

	// Format
	p.TypesSource, err = format.Source(p.TypesSource)
	if err != nil {
		return nil, fmt.Errorf("format Types: %v", err)
	}

	for scanner.Scan() {
		if strings.HasPrefix(scanner.Text(), headings[i+1]) {
			i++
			break
		}
		p.MessagesSource = append(p.MessagesSource, scanner.Bytes()...)
	}

	// Format
	p.MessagesSource, err = format.Source(p.MessagesSource)
	if err != nil {
		return nil, fmt.Errorf("format Messages: %v", err)
	}

	for scanner.Scan() {
		if strings.HasPrefix(scanner.Text(), headings[i+1]) {
			i++
			break
		}
		p.ProfileSource = append(p.ProfileSource, scanner.Bytes()...)
	}

	// Format
	p.ProfileSource, err = format.Source(p.ProfileSource)
	if err != nil {
		return nil, fmt.Errorf("format Profile: %v", err)
	}

	for scanner.Scan() {
		if strings.HasPrefix(scanner.Text(), headings[i+1]) {
			break
		}
		s := strings.TrimSpace(scanner.Text())
		if len(s) > 0 {
			p.StringerInput = append(p.StringerInput, s)
		}
	}

	for scanner.Scan() {
		s := strings.TrimSpace(scanner.Text())
		if len(s) > 0 {
			p.MesgNumsWithoutMessage = append(p.MesgNumsWithoutMessage, s)
		}
	}

	return p, scanner.Err()
}

func profileFingerprint(p *profile.Profile) uint64 {
	h := xxhash.New()
	_ = writeProfile(p, h)
	return h.Sum64()
}

type sdk struct {
	majVer, minVer int
}

var sdks = []sdk{
	{16, 20},
	{20, 14},
	{20, 27},
	{20, 43},
	{21, 40},
}

func TestMain(m *testing.M) {
	flag.Parse()
	os.Exit(m.Run())
}

func TestGenerator(t *testing.T) {
	for _, sdk := range sdks {
		sdk := sdk // Capture range variable.
		sdkFullVer := fmt.Sprintf("%d.%d", sdk.majVer, sdk.minVer)
		t.Run(sdkFullVer, func(t *testing.T) {
			t.Parallel()
			if sdk == currentSDK && testing.Short() {
				t.Skip("skipping test in short mode")
			}
			path := relPath(sdkFullVer)
			data, err := ioutil.ReadFile(path)
			if err != nil {
				t.Fatal(err)
			}
			g, err := profile.NewGenerator(sdk.majVer, sdk.minVer, data, defGenOpts...)
			if err != nil {
				t.Fatal(err)
			}
			p, err := g.GenerateProfile()
			if err != nil {
				t.Fatal(err)
			}
			gotFP := profileFingerprint(p)

			// Read in the golden profile, format it and fingerprint it
			// This makes the test robust against gofmt changes
			goldenProfile, err := readGoldenProfile(path + ".golden")
			if err != nil {
				t.Fatal(err)
			}
			goldenFingerprint := profileFingerprint(goldenProfile)

			if gotFP == goldenFingerprint {
				return
			}
			t.Errorf("profile fingerprint differs: got: %d, want %d", gotFP, goldenFingerprint)
			if !*update {
				path = path + currentSuffix
			} else {
				path = path + goldenSuffix
			}
			err = writeProfileToFile(p, path)
			if err != nil {
				t.Fatalf("error writing output: %v", err)
			}
			if !*update {
				t.Logf("current output written to: %s", path)
			} else {
				t.Logf("%q has been updated", path)
				t.Logf("new fingerprint is: %d", gotFP)
			}
		})
	}
}

var profileSink *profile.Profile

func BenchmarkGenerator(b *testing.B) {
	for _, sdk := range sdks {
		sdkFullVer := fmt.Sprintf("%d.%d", sdk.majVer, sdk.minVer)
		b.Run(sdkFullVer, func(b *testing.B) {
			path := relPath(sdkFullVer)
			data, err := ioutil.ReadFile(path)
			if err != nil {
				b.Fatalf("error reading profile workbook: %v", err)
			}
			b.ReportAllocs()
			b.ResetTimer()
			for i := 0; i < b.N; i++ {
				g, err := profile.NewGenerator(sdk.majVer, sdk.minVer, data, defGenOpts...)
				if err != nil {
					b.Fatal(err)
				}
				profileSink, err = g.GenerateProfile()
				if err != nil {
					b.Fatal(err)
				}
			}
		})
	}
}
<commit_msg>cmd/fitgen/internal/profile: fix test short logic for TestGenerator<commit_after>package profile_test

import (
	"bufio"
	"bytes"
	"flag"
	"fmt"
	"go/format"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/cespare/xxhash"
	"github.com/tormoder/fit/cmd/fitgen/internal/profile"
)

const (
	testdata      = "testdata"
	fileExt       = ".xlsx"
	goldenSuffix  = ".golden"
	currentSuffix = ".current"
)

var update = flag.Bool("update", false, "update .golden output files")

var defGenOpts = []profile.GeneratorOption{
	profile.WithGenerationTimestamp(false),
}

func relPath(sdkVersion string) string {
	return filepath.Join(testdata, sdkVersion+fileExt)
}

func writeProfile(p *profile.Profile, w io.Writer) error {
	var err error
	write := func(buf []byte) {
		if err != nil {
			return
		}
		_, err = w.Write(buf)
	}
	write([]byte("// TYPES\n"))
	write(p.TypesSource)
	write([]byte("\n// MESSAGES\n"))
	write(p.MessagesSource)
	write([]byte("\n// PROFILE\n"))
	write(p.ProfileSource)
	write([]byte("\n// FITSTRINGER TYPE INPUT\n"))
	for _, t := range p.StringerInput {
		write([]byte(t))
		write([]byte{'\n'})
	}
	write([]byte("\n// MESSAGE NUMS WITHOUT MESSAGE\n"))
	for _, mn := range p.MesgNumsWithoutMessage {
		write([]byte(mn))
		write([]byte{'\n'})
	}
	return err
}

func writeProfileToFile(p *profile.Profile, path string) error {
	buf := new(bytes.Buffer)
	err := writeProfile(p, buf)
	if err != nil {
		return err
	}
	return ioutil.WriteFile(path, buf.Bytes(), 0o644)
}

func scanLinesPreserveEOL(data []byte, atEOF bool) (advance int, token []byte, err error) {
	if atEOF && len(data) == 0 {
		return 0, nil, nil
	}
	if i := bytes.IndexByte(data, '\n'); i >= 0 {
		// We have a full newline-terminated line.
		return i + 1, data[0 : i+1], nil
	}
	// If we're at EOF, we have a final, non-terminated line. Return it.
	if atEOF {
		return len(data), data, nil
	}
	// Request more data.
	return 0, nil, nil
}

func readGoldenProfile(path string) (*profile.Profile, error) {
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer func() { _ = f.Close() }() // Sigh. To keep errcheck happy

	p := &profile.Profile{}
	headings := []string{
		"// TYPES",
		"// MESSAGES",
		"// PROFILE",
		"// FITSTRINGER TYPE INPUT",
		"// MESSAGE NUMS WITHOUT MESSAGE",
	}
	i := 0

	scanner := bufio.NewScanner(f)
	scanner.Split(scanLinesPreserveEOL)

	scanner.Scan()
	err = scanner.Err()
	if err != nil {
		return nil, err
	}

	if !strings.HasPrefix(scanner.Text(), headings[i]) {
		return nil, fmt.Errorf("first line should be '%s'", headings[i])
	}

	for scanner.Scan() {
		if strings.HasPrefix(scanner.Text(), headings[i+1]) {
			i++
			break
		}
		p.TypesSource = append(p.TypesSource, scanner.Bytes()...)
	}

	// Format
	p.TypesSource, err = format.Source(p.TypesSource)
	if err != nil {
		return nil, fmt.Errorf("format Types: %v", err)
	}

	for scanner.Scan() {
		if strings.HasPrefix(scanner.Text(), headings[i+1]) {
			i++
			break
		}
		p.MessagesSource = append(p.MessagesSource, scanner.Bytes()...)
	}

	// Format
	p.MessagesSource, err = format.Source(p.MessagesSource)
	if err != nil {
		return nil, fmt.Errorf("format Messages: %v", err)
	}

	for scanner.Scan() {
		if strings.HasPrefix(scanner.Text(), headings[i+1]) {
			i++
			break
		}
		p.ProfileSource = append(p.ProfileSource, scanner.Bytes()...)
	}

	// Format
	p.ProfileSource, err = format.Source(p.ProfileSource)
	if err != nil {
		return nil, fmt.Errorf("format Profile: %v", err)
	}

	for scanner.Scan() {
		if strings.HasPrefix(scanner.Text(), headings[i+1]) {
			break
		}
		s := strings.TrimSpace(scanner.Text())
		if len(s) > 0 {
			p.StringerInput = append(p.StringerInput, s)
		}
	}

	for scanner.Scan() {
		s := strings.TrimSpace(scanner.Text())
		if len(s) > 0 {
			p.MesgNumsWithoutMessage = append(p.MesgNumsWithoutMessage, s)
		}
	}

	return p, scanner.Err()
}

func profileFingerprint(p *profile.Profile) uint64 {
	h := xxhash.New()
	_ = writeProfile(p, h)
	return h.Sum64()
}

type sdk struct {
	majVer, minVer int
}

var sdks = []sdk{
	{16, 20},
	{20, 14},
	{20, 27},
	{20, 43},
	{21, 40},
}

func TestMain(m *testing.M) {
	flag.Parse()
	os.Exit(m.Run())
}

func TestGenerator(t *testing.T) {
	latestSDK := sdks[len(sdks)-1]

	for _, sdk := range sdks {
		sdk := sdk // Capture range variable.
		sdkFullVer := fmt.Sprintf("%d.%d", sdk.majVer, sdk.minVer)
		t.Run(sdkFullVer, func(t *testing.T) {
			t.Parallel()
			if sdk != latestSDK && testing.Short() {
				t.Skip("skipping test in short mode")
			}
			path := relPath(sdkFullVer)
			data, err := ioutil.ReadFile(path)
			if err != nil {
				t.Fatal(err)
			}
			g, err := profile.NewGenerator(sdk.majVer, sdk.minVer, data, defGenOpts...)
			if err != nil {
				t.Fatal(err)
			}
			p, err := g.GenerateProfile()
			if err != nil {
				t.Fatal(err)
			}
			gotFP := profileFingerprint(p)

			// Read in the golden profile, format it and fingerprint it
			// This makes the test robust against gofmt changes
			goldenProfile, err := readGoldenProfile(path + ".golden")
			if err != nil {
				t.Fatal(err)
			}
			goldenFingerprint := profileFingerprint(goldenProfile)

			if gotFP == goldenFingerprint {
				return
			}
			t.Errorf("profile fingerprint differs: got: %d, want %d", gotFP, goldenFingerprint)
			if !*update {
				path = path + currentSuffix
			} else {
				path = path + goldenSuffix
			}
			err = writeProfileToFile(p, path)
			if err != nil {
				t.Fatalf("error writing output: %v", err)
			}
			if !*update {
				t.Logf("current output written to: %s", path)
			} else {
				t.Logf("%q has been updated", path)
				t.Logf("new fingerprint is: %d", gotFP)
			}
		})
	}
}

var profileSink *profile.Profile

func BenchmarkGenerator(b *testing.B) {
	for _, sdk := range sdks {
		sdkFullVer := fmt.Sprintf("%d.%d", sdk.majVer, sdk.minVer)
		b.Run(sdkFullVer, func(b *testing.B) {
			path := relPath(sdkFullVer)
			data, err := ioutil.ReadFile(path)
			if err != nil {
				b.Fatalf("error reading profile workbook: %v", err)
			}
			b.ReportAllocs()
			b.ResetTimer()
			for i := 0; i < b.N; i++ {
				g, err := profile.NewGenerator(sdk.majVer, sdk.minVer, data, defGenOpts...)
				if err != nil {
					b.Fatal(err)
				}
				profileSink, err = g.GenerateProfile()
				if err != nil {
					b.Fatal(err)
				}
			}
		})
	}
}
<|endoftext|>


<commit_before>// Copyright 2009 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.

package gobuild

import (
	"bufio";
	"exec";
	"fmt";
	"io";
	"go/ast";
	"go/parser";
	"os";
	"path";
	"sort";
	"strconv";
	"strings";
)

const (
	ShowErrors = 1<<iota;
	ForceDisplay;
)

var (
	theChar string;
	goarch string;
	goos string;
	bin = make(map[string] string);
)

var theChars = map[string] string {
	"amd64": "6",
	"386": "8",
	"arm": "5"
}

const ObjDir = "_obj"

func fatal(format string, args ...) {
	fmt.Fprintf(os.Stderr, "gobuild: %s\n", fmt.Sprintf(format, args));
	os.Exit(1);
}

func init() {
	goarch = os.Getenv("GOARCH");
	goos = os.Getenv("GOOS");

	var ok bool;
	theChar, ok = theChars[goarch];
	if !ok {
		fatal("unknown $GOARCH: %s", goarch);
	}

	var binaries = []string{
		theChar + "g",
		theChar + "c",
		theChar + "a",
		"gopack",
	};

	for i, v := range binaries {
		var s string;
		var err os.Error;
		if s, err = exec.LookPath(v); err != nil {
			fatal("cannot find binary %s", v);
		}
		bin[v] = s;
	}
}

func PushString(vp *[]string, p string) {
	v := *vp;
	n := len(v);
	if n >= cap(v) {
		m := 2*n + 10;
		a := make([]string, n, m);
		for i := range v {
			a[i] = v[i];
		}
		v = a;
	}
	v = v[0:n+1];
	v[n] = p;
	*vp = v;
}

func run(argv []string, flag int) (ok bool) {
	argv0 := bin[argv[0]];
	null, err := os.Open("/dev/null", os.O_RDWR, 0);
	if err != nil {
		fatal("open /dev/null: %s", err);
	}
	defer null.Close();
	r, w, err := os.Pipe();
	if err != nil {
		fatal("pipe: %s", err);
	}
	pid, err := os.ForkExec(argv0, argv, os.Environ(), "", []*os.File{null, w, w});
	defer r.Close();
	w.Close();
	if err != nil {
		fmt.Fprintln(os.Stderr, err);
		return false;
	}

	// Read the first line of output, if any.  Discard the rest.
	// If there is output and ShowErrors is set, show it,
	// preceded by a shell command line.
	// If ForceDisplay is set, we show the command even
	// if there's no output; this gets set if we're just trying
	// to keep the user informed.
	b := bufio.NewReader(r);
	line, err := b.ReadLineString('\n', true);
	if flag & ShowErrors != 0 && line != "" || flag & ForceDisplay != 0 {
		fmt.Fprint(os.Stderr, "$ ");
		for i, s := range argv {
			fmt.Fprint(os.Stderr, s, " ");
		}
		fmt.Fprint(os.Stderr, "\n");
		fmt.Fprint(os.Stderr, "  ", line);
		io.Copy(r, null);	// don't let process block on pipe
	}
	waitmsg, err := os.Wait(pid, 0);
	if err != nil {
		fmt.Fprintln(os.Stderr, err);
		return false;
	}
	return waitmsg.Exited() && waitmsg.ExitStatus() == 0;
}

func Build(cmd []string, file string, flag int) (ok bool) {
	var argv []string;
	for i, c := range cmd {
		PushString(&argv, c);
	}
	PushString(&argv, file);
	return run(argv, flag);
}

func Archive(pkg string, files []string) {
	argv := []string{ "gopack", "grc", pkg };
	for i, file := range files {
		PushString(&argv, file);
	}
	if !run(argv, ShowErrors) {
		fatal("archive failed");
	}
}

func Compiler(file string) []string {
	switch {
	case strings.HasSuffix(file, ".go"):
		return []string{ theChar + "g", "-I", ObjDir };
	case strings.HasSuffix(file, ".c"):
		return []string{ theChar + "c", "-FVw" };
	case strings.HasSuffix(file, ".s"):
		return []string{ theChar + "a" };
	}
	fatal("don't know how to compile %s", file);
	return nil;
}

func Object(file, suffix string) string {
	ext := path.Ext(file);
	return file[0:len(file)-len(ext)] + "." + suffix;
}

// Dollarstring returns s with literal goarch/goos values
// replaced by $lGOARCHr where l and r are the specified delimeters.
func dollarString(s, l, r string) string {
	out := "";
	j := 0;	// index of last byte in s copied to out.
	for i := 0; i < len(s); {
		switch {
		case i+len(goarch) <= len(s) && s[i:i+len(goarch)] == goarch:
			out += s[j:i];
			out += "$" + l + "GOARCH" + r;
			i += len(goarch);
			j = i;
		case i+len(goos) <= len(s) && s[i:i+len(goos)] == goos:
			out += s[j:i];
			out += "$" + l + "GOOS" + r;
			i += len(goos);
			j = i;
		default:
			i++;
		}
	}
	out += s[j:len(s)];
	return out;
}

// dollarString wrappers.
// Print ShellString(s) or MakeString(s) depending on
// the context in which the result will be interpreted.
type ShellString string;
func (s ShellString) String() string {
	return dollarString(string(s), "{", "}");
}

type MakeString string;
func (s MakeString) String() string {
	return dollarString(string(s), "(", ")");
}

// TODO(rsc): Should this be in the AST library?
func LitString(p []*ast.StringLit) (string, os.Error) {
	s := "";
	for i, lit := range p {
		t, err := strconv.Unquote(string(lit.Value));
		if err != nil {
			return "", err;
		}
		s += t;
	}
	return s, nil;
}

func PackageImports(file string) (pkg string, imports []string, err1 os.Error) {
	prog, err := parser.ParseFile(file, nil, parser.ImportsOnly);
	if err != nil {
		return "", nil, err;
	}

	// Normally one must consult the types of decl and spec,
	// but we told the parser to return imports only,
	// so assume it did.
	var imp []string;
	for _, decl := range prog.Decls {
		for _, spec := range decl.(*ast.GenDecl).Specs {
			str, err := LitString(spec.(*ast.ImportSpec).Path);
			if err != nil {
				return "", nil, os.NewError("invalid import specifier");	// better than os.EINVAL
			}
			PushString(&imp, str);
		}
	}

	// TODO(rsc): should be prog.Package.Value
	return prog.Name.Value, imp, nil;
}

func SourceFiles(dir string) ([]string, os.Error) {
	f, err := os.Open(dir, os.O_RDONLY, 0);
	if err != nil {
		return nil, err;
	}
	names, err1 := f.Readdirnames(-1);
	f.Close();
	out := make([]string, 0, len(names));
	for i, name := range names {
		if strings.HasSuffix(name, ".go")
		|| strings.HasSuffix(name, ".c")
		|| strings.HasSuffix(name, ".s") {
			n := len(out);
			out = out[0:n+1];
			out[n] = name;
		}
	}
	sort.SortStrings(out);
	return out, nil;
}

// TODO(rsc): Implement these for real as
// os.MkdirAll and os.RemoveAll and then
// make these wrappers that call fatal on error.

func MkdirAll(name string) {
	p, err := exec.Run("/bin/mkdir", []string{"mkdir", "-p", name}, os.Environ(), exec.DevNull, exec.PassThrough, exec.PassThrough);
	if err != nil {
		fatal("run /bin/mkdir: %v", err);
	}
	w, err1 := p.Wait(0);
	if err1 != nil {
		fatal("wait /bin/mkdir: %v", err);
	}
	if !w.Exited() || w.ExitStatus() != 0 {
		fatal("/bin/mkdir: %v", w);
	}
}

func RemoveAll(name string) {
	p, err := exec.Run("/bin/rm", []string{"rm", "-rf", name}, os.Environ(), exec.DevNull, exec.PassThrough, exec.PassThrough);
	if err != nil {
		fatal("run /bin/rm: %v", err);
	}
	w, err := p.Wait(0);
	if err != nil {
		fatal("wait /bin/rm: %v", err);
	}
	if !w.Exited() || w.ExitStatus() != 0 {
		fatal("/bin/rm: %v", w);
	}

}

<commit_msg>this time really clean up a TODO<commit_after>// Copyright 2009 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.

package gobuild

import (
	"bufio";
	"exec";
	"fmt";
	"io";
	"go/ast";
	"go/parser";
	"os";
	"path";
	"sort";
	"strconv";
	"strings";
)

const (
	ShowErrors = 1<<iota;
	ForceDisplay;
)

var (
	theChar string;
	goarch string;
	goos string;
	bin = make(map[string] string);
)

var theChars = map[string] string {
	"amd64": "6",
	"386": "8",
	"arm": "5"
}

const ObjDir = "_obj"

func fatal(format string, args ...) {
	fmt.Fprintf(os.Stderr, "gobuild: %s\n", fmt.Sprintf(format, args));
	os.Exit(1);
}

func init() {
	goarch = os.Getenv("GOARCH");
	goos = os.Getenv("GOOS");

	var ok bool;
	theChar, ok = theChars[goarch];
	if !ok {
		fatal("unknown $GOARCH: %s", goarch);
	}

	var binaries = []string{
		theChar + "g",
		theChar + "c",
		theChar + "a",
		"gopack",
	};

	for i, v := range binaries {
		var s string;
		var err os.Error;
		if s, err = exec.LookPath(v); err != nil {
			fatal("cannot find binary %s", v);
		}
		bin[v] = s;
	}
}

func PushString(vp *[]string, p string) {
	v := *vp;
	n := len(v);
	if n >= cap(v) {
		m := 2*n + 10;
		a := make([]string, n, m);
		for i := range v {
			a[i] = v[i];
		}
		v = a;
	}
	v = v[0:n+1];
	v[n] = p;
	*vp = v;
}

func run(argv []string, flag int) (ok bool) {
	argv0 := bin[argv[0]];
	null, err := os.Open("/dev/null", os.O_RDWR, 0);
	if err != nil {
		fatal("open /dev/null: %s", err);
	}
	defer null.Close();
	r, w, err := os.Pipe();
	if err != nil {
		fatal("pipe: %s", err);
	}
	pid, err := os.ForkExec(argv0, argv, os.Environ(), "", []*os.File{null, w, w});
	defer r.Close();
	w.Close();
	if err != nil {
		fmt.Fprintln(os.Stderr, err);
		return false;
	}

	// Read the first line of output, if any.  Discard the rest.
	// If there is output and ShowErrors is set, show it,
	// preceded by a shell command line.
	// If ForceDisplay is set, we show the command even
	// if there's no output; this gets set if we're just trying
	// to keep the user informed.
	b := bufio.NewReader(r);
	line, err := b.ReadLineString('\n', true);
	if flag & ShowErrors != 0 && line != "" || flag & ForceDisplay != 0 {
		fmt.Fprint(os.Stderr, "$ ");
		for i, s := range argv {
			fmt.Fprint(os.Stderr, s, " ");
		}
		fmt.Fprint(os.Stderr, "\n");
		fmt.Fprint(os.Stderr, "  ", line);
		io.Copy(r, null);	// don't let process block on pipe
	}
	waitmsg, err := os.Wait(pid, 0);
	if err != nil {
		fmt.Fprintln(os.Stderr, err);
		return false;
	}
	return waitmsg.Exited() && waitmsg.ExitStatus() == 0;
}

func Build(cmd []string, file string, flag int) (ok bool) {
	var argv []string;
	for i, c := range cmd {
		PushString(&argv, c);
	}
	PushString(&argv, file);
	return run(argv, flag);
}

func Archive(pkg string, files []string) {
	argv := []string{ "gopack", "grc", pkg };
	for i, file := range files {
		PushString(&argv, file);
	}
	if !run(argv, ShowErrors) {
		fatal("archive failed");
	}
}

func Compiler(file string) []string {
	switch {
	case strings.HasSuffix(file, ".go"):
		return []string{ theChar + "g", "-I", ObjDir };
	case strings.HasSuffix(file, ".c"):
		return []string{ theChar + "c", "-FVw" };
	case strings.HasSuffix(file, ".s"):
		return []string{ theChar + "a" };
	}
	fatal("don't know how to compile %s", file);
	return nil;
}

func Object(file, suffix string) string {
	ext := path.Ext(file);
	return file[0:len(file)-len(ext)] + "." + suffix;
}

// Dollarstring returns s with literal goarch/goos values
// replaced by $lGOARCHr where l and r are the specified delimeters.
func dollarString(s, l, r string) string {
	out := "";
	j := 0;	// index of last byte in s copied to out.
	for i := 0; i < len(s); {
		switch {
		case i+len(goarch) <= len(s) && s[i:i+len(goarch)] == goarch:
			out += s[j:i];
			out += "$" + l + "GOARCH" + r;
			i += len(goarch);
			j = i;
		case i+len(goos) <= len(s) && s[i:i+len(goos)] == goos:
			out += s[j:i];
			out += "$" + l + "GOOS" + r;
			i += len(goos);
			j = i;
		default:
			i++;
		}
	}
	out += s[j:len(s)];
	return out;
}

// dollarString wrappers.
// Print ShellString(s) or MakeString(s) depending on
// the context in which the result will be interpreted.
type ShellString string;
func (s ShellString) String() string {
	return dollarString(string(s), "{", "}");
}

type MakeString string;
func (s MakeString) String() string {
	return dollarString(string(s), "(", ")");
}

// TODO(rsc): Should this be in the AST library?
func LitString(p []*ast.StringLit) (string, os.Error) {
	s := "";
	for i, lit := range p {
		t, err := strconv.Unquote(string(lit.Value));
		if err != nil {
			return "", err;
		}
		s += t;
	}
	return s, nil;
}

func PackageImports(file string) (pkg string, imports []string, err1 os.Error) {
	prog, err := parser.ParseFile(file, nil, parser.ImportsOnly);
	if err != nil {
		return "", nil, err;
	}

	// Normally one must consult the types of decl and spec,
	// but we told the parser to return imports only,
	// so assume it did.
	var imp []string;
	for _, decl := range prog.Decls {
		for _, spec := range decl.(*ast.GenDecl).Specs {
			str, err := LitString(spec.(*ast.ImportSpec).Path);
			if err != nil {
				return "", nil, os.NewError("invalid import specifier");	// better than os.EINVAL
			}
			PushString(&imp, str);
		}
	}

	// TODO(rsc): should be prog.Package.Value
	return prog.Name.Value, imp, nil;
}

func SourceFiles(dir string) ([]string, os.Error) {
	f, err := os.Open(dir, os.O_RDONLY, 0);
	if err != nil {
		return nil, err;
	}
	names, err1 := f.Readdirnames(-1);
	f.Close();
	out := make([]string, 0, len(names));
	for i, name := range names {
		if strings.HasSuffix(name, ".go")
		|| strings.HasSuffix(name, ".c")
		|| strings.HasSuffix(name, ".s") {
			n := len(out);
			out = out[0:n+1];
			out[n] = name;
		}
	}
	sort.SortStrings(out);
	return out, nil;
}

func MkdirAll(name string) {
	err := path.MkdirAll(name);
	if err != nil {
		fatal("MkdirAll: %v", err);
	}
}

func RemoveAll(name string) {
	err := path.RemoveAll(name);
	if err != nil {
		fatal("RemoveAll: %v", err);
	}
}
<|endoftext|>


<commit_before>package acceptance_test

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"math/rand"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"code.cloudfoundry.org/cf-networking-helpers/testsupport"

	pusherConfig "cf-pusher/config"

	"github.com/cloudfoundry-incubator/cf-test-helpers/cf"
	"github.com/cloudfoundry-incubator/cf-test-helpers/helpers"
	. "github.com/onsi/ginkgo"
	ginkgoConfig "github.com/onsi/ginkgo/config"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gexec"
)

const Timeout_Push = 2 * time.Minute

var (
	appsDir    string
	config     helpers.Config
	testConfig pusherConfig.Config
)

func TestAcceptance(t *testing.T) {
	RegisterFailHandler(Fail)

	BeforeSuite(func() {
		config = helpers.LoadConfig()

		configPath := helpers.ConfigPath()
		configBytes, err := ioutil.ReadFile(configPath)
		Expect(err).NotTo(HaveOccurred())

		err = json.Unmarshal(configBytes, &testConfig)
		Expect(err).NotTo(HaveOccurred())

		if testConfig.Applications <= 0 {
			Fail("Applications count needs to be greater than 0")
		}

		if testConfig.AppInstances <= 0 {
			Fail("AppInstances count needs to be greater than 0")
		}

		if testConfig.ProxyApplications <= 0 {
			Fail("ProxyApplications count needs to be greater than 0")
		}

		if testConfig.ProxyInstances <= 0 {
			Fail("ProxyInstances count needs to be greater than 0")
		}

		Expect(cf.Cf("api", "--skip-ssl-validation", config.ApiEndpoint).Wait(Timeout_Short)).To(gexec.Exit(0))
		AuthAsAdmin()

		appsDir = os.Getenv("APPS_DIR")
		Expect(appsDir).NotTo(BeEmpty())

		rand.Seed(ginkgoConfig.GinkgoConfig.RandomSeed + int64(GinkgoParallelNode()))
	})

	RunSpecs(t, "Acceptance Suite")
}

func Auth(username, password string) {
	By("authenticating as " + username)
	cmd := exec.Command("cf", "auth", username, password)
	sess, err := gexec.Start(cmd, nil, nil)
	Expect(err).NotTo(HaveOccurred())
	Eventually(sess.Wait(Timeout_Short)).Should(gexec.Exit(0))
}

func getUAABaseURL() string {
	sess := cf.Cf("curl", "/v2/info")
	Eventually(sess.Wait(Timeout_Short)).Should(gexec.Exit(0))
	var response struct {
		TokenEndpoint string `json:"token_endpoint"`
	}
	err := json.Unmarshal(sess.Out.Contents(), &response)
	Expect(err).NotTo(HaveOccurred())

	uaaBaseURL := response.TokenEndpoint
	Expect(uaaBaseURL).To(HavePrefix("https://uaa."))
	return uaaBaseURL
}

func AuthAsAdmin() {
	Auth(config.AdminUser, config.AdminPassword)
}

func appDir(appType string) string {
	return filepath.Join(appsDir, appType)
}

func pushProxy(appName string) {
	Expect(cf.Cf(
		"push", appName,
		"-p", appDir("proxy"),
		"-f", defaultManifest("proxy"),
	).Wait(Timeout_Push)).To(gexec.Exit(0))
}

func defaultManifest(appType string) string {
	return filepath.Join(appDir(appType), "manifest.yml")
}

func scaleApps(apps []string, instances int) {
	parallelRunner := &testsupport.ParallelRunner{
		NumWorkers: 16,
	}
	parallelRunner.RunOnSliceStrings(apps, func(app string) {
		scaleApp(app, instances)
	})
}

func scaleApp(appName string, instances int) {
	Expect(cf.Cf(
		"scale", appName,
		"-i", fmt.Sprintf("%d", instances),
	).Wait(Timeout_Short)).To(gexec.Exit(0))

	waitForAllInstancesToBeRunning(appName)
}

func pushAppWithInstanceCount(appName string, appCount int) {
	Expect(cf.Cf(
		"push", appName,
		"-p", appDir("proxy"),
		"-i", fmt.Sprintf("%d", appCount),
		"-f", defaultManifest("proxy"),
	).Wait(Timeout_Push)).To(gexec.Exit(0))

	waitForAllInstancesToBeRunning(appName)
}

func waitForAllInstancesToBeRunning(appName string) {
	appGuidSession := cf.Cf("app", appName, "--guid")
	Expect(appGuidSession.Wait(Timeout_Short)).To(gexec.Exit(0))

	capiURL := fmt.Sprintf("v2/apps/%s/instances", strings.TrimSpace(string(appGuidSession.Out.Contents())))

	type instanceInfo struct {
		State string `json:"state"`
	}

	instances := make(map[string]instanceInfo)

	allInstancesRunning := func() bool {
		session := cf.Cf("curl", capiURL)
		Expect(session.Wait(Timeout_Short)).To(gexec.Exit(0))

		json.Unmarshal(session.Out.Contents(), &instances)
		Expect(len(instances)).To(Not(BeEmpty()))

		for _, instance := range instances {
			if instance.State != "RUNNING" {
				return false
			}
		}
		return true
	}

	Eventually(allInstancesRunning).Should(Equal(true), "not all instances running")

}

func restage(appName string) {
	Expect(cf.Cf(
		"restage", appName,
	).Wait(Timeout_Push)).To(gexec.Exit(0))

	waitForAllInstancesToBeRunning(appName)
}

type AppInstance struct {
	hostIdentifier string
	index          string
	internalIP     string
}

func getAppInstances(appName string, instances int) []AppInstance {
	apps := make([]AppInstance, instances)
	for i := 0; i < instances; i++ {
		session := cf.Cf("ssh", appName, "-i", fmt.Sprintf("%d", i), "-c", "env | grep CF_INSTANCE")
		Expect(session.Wait(Timeout_Push)).To(gexec.Exit(0))

		env := strings.Split(string(session.Out.Contents()), "\n")
		var app AppInstance
		for _, envVar := range env {
			kv := strings.Split(envVar, "=")
			switch kv[0] {
			case "CF_INSTANCE_IP":
				app.hostIdentifier = kv[1]
			case "CF_INSTANCE_INDEX":
				app.index = kv[1]
			case "CF_INSTANCE_INTERNAL_IP":
				app.internalIP = kv[1]
			}
		}
		apps[i] = app
	}
	return apps
}

func findTwoInstancesOnTheSameHost(apps []AppInstance) (AppInstance, AppInstance) {
	hostsToApps := map[string]AppInstance{}

	for _, app := range apps {
		foundApp, ok := hostsToApps[app.hostIdentifier]
		if ok {
			return foundApp, app
		}
		hostsToApps[app.hostIdentifier] = app
	}

	Expect(errors.New("failed to find two instances on the same host")).ToNot(HaveOccurred())
	return AppInstance{}, AppInstance{}
}

func findTwoInstancesOnDifferentHosts(apps []AppInstance) (AppInstance, AppInstance) {
	for _, app := range apps[1:] {
		if apps[0].hostIdentifier != app.hostIdentifier {
			return apps[0], app
		}
	}

	Expect(errors.New("failed to find two instances on different hosts")).ToNot(HaveOccurred())
	return AppInstance{}, AppInstance{}
}
<commit_msg>Do not expect length to be empty. Oops.<commit_after>package acceptance_test

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"math/rand"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"code.cloudfoundry.org/cf-networking-helpers/testsupport"

	pusherConfig "cf-pusher/config"

	"github.com/cloudfoundry-incubator/cf-test-helpers/cf"
	"github.com/cloudfoundry-incubator/cf-test-helpers/helpers"
	. "github.com/onsi/ginkgo"
	ginkgoConfig "github.com/onsi/ginkgo/config"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gexec"
)

const Timeout_Push = 2 * time.Minute

var (
	appsDir    string
	config     helpers.Config
	testConfig pusherConfig.Config
)

func TestAcceptance(t *testing.T) {
	RegisterFailHandler(Fail)

	BeforeSuite(func() {
		config = helpers.LoadConfig()

		configPath := helpers.ConfigPath()
		configBytes, err := ioutil.ReadFile(configPath)
		Expect(err).NotTo(HaveOccurred())

		err = json.Unmarshal(configBytes, &testConfig)
		Expect(err).NotTo(HaveOccurred())

		if testConfig.Applications <= 0 {
			Fail("Applications count needs to be greater than 0")
		}

		if testConfig.AppInstances <= 0 {
			Fail("AppInstances count needs to be greater than 0")
		}

		if testConfig.ProxyApplications <= 0 {
			Fail("ProxyApplications count needs to be greater than 0")
		}

		if testConfig.ProxyInstances <= 0 {
			Fail("ProxyInstances count needs to be greater than 0")
		}

		Expect(cf.Cf("api", "--skip-ssl-validation", config.ApiEndpoint).Wait(Timeout_Short)).To(gexec.Exit(0))
		AuthAsAdmin()

		appsDir = os.Getenv("APPS_DIR")
		Expect(appsDir).NotTo(BeEmpty())

		rand.Seed(ginkgoConfig.GinkgoConfig.RandomSeed + int64(GinkgoParallelNode()))
	})

	RunSpecs(t, "Acceptance Suite")
}

func Auth(username, password string) {
	By("authenticating as " + username)
	cmd := exec.Command("cf", "auth", username, password)
	sess, err := gexec.Start(cmd, nil, nil)
	Expect(err).NotTo(HaveOccurred())
	Eventually(sess.Wait(Timeout_Short)).Should(gexec.Exit(0))
}

func getUAABaseURL() string {
	sess := cf.Cf("curl", "/v2/info")
	Eventually(sess.Wait(Timeout_Short)).Should(gexec.Exit(0))
	var response struct {
		TokenEndpoint string `json:"token_endpoint"`
	}
	err := json.Unmarshal(sess.Out.Contents(), &response)
	Expect(err).NotTo(HaveOccurred())

	uaaBaseURL := response.TokenEndpoint
	Expect(uaaBaseURL).To(HavePrefix("https://uaa."))
	return uaaBaseURL
}

func AuthAsAdmin() {
	Auth(config.AdminUser, config.AdminPassword)
}

func appDir(appType string) string {
	return filepath.Join(appsDir, appType)
}

func pushProxy(appName string) {
	Expect(cf.Cf(
		"push", appName,
		"-p", appDir("proxy"),
		"-f", defaultManifest("proxy"),
	).Wait(Timeout_Push)).To(gexec.Exit(0))
}

func defaultManifest(appType string) string {
	return filepath.Join(appDir(appType), "manifest.yml")
}

func scaleApps(apps []string, instances int) {
	parallelRunner := &testsupport.ParallelRunner{
		NumWorkers: 16,
	}
	parallelRunner.RunOnSliceStrings(apps, func(app string) {
		scaleApp(app, instances)
	})
}

func scaleApp(appName string, instances int) {
	Expect(cf.Cf(
		"scale", appName,
		"-i", fmt.Sprintf("%d", instances),
	).Wait(Timeout_Short)).To(gexec.Exit(0))

	waitForAllInstancesToBeRunning(appName)
}

func pushAppWithInstanceCount(appName string, appCount int) {
	Expect(cf.Cf(
		"push", appName,
		"-p", appDir("proxy"),
		"-i", fmt.Sprintf("%d", appCount),
		"-f", defaultManifest("proxy"),
	).Wait(Timeout_Push)).To(gexec.Exit(0))

	waitForAllInstancesToBeRunning(appName)
}

func waitForAllInstancesToBeRunning(appName string) {
	appGuidSession := cf.Cf("app", appName, "--guid")
	Expect(appGuidSession.Wait(Timeout_Short)).To(gexec.Exit(0))

	capiURL := fmt.Sprintf("v2/apps/%s/instances", strings.TrimSpace(string(appGuidSession.Out.Contents())))

	type instanceInfo struct {
		State string `json:"state"`
	}

	instances := make(map[string]instanceInfo)

	allInstancesRunning := func() bool {
		session := cf.Cf("curl", capiURL)
		Expect(session.Wait(Timeout_Short)).To(gexec.Exit(0))

		json.Unmarshal(session.Out.Contents(), &instances)
		Expect(instances).To(Not(BeEmpty()))

		for _, instance := range instances {
			if instance.State != "RUNNING" {
				return false
			}
		}
		return true
	}

	Eventually(allInstancesRunning).Should(Equal(true), "not all instances running")

}

func restage(appName string) {
	Expect(cf.Cf(
		"restage", appName,
	).Wait(Timeout_Push)).To(gexec.Exit(0))

	waitForAllInstancesToBeRunning(appName)
}

type AppInstance struct {
	hostIdentifier string
	index          string
	internalIP     string
}

func getAppInstances(appName string, instances int) []AppInstance {
	apps := make([]AppInstance, instances)
	for i := 0; i < instances; i++ {
		session := cf.Cf("ssh", appName, "-i", fmt.Sprintf("%d", i), "-c", "env | grep CF_INSTANCE")
		Expect(session.Wait(Timeout_Push)).To(gexec.Exit(0))

		env := strings.Split(string(session.Out.Contents()), "\n")
		var app AppInstance
		for _, envVar := range env {
			kv := strings.Split(envVar, "=")
			switch kv[0] {
			case "CF_INSTANCE_IP":
				app.hostIdentifier = kv[1]
			case "CF_INSTANCE_INDEX":
				app.index = kv[1]
			case "CF_INSTANCE_INTERNAL_IP":
				app.internalIP = kv[1]
			}
		}
		apps[i] = app
	}
	return apps
}

func findTwoInstancesOnTheSameHost(apps []AppInstance) (AppInstance, AppInstance) {
	hostsToApps := map[string]AppInstance{}

	for _, app := range apps {
		foundApp, ok := hostsToApps[app.hostIdentifier]
		if ok {
			return foundApp, app
		}
		hostsToApps[app.hostIdentifier] = app
	}

	Expect(errors.New("failed to find two instances on the same host")).ToNot(HaveOccurred())
	return AppInstance{}, AppInstance{}
}

func findTwoInstancesOnDifferentHosts(apps []AppInstance) (AppInstance, AppInstance) {
	for _, app := range apps[1:] {
		if apps[0].hostIdentifier != app.hostIdentifier {
			return apps[0], app
		}
	}

	Expect(errors.New("failed to find two instances on different hosts")).ToNot(HaveOccurred())
	return AppInstance{}, AppInstance{}
}
<|endoftext|>


<commit_before>package lms

import (
	"archive/zip"
	"context"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/aws/request"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/google/uuid"
)

const timeout = 60 * 60 * time.Second // max time for single upload (1h)

// Uploads single file from the server; Returns S3 path if successful
func uploadFileFromServerToS3(fileNameWithPath string) (error, string) {
	fileExtension := strings.ToUpper(filepath.Ext(fileNameWithPath))

	if fileExtension == ".H5P" {
		return unzipAndUploadH5P(fileNameWithPath)
	}
	return uploadSingleFileToS3("", fileNameWithPath)
}

func unzipAndUploadH5P(fileNameWithPath string) (error, string) {
	guid := strings.Replace(uuid.New().String(), "-", "", -1)
	unzipPath := getTempPath(guid + "/")

	err := Unzip(fileNameWithPath, unzipPath)
	if err != nil {
		return err, ""
	}
	// go through unzipped files, upload everything except dir's
	err = filepath.Walk(unzipPath,
		func(fileNameWithPath string, info os.FileInfo, err error) error {
			if err != nil {
				return err
			}

			if !info.IsDir() {
				fileNameWithoutTempPath := strings.Replace(fileNameWithPath, getTempPath(""), "", -1)
				s3Path := filepath.FromSlash("H5P/lessons/" + fileNameWithoutTempPath)
				s3Path = strings.Replace(s3Path, string(filepath.Separator), "/", -1) // fix path for S3
				err, _ := uploadSingleFileToS3(s3Path, fileNameWithPath)
				if err != nil {
					return err
				}
			}
			return nil
		})
	_ = os.RemoveAll(unzipPath)
	if err != nil {
		return err, ""
	}

	// upload template.html as it's needed to show the H5P content
	workingDir, _ := os.Getwd()
	fileNameWithPath = filepath.FromSlash(workingDir + "/client/H5Ptemplate.html")
	return uploadSingleFileToS3("H5P/lessons/"+guid+"/template.html", fileNameWithPath)
}

// Uploads single file from the server; Returns S3 path if successful
// S3 full path can be specified (optional)
func uploadSingleFileToS3(destinations3Path string, fileNameWithPath string) (error, string) {
	var key *string
	var uploadedFilePath string
	fileName := filepath.Base(fileNameWithPath)
	bucket := getEnvWithDefault("AWS_KB_BUCKET", "rt-knowledge-base-dev")

	if destinations3Path == "" {
		key = aws.String(fileName) // upload to Root dir if no specific path given
	} else {
		key = aws.String(destinations3Path)
	}
	uploadedFilePath = "https://" + bucket + ".s3.amazonaws.com/" + *key

	defaultRegion := getEnvWithDefault("AWS_REGION", "us-east-1")
	// Init session and service. Uses ENV variables AWS_ACCESS_KEY_ID & AWS_SECRET_ACCESS_KEY
	sess, err := session.NewSession(&aws.Config{Region: aws.String(defaultRegion)})
	if err != nil {
		return err, ""
	}
	amazonFileBucket := s3.New(sess)

	// To abort the upload if it takes more than timeout seconds
	ctx, cancelFn := context.WithTimeout(context.Background(), timeout)
	defer cancelFn() // Ensure the context is canceled to prevent leaking.

	file, IOerr := os.Open(fileNameWithPath)
	if IOerr != nil {
		return IOerr, ""
	}
	defer file.Close()

	_, err = amazonFileBucket.PutObjectWithContext(ctx, &s3.PutObjectInput{
		Bucket:      aws.String(bucket),
		Key:         key,
		Body:        file,
		ContentType: getContentType(fileNameWithPath),
	})

	if err != nil {
		if aerr, ok := err.(awserr.Error); ok && aerr.Code() == request.CanceledErrorCode {
			return err, "" // timeout
		} else {
			return err, ""
		}
	}

	return nil, uploadedFilePath
}

func getContentType(fileNameWithPath string) *string {

	fileExtension := strings.ToUpper(filepath.Ext(fileNameWithPath))
	if fileExtension == ".HTML" {
		return aws.String("text/html")
	} else if fileExtension == ".CSS" {
		return aws.String("text/css")
	} else if fileExtension == ".JS" {
		return aws.String("text/javascript")
	} else if fileExtension == ".JSON" {
		return aws.String("application/json")
	} else {
		file, err := os.Open(fileNameWithPath)
		if err != nil {
			return aws.String("binary/octet-stream")
		}
		defer file.Close()

		header := make([]byte, 512)
		_, _ = file.Read(header)
		return aws.String(http.DetectContentType(header))
	}
}

func getEnvWithDefault(key, fallback string) string {
	value, exists := os.LookupEnv(key)
	if !exists {
		return fallback
	}
	return value
}

func getTempPath(append string) string {
	workingDir, _ := os.Getwd()
	workingDir += "/temp/" + append

	return filepath.FromSlash(workingDir)
}

// todo: return JSON; Filter for H5PDist folder & list only uploads i.e guid/template.hmtl files; 500 response on errors
func ListFilesFromBucket(w http.ResponseWriter) {
	bucket := getEnvWithDefault("AWS_KB_BUCKET", "rt-knowledge-base-dev")
	defaultRegion := getEnvWithDefault("AWS_REGION", "us-east-1")

	// Init session and service. Uses ENV variables AWS_ACCESS_KEY_ID & AWS_SECRET_ACCESS_KEY
	sess, err1 := session.NewSession(&aws.Config{Region: aws.String(defaultRegion)})
	if err1 != nil {
		fmt.Fprintf(w, "Unable to list items in bucket %q, %v", bucket, err1)
		return
	}
	amazonFileBucket := s3.New(sess)

	resp, err := amazonFileBucket.ListObjectsV2(&s3.ListObjectsV2Input{Bucket: aws.String(bucket)})
	if err != nil {
		fmt.Fprintf(w, "Unable to list items in bucket %q, %v", bucket, err)
		return
	}

	for _, item := range resp.Contents {
		fmt.Fprintf(w, "https://"+bucket+".s3.amazonaws.com/"+*item.Key)
	}
}

// Saves single(first) file from http request to temp folder. Expects form key to be "file".
// on success returns full path to the received file
// todo: return 400 in case input was invalid
func saveFileFromHttpRequestToServer(r *http.Request) (error, string) {
	file, fileHeader, fileErr := r.FormFile("file")

	if fileErr != nil {
		log.Println(fileErr)
		log.Println("Content type was: " + r.Header.Get("Content-type"))
		return fileErr, ""
	}
	if file == nil || fileHeader == nil {
		return errors.New("Upload error: file and/or header missing."), ""
	}

	fileNameWithPath := getTempPath(fileHeader.Filename)

	f, err := os.Create(fileNameWithPath)
	if err != nil {
		return err, ""
	}
	io.Copy(f, file)
	defer f.Close()

	log.Println("HTTP -> Server upload complete. Received file: " + fileHeader.Filename)
	return nil, fileNameWithPath
}

func Unzip(src, dest string) error {
	r, err := zip.OpenReader(src)
	if err != nil {
		return err
	}
	defer r.Close()

	os.MkdirAll(dest, 0644)

	// Closure to address file descriptors issue with all the deferred .Close() methods
	extractAndWriteFile := func(f *zip.File) error {
		rc, err := f.Open()
		if err != nil {
			return err
		}
		defer rc.Close()

		path := filepath.Join(dest, f.Name)
		// Check for ZipSlip (Directory traversal)
		if !strings.HasPrefix(path, filepath.Clean(dest)+string(os.PathSeparator)) {
			return fmt.Errorf("illegal file path: %s", path)
		}

		if f.FileInfo().IsDir() {
			os.MkdirAll(path, f.Mode())
		} else {
			os.MkdirAll(filepath.Dir(path), f.Mode())
			f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
			if err != nil {
				return err
			}
			defer f.Close()

			_, err = io.Copy(f, rc)
			if err != nil {
				return err
			}
		}
		return nil
	}

	for _, f := range r.File {
		err := extractAndWriteFile(f)
		if err != nil {
			return err
		}
	}

	return nil
}
<commit_msg>Fix: returning all lessons not just first 1000; Feature: signed links + video uploads<commit_after>package lms

import (
	"archive/zip"
	"context"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/aws/request"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/google/uuid"
	"github.com/raintreeinc/knowledgebase/kb"
)

const timeout = 60 * 60 * time.Second // max time for single upload (1h)

// Uploads single video file from the server; Returns S3 path if successful
func uploadVideoFileFromServerToS3(fileNameWithPath, clientID, environment, guid string) (error, string) {
	year := strconv.Itoa(time.Now().Year())
	path := "videos/" + environment + "/" + clientID + "/" + year + "/" + guid + "_" + filepath.Base(fileNameWithPath)

	return uploadSingleFileToS3(path, fileNameWithPath, "rt-kb-videos")
}

// todo: return 200 / 40* / 500
// Deletes single video file from S3
func deleteVideoFileFromS3(key, bucket string) string {
	sess, err := session.NewSession(&aws.Config{Region: aws.String(getEnvWithDefault("AWS_REGION", "us-east-1"))})
	if err != nil {
		return ""
	}
	svc := s3.New(sess)

	if bucket == "" {
		bucket = getEnvWithDefault("AWS_KB_BUCKET", "rt-knowledge-base-dev")
	}

	prefix := "https://" + bucket + ".s3.amazonaws.com/"
	key = strings.Replace(key, prefix, "", -1)

	_, err = svc.DeleteObject(&s3.DeleteObjectInput{Bucket: aws.String(bucket), Key: aws.String(key)})
	if err != nil {
		return "Unable to delete given object"
	}

	err = svc.WaitUntilObjectNotExists(&s3.HeadObjectInput{
		Bucket: aws.String(bucket),
		Key:    aws.String(key),
	})
	if err != nil {
		return "Unable to delete given object"
	}

	return "OK"
}

// Uploads single file from the server; Returns S3 path if successful
func uploadFileFromServerToS3(fileNameWithPath string) (error, string) {
	fileExtension := strings.ToUpper(filepath.Ext(fileNameWithPath))

	if fileExtension == ".H5P" {
		return unzipAndUploadH5P(fileNameWithPath)
	}
	return uploadSingleFileToS3("", fileNameWithPath, "")
}

func unzipAndUploadH5P(fileNameWithPath string) (error, string) {
	guid := strings.Replace(uuid.New().String(), "-", "", -1)
	unzipPath := getTempPath(guid + "/")

	err := Unzip(fileNameWithPath, unzipPath)
	if err != nil {
		return err, ""
	}
	// go through unzipped files, upload everything except dir's
	err = filepath.Walk(unzipPath,
		func(fileNameWithPath string, info os.FileInfo, err error) error {
			if err != nil {
				return err
			}

			if !info.IsDir() {
				fileNameWithoutTempPath := strings.Replace(fileNameWithPath, getTempPath(""), "", -1)
				s3Path := filepath.FromSlash("H5P/lessons/" + fileNameWithoutTempPath)
				s3Path = strings.Replace(s3Path, string(filepath.Separator), "/", -1) // fix path for S3
				err, _ := uploadSingleFileToS3(s3Path, fileNameWithPath, "")
				if err != nil {
					return err
				}
			}
			return nil
		})
	_ = os.RemoveAll(unzipPath)
	if err != nil {
		return err, ""
	}

	// upload template.html as it's needed to show the H5P content
	workingDir, _ := os.Getwd()
	fileNameWithPath = filepath.FromSlash(workingDir + "/client/H5Ptemplate.html")
	return uploadSingleFileToS3("H5P/lessons/"+guid+"/template.html", fileNameWithPath, "")
}

// Uploads single file from the server; Returns S3 path if successful
// S3 full path can be specified (optional)
func uploadSingleFileToS3(destinations3Path, fileNameWithPath, bucket string) (error, string) {
	var key *string
	var uploadedFilePath string
	fileName := filepath.Base(fileNameWithPath)
	if bucket == "" {
		bucket = getEnvWithDefault("AWS_KB_BUCKET", "rt-knowledge-base-dev")
	}

	if destinations3Path == "" {
		key = aws.String(fileName) // upload to Root dir if no specific path given
	} else {
		key = aws.String(destinations3Path)
	}
	uploadedFilePath = "https://" + bucket + ".s3.amazonaws.com/" + *key

	defaultRegion := getEnvWithDefault("AWS_REGION", "us-east-1")
	// Init session and service. Uses ENV variables AWS_ACCESS_KEY_ID & AWS_SECRET_ACCESS_KEY
	sess, err := session.NewSession(&aws.Config{Region: aws.String(defaultRegion)})
	if err != nil {
		return err, ""
	}
	svc := s3.New(sess)

	// To abort the upload if it takes more than timeout seconds
	ctx, cancelFn := context.WithTimeout(context.Background(), timeout)
	defer cancelFn() // Ensure the context is canceled to prevent leaking.

	file, IOerr := os.Open(fileNameWithPath)
	if IOerr != nil {
		return IOerr, ""
	}
	defer file.Close()

	_, err = svc.PutObjectWithContext(ctx, &s3.PutObjectInput{
		Bucket:      aws.String(bucket),
		Key:         key,
		Body:        file,
		ContentType: getContentType(fileNameWithPath),
	})

	if err != nil {
		if aerr, ok := err.(awserr.Error); ok && aerr.Code() == request.CanceledErrorCode {
			return err, "" // timeout
		} else {
			return err, ""
		}
	}

	return nil, uploadedFilePath
}

func getContentType(fileNameWithPath string) *string {

	fileExtension := strings.ToUpper(filepath.Ext(fileNameWithPath))
	if fileExtension == ".HTML" {
		return aws.String("text/html")
	} else if fileExtension == ".CSS" {
		return aws.String("text/css")
	} else if fileExtension == ".JS" {
		return aws.String("text/javascript")
	} else if fileExtension == ".JSON" {
		return aws.String("application/json")
	} else {
		file, err := os.Open(fileNameWithPath)
		if err != nil {
			return aws.String("binary/octet-stream")
		}
		defer file.Close()

		header := make([]byte, 512)
		_, _ = file.Read(header)
		return aws.String(http.DetectContentType(header))
	}
}

func getEnvWithDefault(key, fallback string) string {
	value, exists := os.LookupEnv(key)
	if !exists {
		return fallback
	}
	return value
}

func getTempPath(append string) string {
	workingDir, _ := os.Getwd()
	workingDir += "/temp/" + append

	return filepath.FromSlash(workingDir)
}

func ListLessonsFromBucket(w http.ResponseWriter) {
	bucket := getEnvWithDefault("AWS_KB_BUCKET", "rt-knowledge-base-dev")
	defaultRegion := getEnvWithDefault("AWS_REGION", "us-east-1")

	// Init session and service. Uses ENV variables AWS_ACCESS_KEY_ID & AWS_SECRET_ACCESS_KEY
	sess, err1 := session.NewSession(&aws.Config{Region: aws.String(defaultRegion)})
	if err1 != nil {
		fmt.Fprintf(w, "Unable to list items from bucket %q, %v", bucket, err1)
		return
	}
	svc := s3.New(sess)

	params := &s3.ListObjectsInput{
		Bucket: aws.String(bucket),
		Prefix: aws.String("H5P/lessons"),
	}

	var result struct {
		Lessons []string `json:"lessons"`
	}

	err := svc.ListObjectsPages(params,
		func(response *s3.ListObjectsOutput, lastPage bool) bool {
			// Match URL-s up to lesson ID
			re := regexp.MustCompile(`^.+([/]{2}).+?([/]{1}).+?([/]{1}).+?([/]{1}).+?([/]{1})`)
			lessonLink := ""

			for _, item := range response.Contents {
				temp := re.FindString("https://" + bucket + ".s3.amazonaws.com/" + *item.Key)
				if lessonLink != temp {
					lessonLink = temp
					result.Lessons = append(result.Lessons, lessonLink+"template.html")
				}
			}
			// continue with the next page
			return true
		})

	if err != nil {
		fmt.Fprintf(w, "Unable to list all items from bucket %q, %v", bucket, err)
		return
	}

	data, err := json.Marshal(result)
	if err != nil {
		kb.WriteResult(w, err)
	}

	w.Write(data)
	w.Header().Set("Content-Type", "application/json")
}

// Saves single(first) file from http request to temp folder. Expects form key to be "file".
// on success returns full path to the received file
// todo: return 400 in case input was invalid
func saveFileFromHttpRequestToServer(r *http.Request) (error, string) {
	file, fileHeader, fileErr := r.FormFile("file")

	if fileErr != nil {
		log.Println(fileErr)
		log.Println("Content type was: " + r.Header.Get("Content-type"))
		return fileErr, ""
	}
	if file == nil || fileHeader == nil {
		return errors.New("Upload error: file and/or header missing."), ""
	}

	fileNameWithPath := getTempPath(fileHeader.Filename)

	f, err := os.Create(fileNameWithPath)
	if err != nil {
		return err, ""
	}
	io.Copy(f, file)
	defer f.Close()

	log.Println("HTTP -> Server upload complete. Received file: " + fileHeader.Filename)
	return nil, fileNameWithPath
}

func Unzip(src, dest string) error {
	r, err := zip.OpenReader(src)
	if err != nil {
		return err
	}
	defer r.Close()

	os.MkdirAll(dest, 0644)

	// Closure to address file descriptors issue with all the deferred .Close() methods
	extractAndWriteFile := func(f *zip.File) error {
		rc, err := f.Open()
		if err != nil {
			return err
		}
		defer rc.Close()

		path := filepath.Join(dest, f.Name)
		// Check for ZipSlip (Directory traversal)
		if !strings.HasPrefix(path, filepath.Clean(dest)+string(os.PathSeparator)) {
			return fmt.Errorf("illegal file path: %s", path)
		}

		if f.FileInfo().IsDir() {
			os.MkdirAll(path, f.Mode())
		} else {
			os.MkdirAll(filepath.Dir(path), f.Mode())
			f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
			if err != nil {
				return err
			}
			defer f.Close()

			_, err = io.Copy(f, rc)
			if err != nil {
				return err
			}
		}
		return nil
	}

	for _, f := range r.File {
		err := extractAndWriteFile(f)
		if err != nil {
			return err
		}
	}

	return nil
}

// todo: error out only if bucket does not exist and err. happens; i.e ignore bucket exists errors
func createBucket(bucketName string) error {
	sess, err := session.NewSession(&aws.Config{Region: aws.String(getEnvWithDefault("AWS_REGION", "us-east-1"))})
	if err != nil {
		return err
	}
	svc := s3.New(sess)

	_, err = svc.CreateBucket(&s3.CreateBucketInput{
		Bucket: aws.String("rt-videos-" + bucketName),
	})
	if err != nil {
		return err
	}

	err = svc.WaitUntilBucketExists(&s3.HeadBucketInput{
		Bucket: aws.String(bucketName),
	})
	if err != nil {
		return err
	}

	return nil
}

func getSignedLink(key, bucket string) string {
	sess, err := session.NewSession(&aws.Config{Region: aws.String(getEnvWithDefault("AWS_REGION", "us-east-1"))})
	if err != nil {
		return ""
	}
	svc := s3.New(sess)

	if bucket == "" {
		bucket = getEnvWithDefault("AWS_KB_BUCKET", "rt-knowledge-base-dev")
	}

	prefix := "https://" + bucket + ".s3.amazonaws.com/"
	key = strings.Replace(key, prefix, "", -1)

	req, _ := svc.GetObjectRequest(&s3.GetObjectInput{
		Bucket: aws.String(bucket),
		Key:    aws.String(key),
	})
	urlStr, err := req.Presign(8 * 60 * time.Minute)

	if err != nil {
		return ""
	}

	return base64.StdEncoding.EncodeToString([]byte(urlStr))
}
<|endoftext|>