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

// Package certs implements a CertSource which speaks to the public Cloud SQL API endpoint.
package certs

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"encoding/pem"
	"errors"
	"fmt"
	"math"
	mrand "math/rand"
	"net/http"
	"strings"
	"time"

	"github.com/GoogleCloudPlatform/cloudsql-proxy/logging"
	"github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/util"
	"golang.org/x/oauth2"
	"google.golang.org/api/googleapi"
	sqladmin "google.golang.org/api/sqladmin/v1beta4"
)

const defaultUserAgent = "custom cloud_sql_proxy version >= 1.10"

// NewCertSource returns a CertSource which can be used to authenticate using
// the provided client, which must not be nil.
//
// This function is deprecated; use NewCertSourceOpts instead.
func NewCertSource(host string, c *http.Client, checkRegion bool) *RemoteCertSource {
	return NewCertSourceOpts(c, RemoteOpts{
		APIBasePath:  host,
		IgnoreRegion: !checkRegion,
		UserAgent:    defaultUserAgent,
	})
}

// RemoteOpts are a collection of options for NewCertSourceOpts. All fields are
// optional.
type RemoteOpts struct {
	// APIBasePath specifies the base path for the sqladmin API. If left blank,
	// the default from the autogenerated sqladmin library is used (which is
	// sufficient for nearly all users)
	APIBasePath string

	// IgnoreRegion specifies whether a missing or mismatched region in the
	// instance name should be ignored. In a future version this value will be
	// forced to 'false' by the RemoteCertSource.
	IgnoreRegion bool

	// A string for the RemoteCertSource to identify itself when contacting the
	// sqladmin API.
	UserAgent string

	// IP address type options
	IPAddrTypeOpts []string

	// Enable IAM proxy db authentication
	EnableIAMLogin bool

	// Token source for token information used in cert creation
	TokenSource oauth2.TokenSource
}

// NewCertSourceOpts returns a CertSource configured with the provided Opts.
// The provided http.Client must not be nil.
//
// Use this function instead of NewCertSource; it has a more forward-compatible
// signature.
func NewCertSourceOpts(c *http.Client, opts RemoteOpts) *RemoteCertSource {
	pkey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		panic(err) // very unexpected.
	}
	serv, err := sqladmin.New(c)
	if err != nil {
		panic(err) // Only will happen if the provided client is nil.
	}
	if opts.APIBasePath != "" {
		serv.BasePath = opts.APIBasePath
	}
	ua := opts.UserAgent
	if ua == "" {
		ua = defaultUserAgent
	}
	serv.UserAgent = ua

	// Set default value to be "PUBLIC,PRIVATE" if not specified
	if len(opts.IPAddrTypeOpts) == 0 {
		opts.IPAddrTypeOpts = []string{"PUBLIC", "PRIVATE"}
	}

	// Add "PUBLIC" as an alias for "PRIMARY"
	for index, ipAddressType := range opts.IPAddrTypeOpts {
		if strings.ToUpper(ipAddressType) == "PUBLIC" {
			opts.IPAddrTypeOpts[index] = "PRIMARY"
		}
	}

	return &RemoteCertSource{pkey, serv, !opts.IgnoreRegion, opts.IPAddrTypeOpts, opts.EnableIAMLogin, opts.TokenSource}
}

// RemoteCertSource implements a CertSource, using Cloud SQL APIs to
// return Local certificates for identifying oneself as a specific user
// to the remote instance and Remote certificates for confirming the
// remote database's identity.
type RemoteCertSource struct {
	// key is the private key used for certificates returned by Local.
	key *rsa.PrivateKey
	// serv is used to make authenticated API calls to Cloud SQL.
	serv *sqladmin.Service
	// If set, providing an incorrect region in their connection string will be
	// treated as an error. This is to provide the same functionality that will
	// occur when API calls require the region.
	checkRegion bool
	// a list of ip address types that users select
	IPAddrTypes []string
	// flag to enable IAM proxy db authentication
	EnableIAMLogin bool
	// token source for the token information used in cert creation
	TokenSource oauth2.TokenSource
}

// Constants for backoffAPIRetry. These cause the retry logic to scale the
// backoff delay from 200ms to around 3.5s.
const (
	baseBackoff    = float64(200 * time.Millisecond)
	backoffMult    = 1.618
	backoffRetries = 5
)

func backoffAPIRetry(desc, instance string, do func() error) error {
	var err error
	for i := 0; i < backoffRetries; i++ {
		err = do()
		gErr, ok := err.(*googleapi.Error)
		switch {
		case !ok:
			// 'ok' will also be false if err is nil.
			return err
		case gErr.Code == 403 && len(gErr.Errors) > 0 && gErr.Errors[0].Reason == "insufficientPermissions":
			// The case where the admin API has not yet been enabled.
			return fmt.Errorf("ensure that the Cloud SQL API is enabled for your project (https://console.cloud.google.com/flows/enableapi?apiid=sqladmin). Error during %s %s: %v", desc, instance, err)
		case gErr.Code == 404 || gErr.Code == 403:
			return fmt.Errorf("ensure that the account has access to %q (and make sure there's no typo in that name). Error during %s %s: %v", instance, desc, instance, err)
		case gErr.Code < 500:
			// Only Server-level HTTP errors are immediately retryable.
			return err
		}

		// sleep = baseBackoff * backoffMult^(retries + randomFactor)
		exp := float64(i+1) + mrand.Float64()
		sleep := time.Duration(baseBackoff * math.Pow(backoffMult, exp))
		logging.Errorf("Error in %s %s: %v; retrying in %v", desc, instance, err, sleep)
		time.Sleep(sleep)
	}
	return err
}

func refreshToken(ts oauth2.TokenSource, tok *oauth2.Token) (*oauth2.Token, error) {
	expiredToken := &oauth2.Token{
		AccessToken:  tok.AccessToken,
		TokenType:    tok.TokenType,
		RefreshToken: tok.RefreshToken,
		Expiry:       time.Time{}.Add(1), // Expired
	}
	return oauth2.ReuseTokenSource(expiredToken, ts).Token()
}

// Local returns a certificate that may be used to establish a TLS
// connection to the specified instance.
func (s *RemoteCertSource) Local(instance string) (tls.Certificate, error) {
	pkix, err := x509.MarshalPKIXPublicKey(&s.key.PublicKey)
	if err != nil {
		return tls.Certificate{}, err
	}

	p, r, n := util.SplitName(instance)
	regionName := fmt.Sprintf("%s~%s", r, n)
	pubKey := string(pem.EncodeToMemory(&pem.Block{Bytes: pkix, Type: "RSA PUBLIC KEY"}))
	createEphemeralRequest := sqladmin.SslCertsCreateEphemeralRequest{
		PublicKey: pubKey,
	}
	var tok *oauth2.Token
	// If IAM login is enabled, add the OAuth2 token into the ephemeral
	// certificate request.
	if s.EnableIAMLogin {
		var tokErr error
		tok, tokErr = s.TokenSource.Token()
		if err != nil {
			return tls.Certificate{}, tokErr
		}
		// Always refresh the token to ensure its expiration is far enough in
		// the future.
		tok, tokErr = refreshToken(s.TokenSource, tok)
		if tokErr != nil {
			return tls.Certificate{}, tokErr
		}
		createEphemeralRequest.AccessToken = tok.AccessToken
	}
	req := s.serv.SslCerts.CreateEphemeral(p, regionName, &createEphemeralRequest)

	var data *sqladmin.SslCert
	err = backoffAPIRetry("createEphemeral for", instance, func() error {
		data, err = req.Do()
		return err
	})
	if err != nil {
		return tls.Certificate{}, err
	}

	c, err := parseCert(data.Cert)
	if err != nil {
		return tls.Certificate{}, fmt.Errorf("couldn't parse ephemeral certificate for instance %q: %v", instance, err)
	}
	if s.EnableIAMLogin {
		// Adjust the certificate's expiration to be the earlier of tok.Expiry or c.NotAfter
		if tok.Expiry.Before(c.NotAfter) {
			c.NotAfter = tok.Expiry
		}
	}
	return tls.Certificate{
		Certificate: [][]byte{c.Raw},
		PrivateKey:  s.key,
		Leaf:        c,
	}, nil
}

func parseCert(pemCert string) (*x509.Certificate, error) {
	bl, _ := pem.Decode([]byte(pemCert))
	if bl == nil {
		return nil, errors.New("invalid PEM: " + pemCert)
	}
	return x509.ParseCertificate(bl.Bytes)
}

// Find the first matching IP address by user input IP address types
func (s *RemoteCertSource) findIPAddr(data *sqladmin.DatabaseInstance, instance string) (ipAddrInUse string, err error) {
	for _, eachIPAddrTypeByUser := range s.IPAddrTypes {
		for _, eachIPAddrTypeOfInstance := range data.IpAddresses {
			if strings.ToUpper(eachIPAddrTypeOfInstance.Type) == strings.ToUpper(eachIPAddrTypeByUser) {
				ipAddrInUse = eachIPAddrTypeOfInstance.IpAddress
				return ipAddrInUse, nil
			}
		}
	}

	ipAddrTypesOfInstance := ""
	for _, eachIPAddrTypeOfInstance := range data.IpAddresses {
		ipAddrTypesOfInstance += fmt.Sprintf("(TYPE=%v, IP_ADDR=%v)", eachIPAddrTypeOfInstance.Type, eachIPAddrTypeOfInstance.IpAddress)
	}

	ipAddrTypeOfUser := fmt.Sprintf("%v", s.IPAddrTypes)

	return "", fmt.Errorf("User input IP address type %v does not match the instance %v, the instance's IP addresses are %v ", ipAddrTypeOfUser, instance, ipAddrTypesOfInstance)
}

// Remote returns the specified instance's CA certificate, address, and name.
func (s *RemoteCertSource) Remote(instance string) (cert *x509.Certificate, addr, name, version string, err error) {
	p, region, n := util.SplitName(instance)
	regionName := fmt.Sprintf("%s~%s", region, n)
	req := s.serv.Instances.Get(p, regionName)

	var data *sqladmin.DatabaseInstance
	err = backoffAPIRetry("get instance", instance, func() error {
		data, err = req.Do()
		return err
	})
	if err != nil {
		return nil, "", "", "", err
	}

	// TODO(chowski): remove this when us-central is removed.
	if data.Region == "us-central" {
		data.Region = "us-central1"
	}
	if data.Region != region {
		if region == "" {
			err = fmt.Errorf("instance %v doesn't provide region", instance)
		} else {
			err = fmt.Errorf(`for connection string "%s": got region %q, want %q`, instance, region, data.Region)
		}
		if s.checkRegion {
			return nil, "", "", "", err
		}
		logging.Errorf("%v", err)
		logging.Errorf("WARNING: specifying the correct region in an instance string will become required in a future version!")
	}

	if len(data.IpAddresses) == 0 {
		return nil, "", "", "", fmt.Errorf("no IP address found for %v", instance)
	}
	if data.BackendType == "FIRST_GEN" {
		logging.Errorf("WARNING: proxy client does not support first generation Cloud SQL instances.")
		return nil, "", "", "", fmt.Errorf("%q is a first generation instance", instance)
	}

	// Find the first matching IP address by user input IP address types
	ipAddrInUse := ""
	ipAddrInUse, err = s.findIPAddr(data, instance)
	if err != nil {
		return nil, "", "", "", err
	}

	c, err := parseCert(data.ServerCaCert.Cert)

	return c, ipAddrInUse, p + ":" + n, data.DatabaseVersion, err
}
<commit_msg>fix(certs): fix panic on error (#682)<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package certs implements a CertSource which speaks to the public Cloud SQL API endpoint.
package certs

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"encoding/pem"
	"errors"
	"fmt"
	"math"
	mrand "math/rand"
	"net/http"
	"strings"
	"time"

	"github.com/GoogleCloudPlatform/cloudsql-proxy/logging"
	"github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/util"
	"golang.org/x/oauth2"
	"google.golang.org/api/googleapi"
	sqladmin "google.golang.org/api/sqladmin/v1beta4"
)

const defaultUserAgent = "custom cloud_sql_proxy version >= 1.10"

// NewCertSource returns a CertSource which can be used to authenticate using
// the provided client, which must not be nil.
//
// This function is deprecated; use NewCertSourceOpts instead.
func NewCertSource(host string, c *http.Client, checkRegion bool) *RemoteCertSource {
	return NewCertSourceOpts(c, RemoteOpts{
		APIBasePath:  host,
		IgnoreRegion: !checkRegion,
		UserAgent:    defaultUserAgent,
	})
}

// RemoteOpts are a collection of options for NewCertSourceOpts. All fields are
// optional.
type RemoteOpts struct {
	// APIBasePath specifies the base path for the sqladmin API. If left blank,
	// the default from the autogenerated sqladmin library is used (which is
	// sufficient for nearly all users)
	APIBasePath string

	// IgnoreRegion specifies whether a missing or mismatched region in the
	// instance name should be ignored. In a future version this value will be
	// forced to 'false' by the RemoteCertSource.
	IgnoreRegion bool

	// A string for the RemoteCertSource to identify itself when contacting the
	// sqladmin API.
	UserAgent string

	// IP address type options
	IPAddrTypeOpts []string

	// Enable IAM proxy db authentication
	EnableIAMLogin bool

	// Token source for token information used in cert creation
	TokenSource oauth2.TokenSource
}

// NewCertSourceOpts returns a CertSource configured with the provided Opts.
// The provided http.Client must not be nil.
//
// Use this function instead of NewCertSource; it has a more forward-compatible
// signature.
func NewCertSourceOpts(c *http.Client, opts RemoteOpts) *RemoteCertSource {
	pkey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		panic(err) // very unexpected.
	}
	serv, err := sqladmin.New(c)
	if err != nil {
		panic(err) // Only will happen if the provided client is nil.
	}
	if opts.APIBasePath != "" {
		serv.BasePath = opts.APIBasePath
	}
	ua := opts.UserAgent
	if ua == "" {
		ua = defaultUserAgent
	}
	serv.UserAgent = ua

	// Set default value to be "PUBLIC,PRIVATE" if not specified
	if len(opts.IPAddrTypeOpts) == 0 {
		opts.IPAddrTypeOpts = []string{"PUBLIC", "PRIVATE"}
	}

	// Add "PUBLIC" as an alias for "PRIMARY"
	for index, ipAddressType := range opts.IPAddrTypeOpts {
		if strings.ToUpper(ipAddressType) == "PUBLIC" {
			opts.IPAddrTypeOpts[index] = "PRIMARY"
		}
	}

	return &RemoteCertSource{pkey, serv, !opts.IgnoreRegion, opts.IPAddrTypeOpts, opts.EnableIAMLogin, opts.TokenSource}
}

// RemoteCertSource implements a CertSource, using Cloud SQL APIs to
// return Local certificates for identifying oneself as a specific user
// to the remote instance and Remote certificates for confirming the
// remote database's identity.
type RemoteCertSource struct {
	// key is the private key used for certificates returned by Local.
	key *rsa.PrivateKey
	// serv is used to make authenticated API calls to Cloud SQL.
	serv *sqladmin.Service
	// If set, providing an incorrect region in their connection string will be
	// treated as an error. This is to provide the same functionality that will
	// occur when API calls require the region.
	checkRegion bool
	// a list of ip address types that users select
	IPAddrTypes []string
	// flag to enable IAM proxy db authentication
	EnableIAMLogin bool
	// token source for the token information used in cert creation
	TokenSource oauth2.TokenSource
}

// Constants for backoffAPIRetry. These cause the retry logic to scale the
// backoff delay from 200ms to around 3.5s.
const (
	baseBackoff    = float64(200 * time.Millisecond)
	backoffMult    = 1.618
	backoffRetries = 5
)

func backoffAPIRetry(desc, instance string, do func() error) error {
	var err error
	for i := 0; i < backoffRetries; i++ {
		err = do()
		gErr, ok := err.(*googleapi.Error)
		switch {
		case !ok:
			// 'ok' will also be false if err is nil.
			return err
		case gErr.Code == 403 && len(gErr.Errors) > 0 && gErr.Errors[0].Reason == "insufficientPermissions":
			// The case where the admin API has not yet been enabled.
			return fmt.Errorf("ensure that the Cloud SQL API is enabled for your project (https://console.cloud.google.com/flows/enableapi?apiid=sqladmin). Error during %s %s: %v", desc, instance, err)
		case gErr.Code == 404 || gErr.Code == 403:
			return fmt.Errorf("ensure that the account has access to %q (and make sure there's no typo in that name). Error during %s %s: %v", instance, desc, instance, err)
		case gErr.Code < 500:
			// Only Server-level HTTP errors are immediately retryable.
			return err
		}

		// sleep = baseBackoff * backoffMult^(retries + randomFactor)
		exp := float64(i+1) + mrand.Float64()
		sleep := time.Duration(baseBackoff * math.Pow(backoffMult, exp))
		logging.Errorf("Error in %s %s: %v; retrying in %v", desc, instance, err, sleep)
		time.Sleep(sleep)
	}
	return err
}

func refreshToken(ts oauth2.TokenSource, tok *oauth2.Token) (*oauth2.Token, error) {
	expiredToken := &oauth2.Token{
		AccessToken:  tok.AccessToken,
		TokenType:    tok.TokenType,
		RefreshToken: tok.RefreshToken,
		Expiry:       time.Time{}.Add(1), // Expired
	}
	return oauth2.ReuseTokenSource(expiredToken, ts).Token()
}

// Local returns a certificate that may be used to establish a TLS
// connection to the specified instance.
func (s *RemoteCertSource) Local(instance string) (tls.Certificate, error) {
	pkix, err := x509.MarshalPKIXPublicKey(&s.key.PublicKey)
	if err != nil {
		return tls.Certificate{}, err
	}

	p, r, n := util.SplitName(instance)
	regionName := fmt.Sprintf("%s~%s", r, n)
	pubKey := string(pem.EncodeToMemory(&pem.Block{Bytes: pkix, Type: "RSA PUBLIC KEY"}))
	createEphemeralRequest := sqladmin.SslCertsCreateEphemeralRequest{
		PublicKey: pubKey,
	}
	var tok *oauth2.Token
	// If IAM login is enabled, add the OAuth2 token into the ephemeral
	// certificate request.
	if s.EnableIAMLogin {
		var tokErr error
		tok, tokErr = s.TokenSource.Token()
		if tokErr != nil {
			return tls.Certificate{}, tokErr
		}
		// Always refresh the token to ensure its expiration is far enough in
		// the future.
		tok, tokErr = refreshToken(s.TokenSource, tok)
		if tokErr != nil {
			return tls.Certificate{}, tokErr
		}
		createEphemeralRequest.AccessToken = tok.AccessToken
	}
	req := s.serv.SslCerts.CreateEphemeral(p, regionName, &createEphemeralRequest)

	var data *sqladmin.SslCert
	err = backoffAPIRetry("createEphemeral for", instance, func() error {
		data, err = req.Do()
		return err
	})
	if err != nil {
		return tls.Certificate{}, err
	}

	c, err := parseCert(data.Cert)
	if err != nil {
		return tls.Certificate{}, fmt.Errorf("couldn't parse ephemeral certificate for instance %q: %v", instance, err)
	}
	if s.EnableIAMLogin {
		// Adjust the certificate's expiration to be the earlier of tok.Expiry or c.NotAfter
		if tok.Expiry.Before(c.NotAfter) {
			c.NotAfter = tok.Expiry
		}
	}
	return tls.Certificate{
		Certificate: [][]byte{c.Raw},
		PrivateKey:  s.key,
		Leaf:        c,
	}, nil
}

func parseCert(pemCert string) (*x509.Certificate, error) {
	bl, _ := pem.Decode([]byte(pemCert))
	if bl == nil {
		return nil, errors.New("invalid PEM: " + pemCert)
	}
	return x509.ParseCertificate(bl.Bytes)
}

// Find the first matching IP address by user input IP address types
func (s *RemoteCertSource) findIPAddr(data *sqladmin.DatabaseInstance, instance string) (ipAddrInUse string, err error) {
	for _, eachIPAddrTypeByUser := range s.IPAddrTypes {
		for _, eachIPAddrTypeOfInstance := range data.IpAddresses {
			if strings.ToUpper(eachIPAddrTypeOfInstance.Type) == strings.ToUpper(eachIPAddrTypeByUser) {
				ipAddrInUse = eachIPAddrTypeOfInstance.IpAddress
				return ipAddrInUse, nil
			}
		}
	}

	ipAddrTypesOfInstance := ""
	for _, eachIPAddrTypeOfInstance := range data.IpAddresses {
		ipAddrTypesOfInstance += fmt.Sprintf("(TYPE=%v, IP_ADDR=%v)", eachIPAddrTypeOfInstance.Type, eachIPAddrTypeOfInstance.IpAddress)
	}

	ipAddrTypeOfUser := fmt.Sprintf("%v", s.IPAddrTypes)

	return "", fmt.Errorf("User input IP address type %v does not match the instance %v, the instance's IP addresses are %v ", ipAddrTypeOfUser, instance, ipAddrTypesOfInstance)
}

// Remote returns the specified instance's CA certificate, address, and name.
func (s *RemoteCertSource) Remote(instance string) (cert *x509.Certificate, addr, name, version string, err error) {
	p, region, n := util.SplitName(instance)
	regionName := fmt.Sprintf("%s~%s", region, n)
	req := s.serv.Instances.Get(p, regionName)

	var data *sqladmin.DatabaseInstance
	err = backoffAPIRetry("get instance", instance, func() error {
		data, err = req.Do()
		return err
	})
	if err != nil {
		return nil, "", "", "", err
	}

	// TODO(chowski): remove this when us-central is removed.
	if data.Region == "us-central" {
		data.Region = "us-central1"
	}
	if data.Region != region {
		if region == "" {
			err = fmt.Errorf("instance %v doesn't provide region", instance)
		} else {
			err = fmt.Errorf(`for connection string "%s": got region %q, want %q`, instance, region, data.Region)
		}
		if s.checkRegion {
			return nil, "", "", "", err
		}
		logging.Errorf("%v", err)
		logging.Errorf("WARNING: specifying the correct region in an instance string will become required in a future version!")
	}

	if len(data.IpAddresses) == 0 {
		return nil, "", "", "", fmt.Errorf("no IP address found for %v", instance)
	}
	if data.BackendType == "FIRST_GEN" {
		logging.Errorf("WARNING: proxy client does not support first generation Cloud SQL instances.")
		return nil, "", "", "", fmt.Errorf("%q is a first generation instance", instance)
	}

	// Find the first matching IP address by user input IP address types
	ipAddrInUse := ""
	ipAddrInUse, err = s.findIPAddr(data, instance)
	if err != nil {
		return nil, "", "", "", err
	}

	c, err := parseCert(data.ServerCaCert.Cert)

	return c, ipAddrInUse, p + ":" + n, data.DatabaseVersion, err
}
<|endoftext|>


<commit_before>package cli

import (
	"fmt"
	"io"

	"github.com/codegangsta/cli"
	"polydawn.net/repeatr/def"
	"polydawn.net/repeatr/executor/dispatch"
	"polydawn.net/repeatr/lib/guid"
)

func TwerkCommandPattern(stdin io.Reader, stdout, stderr io.Writer) cli.Command {
	return cli.Command{
		Name:  "twerk",
		Usage: "Run one-time-use interactive (thus nonrepeatable!) command.  All the defaults are filled in for you.  Great for experimentation.",
		Action: func(ctx *cli.Context) {
			executor := executordispatch.Get("chroot")
			formula := def.Formula{
				Inputs: []def.Input{{
					Type:      "tar",
					MountPath: "/",
					Hash:      "uJRF46th6rYHt0zt_n3fcDuBfGFVPS6lzRZla5hv6iDoh5DVVzxUTMMzENfPoboL",
					Warehouses: []string{
						"http+ca://repeatr.s3.amazonaws.com/assets/",
					},
				}},
				Action: def.Action{
					Entrypoint: []string{"bash"},
				},
			}

			// TODO bonus points if you eventually can get the default mode to have no setuid binaries, in addition to making a spare user and dropping privs immediately.

			job := executor.Start(formula, def.JobID(guid.New()), stdin, ctx.App.Writer)
			go io.Copy(stdout, job.Outputs().Reader(1))
			go io.Copy(stderr, job.Outputs().Reader(2))
			result := job.Wait()
			if result.Error != nil {
				fmt.Fprintf(ctx.App.Writer, "error: %s\n", result.Error)
			} else {
				fmt.Fprintf(ctx.App.Writer, "done; exit code %d\n", result.ExitCode)
			}
		},
	}
}
<commit_msg>Update default base image for interactive mode.<commit_after>package cli

import (
	"fmt"
	"io"

	"github.com/codegangsta/cli"
	"polydawn.net/repeatr/def"
	"polydawn.net/repeatr/executor/dispatch"
	"polydawn.net/repeatr/lib/guid"
)

func TwerkCommandPattern(stdin io.Reader, stdout, stderr io.Writer) cli.Command {
	return cli.Command{
		Name:  "twerk",
		Usage: "Run one-time-use interactive (thus nonrepeatable!) command.  All the defaults are filled in for you.  Great for experimentation.",
		Action: func(ctx *cli.Context) {
			executor := executordispatch.Get("chroot")
			formula := def.Formula{
				Inputs: []def.Input{{
					Type:      "tar",
					MountPath: "/",
					Hash:      "lzcqJKln2_H4TIoizNBCr0qoh8u_Nb_LRwARTZL2RumfbChX031pVl46dcSCG4q3",
					Warehouses: []string{
						"http+ca://repeatr.s3.amazonaws.com/assets/",
					},
				}},
				Action: def.Action{
					Entrypoint: []string{"bash"},
				},
			}

			// TODO bonus points if you eventually can get the default mode to have no setuid binaries, in addition to making a spare user and dropping privs immediately.

			job := executor.Start(formula, def.JobID(guid.New()), stdin, ctx.App.Writer)
			go io.Copy(stdout, job.Outputs().Reader(1))
			go io.Copy(stderr, job.Outputs().Reader(2))
			result := job.Wait()
			if result.Error != nil {
				fmt.Fprintf(ctx.App.Writer, "error: %s\n", result.Error)
			} else {
				fmt.Fprintf(ctx.App.Writer, "done; exit code %d\n", result.ExitCode)
			}
		},
	}
}
<|endoftext|>


<commit_before>package server

import (
	"crypto/rand"
	"crypto/rsa"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"strconv"
	"strings"

	"github.com/gorilla/websocket"
	"github.com/leonlee/jrdwp/common"
)

type WSServer struct {
	port      int
	path      string
	origin    string
	client    *TCPClient
	key       *rsa.PrivateKey
	jdwpPorts []int
	jdwpPort  int
}

func NewWSServer(path string, origin string, port int, jdwpPorts []int, client *TCPClient) *WSServer {
	if !strings.HasPrefix(path, "/") {
		path = "/" + path
	}

	return &WSServer{
		port:      port,
		path:      path,
		origin:    origin,
		client:    client,
		jdwpPorts: jdwpPorts,
	}
}

func (server *WSServer) Start() {
	if err := initKey(server); err != nil {
		log.Fatalln("can't start ws server", err.Error())
	}

	addr := fmt.Sprintf(":%d", server.port)
	log.Printf("starting ws server: %s\n", addr)
	log.Printf("ws path: %s\n", server.path)
	server.listen(addr)

	defer func() {
		if err := recover(); err != nil {
			log.Println("recovering from:", err)
			server.listen(addr)
		}
	}()
}

func initKey(server *WSServer) error {
	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		log.Println("can't generate key", err.Error())
		return err
	}
	server.key = privateKey

	publicKeyBytes, err := common.PublicKeyToBytes(&privateKey.PublicKey)
	if err != nil {
		log.Println("can't convert public key to bytes", err.Error())
		return err
	}
	log.Printf("generated public key:\n\n%s\n", publicKeyBytes)

	err = ioutil.WriteFile(common.PublicKeyPath(), publicKeyBytes, 0655)
	if err != nil {
		log.Println("can't write public key", common.PublicKeyPath(), err.Error())
		return err
	}

	return nil
}

func (server *WSServer) listen(addr string) {
	http.HandleFunc(server.path, server.onMessage)
	if err := http.ListenAndServe(addr, nil); err != nil {
		log.Fatal(err)
	}
}

func (server *WSServer) onMessage(w http.ResponseWriter, request *http.Request) {
	var conn *websocket.Conn
	var clientConn *net.TCPConn

	defer closeOnFail(conn, clientConn)

	var upgrader = websocket.Upgrader{
		CheckOrigin: func(req *http.Request) bool {
			return server.verifyRequest(req)
		},
	}

	conn, err := upgrader.Upgrade(w, request, nil)
	if err != nil {
		log.Panicln("upgrade:", err)
	}

	clientConn, err = server.client.Connect(server.jdwpPort)
	if err != nil {
		log.Panicln("can't connect to jvm:", err)
	}

	common.InitTCPConn(clientConn)

	go send(conn, clientConn)
	go receive(conn, clientConn)
}

func closeOnFail(conn *websocket.Conn, clientConn *net.TCPConn) {
	if err := recover(); err != nil {
		log.Println("recover from", err)
		if conn != nil {
			conn.Close()
		}
		if clientConn != nil {
			clientConn.Close()
		}
	}
}

func (server *WSServer) verifyRequest(req *http.Request) bool {
	port, err := strconv.Atoi(req.Header.Get(common.HeaderPort))
	if err != nil {
		log.Println("bad port", err.Error())
		return false
	}
	log.Println("got port", port)

	if !server.allowJdwpPort(port) {
		log.Println("not allowed port", port)
		return false
	}

	token := req.Header.Get(common.HeaderToken)
	if common.VerifyToken(server.key, token) {
		return true
	} else {
		log.Println("bad token", token)
		return false
	}
}

func (server *WSServer) allowJdwpPort(port int) bool {
	for _, jdwpPort := range server.jdwpPorts {
		if port == jdwpPort {
			server.jdwpPort = port
			return true
		}
	}
	return false
}

func send(conn *websocket.Conn, clientConn *net.TCPConn) {
	defer cleanConn(conn, clientConn)

	var buffer []byte
	var err error
	for {
		if clientConn == nil || conn == nil {
			return
		}

		_, buffer, err = conn.ReadMessage()
		if err != nil {
			log.Printf("read ws failed: %v\n", err.Error())
			return
		}

		_, err = clientConn.Write(buffer)
		if err != nil {
			log.Printf("write tcp failed: %v\n", err.Error())
			return
		}
	}
}

func receive(conn *websocket.Conn, clientConn *net.TCPConn) {
	defer cleanConn(conn, clientConn)

	buffer := make([]byte, 256)
	var read = 0
	var err error
	for {
		if clientConn == nil || conn == nil {
			return
		}

		read, err = clientConn.Read(buffer)
		if err != nil {
			log.Printf("read tcp failed: %v\n", err.Error())
			return
		}

		err = conn.WriteMessage(websocket.BinaryMessage, buffer[:read])
		if err != nil {
			log.Printf("write ws failed: %v\n", err.Error())
			return
		}
	}
}

func cleanConn(conn *websocket.Conn, clientConn *net.TCPConn) {
	log.Println("connection was disconnected")

	if err := recover(); err != nil {
		log.Println("recover from", err)
	}

	if conn != nil {
		conn.Close()
	}
	if clientConn != nil {
		clientConn.Close()
	}
}
<commit_msg>Updated to save private key for reuse<commit_after>package server

import (
	"crypto/rand"
	"crypto/rsa"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"strconv"
	"strings"

	"github.com/gorilla/websocket"
	"github.com/leonlee/jrdwp/common"
)

type WSServer struct {
	port      int
	path      string
	origin    string
	client    *TCPClient
	key       *rsa.PrivateKey
	jdwpPorts []int
	jdwpPort  int
}

func NewWSServer(path string, origin string, port int, jdwpPorts []int, client *TCPClient) *WSServer {
	if !strings.HasPrefix(path, "/") {
		path = "/" + path
	}

	return &WSServer{
		port:      port,
		path:      path,
		origin:    origin,
		client:    client,
		jdwpPorts: jdwpPorts,
	}
}

func (server *WSServer) Start() {
	if err := initKey(server); err != nil {
		log.Fatalln("can't start ws server", err.Error())
	}

	addr := fmt.Sprintf(":%d", server.port)
	log.Printf("starting ws server: %s\n", addr)
	log.Printf("ws path: %s\n", server.path)
	server.listen(addr)

	defer func() {
		if err := recover(); err != nil {
			log.Println("recovering from:", err)
			server.listen(addr)
		}
	}()
}

func initKey(server *WSServer) error {

	if _, err := os.Stat(common.PrivateKeyPath()); os.IsNotExist(err) {
		privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
		if err != nil {
			log.Println("can't generate key", err.Error())
			return err
		}

		privateKeyBytes := pem.EncodeToMemory(
		    &pem.Block{
			Type: "RSA PRIVATE KEY",
			Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
		    },
		)

		err = ioutil.WriteFile(common.PrivateKeyPath(), privateKeyBytes, 0655)
		if err != nil {
			log.Println("can't write private key", common.PrivateKeyPath(), err.Error())
			return err
		}

	}

	privateKeyFileBytes, err := ioutil.ReadFile(common.PrivateKeyPath())
        block, _ := pem.Decode([]byte(privateKeyFileBytes))
	privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
	if err != nil {
		log.Println("can't read private key", common.PrivateKeyPath(), err.Error())
		return err
	}
	server.key = privateKey

	publicKeyBytes, err := common.PublicKeyToBytes(&privateKey.PublicKey)
	if err != nil {
		log.Println("can't convert public key to bytes", err.Error())
		return err
	}
	log.Printf("generated public key:\n\n%s\n", publicKeyBytes)

	if _, err := os.Stat(common.PublicKeyPath()); os.IsNotExist(err) {
		err = ioutil.WriteFile(common.PublicKeyPath(), publicKeyBytes, 0655)
		if err != nil {
			log.Println("can't write public key", common.PublicKeyPath(), err.Error())
			return err
		}
	}
	return nil
}

func (server *WSServer) listen(addr string) {
	http.HandleFunc(server.path, server.onMessage)
	if err := http.ListenAndServe(addr, nil); err != nil {
		log.Fatal(err)
	}
}

func (server *WSServer) onMessage(w http.ResponseWriter, request *http.Request) {
	var conn *websocket.Conn
	var clientConn *net.TCPConn

	defer closeOnFail(conn, clientConn)

	var upgrader = websocket.Upgrader{
		CheckOrigin: func(req *http.Request) bool {
			return server.verifyRequest(req)
		},
	}

	conn, err := upgrader.Upgrade(w, request, nil)
	if err != nil {
		log.Panicln("upgrade:", err)
	}

	clientConn, err = server.client.Connect(server.jdwpPort)
	if err != nil {
		log.Panicln("can't connect to jvm:", err)
	}

	common.InitTCPConn(clientConn)

	go send(conn, clientConn)
	go receive(conn, clientConn)
}

func closeOnFail(conn *websocket.Conn, clientConn *net.TCPConn) {
	if err := recover(); err != nil {
		log.Println("recover from", err)
		if conn != nil {
			conn.Close()
		}
		if clientConn != nil {
			clientConn.Close()
		}
	}
}

func (server *WSServer) verifyRequest(req *http.Request) bool {
	port, err := strconv.Atoi(req.Header.Get(common.HeaderPort))
	if err != nil {
		log.Println("bad port", err.Error())
		return false
	}
	log.Println("got port", port)

	if !server.allowJdwpPort(port) {
		log.Println("not allowed port", port)
		return false
	}

	token := req.Header.Get(common.HeaderToken)
	if common.VerifyToken(server.key, token) {
		return true
	} else {
		log.Println("bad token", token)
		return false
	}
}

func (server *WSServer) allowJdwpPort(port int) bool {
	for _, jdwpPort := range server.jdwpPorts {
		if port == jdwpPort {
			server.jdwpPort = port
			return true
		}
	}
	return false
}

func send(conn *websocket.Conn, clientConn *net.TCPConn) {
	defer cleanConn(conn, clientConn)

	var buffer []byte
	var err error
	for {
		if clientConn == nil || conn == nil {
			return
		}

		_, buffer, err = conn.ReadMessage()
		if err != nil {
			log.Printf("read ws failed: %v\n", err.Error())
			return
		}

		_, err = clientConn.Write(buffer)
		if err != nil {
			log.Printf("write tcp failed: %v\n", err.Error())
			return
		}
	}
}

func receive(conn *websocket.Conn, clientConn *net.TCPConn) {
	defer cleanConn(conn, clientConn)

	buffer := make([]byte, 256)
	var read = 0
	var err error
	for {
		if clientConn == nil || conn == nil {
			return
		}

		read, err = clientConn.Read(buffer)
		if err != nil {
			log.Printf("read tcp failed: %v\n", err.Error())
			return
		}

		err = conn.WriteMessage(websocket.BinaryMessage, buffer[:read])
		if err != nil {
			log.Printf("write ws failed: %v\n", err.Error())
			return
		}
	}
}

func cleanConn(conn *websocket.Conn, clientConn *net.TCPConn) {
	log.Println("connection was disconnected")

	if err := recover(); err != nil {
		log.Println("recover from", err)
	}

	if conn != nil {
		conn.Close()
	}
	if clientConn != nil {
		clientConn.Close()
	}
}
<|endoftext|>


<commit_before>package gf

// 框架信息
const VERSION  = "0.92 beta"
const AUTHORS  = "john<john@johng.cn>"

<commit_msg>0.93beta<commit_after>package gf

// 框架信息
const VERSION  = "0.93 beta"
const AUTHORS  = "john<john@johng.cn>"

<|endoftext|>


<commit_before>// Copyright 2018 The Moov Authors
// Use of this source code is governed by an Apache License
// license that can be found in the LICENSE file.

package ach

// Version Number
const Version = "v0.6.0-rc3"
<commit_msg>release v0.6.0-rc4<commit_after>// Copyright 2018 The Moov Authors
// Use of this source code is governed by an Apache License
// license that can be found in the LICENSE file.

package ach

// Version Number
const Version = "v0.6.0-rc4"
<|endoftext|>


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

package main

const Version = "0.5.1"
<commit_msg>version: bump to v0.5.1+git<commit_after>// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

const Version = "0.5.1+git"
<|endoftext|>


<commit_before>package main

import "fmt"

type version struct {
	Major, Minor, Patch int
	Label               string
	Nick                string
}

var ver = version{
	Major: 0,
	Minor: 1,
	Patch: 2,
	Label: "alpha"}

// CommitHash may be set on the build command line:
// go build -ldflags "-X main.CommitHash=`git rev-parse --short HEAD`"
var CommitHash string

const appName string = "dcrdata"

func (v *version) String() string {
	var hashStr string
	if CommitHash != "" {
		hashStr = "+" + CommitHash
	}
	if v.Label != "" {
		return fmt.Sprintf("%d.%d.%d-%s%s",
			v.Major, v.Minor, v.Patch, v.Label, hashStr)
	}
	return fmt.Sprintf("%d.%d.%d%s",
		v.Major, v.Minor, v.Patch, hashStr)
}
<commit_msg>Bump version to 0.1.3.<commit_after>package main

import "fmt"

type version struct {
	Major, Minor, Patch int
	Label               string
	Nick                string
}

var ver = version{
	Major: 0,
	Minor: 1,
	Patch: 3,
	Label: "alpha"}

// CommitHash may be set on the build command line:
// go build -ldflags "-X main.CommitHash=`git rev-parse --short HEAD`"
var CommitHash string

const appName string = "dcrdata"

func (v *version) String() string {
	var hashStr string
	if CommitHash != "" {
		hashStr = "+" + CommitHash
	}
	if v.Label != "" {
		return fmt.Sprintf("%d.%d.%d-%s%s",
			v.Major, v.Minor, v.Patch, v.Label, hashStr)
	}
	return fmt.Sprintf("%d.%d.%d%s",
		v.Major, v.Minor, v.Patch, hashStr)
}
<|endoftext|>


<commit_before>package dns

import "fmt"

// Version is current version of this library.
var Version = V{1, 1, 9}

// V holds the version of this library.
type V struct {
	Major, Minor, Patch int
}

func (v V) String() string {
	return fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch)
}
<commit_msg>Release 1.1.10<commit_after>package dns

import "fmt"

// Version is current version of this library.
var Version = V{1, 1, 10}

// V holds the version of this library.
type V struct {
	Major, Minor, Patch int
}

func (v V) String() string {
	return fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch)
}
<|endoftext|>


<commit_before>package main

var version = "0.2.3"
<commit_msg>bump version to 0.3.1<commit_after>package main

var version = "0.3.1"
<|endoftext|>


<commit_before>package csvutil

import "github.com/coreos/go-semver/semver"

// Version of csvutil.
var Version = semver.Version{
	Major: 0,
	Minor: 19,
	Patch: 0,
}
<commit_msg>Version up to v0.20.0 [ci skip]<commit_after>package csvutil

import "github.com/coreos/go-semver/semver"

// Version of csvutil.
var Version = semver.Version{
	Major: 0,
	Minor: 20,
	Patch: 0,
}
<|endoftext|>


<commit_before>package main

// The git commit that was compiled. This will be filled in by the compiler.
var (
	GitCommit   string
	GitDescribe string
)

// The main version number that is being run at the moment.
const Version = "0.6.0"

// A pre-release marker for the version. If this is "" (empty string)
// then it means that it is a final release. Otherwise, this is a pre-release
// such as "dev" (in development), "beta", "rc1", etc.
const VersionPrerelease = ""
<commit_msg>Puts the version file into the "next version" state.<commit_after>package main

// The git commit that was compiled. This will be filled in by the compiler.
var (
	GitCommit   string
	GitDescribe string
)

// The main version number that is being run at the moment.
const Version = "0.7.0"

// A pre-release marker for the version. If this is "" (empty string)
// then it means that it is a final release. Otherwise, this is a pre-release
// such as "dev" (in development), "beta", "rc1", etc.
const VersionPrerelease = "dev"
<|endoftext|>


<commit_before>package main

const VERSION = "2.4-beta"
<commit_msg>Release v2.4<commit_after>package main

const VERSION = "2.4"
<|endoftext|>


<commit_before>// (c) Copyright IBM Corp. 2021
// (c) Copyright Instana Inc. 2021

package instana

// Version is the version of Instana sensor
const Version = "1.32.1"
<commit_msg>Bump version to v1.33.0<commit_after>// (c) Copyright IBM Corp. 2021
// (c) Copyright Instana Inc. 2021

package instana

// Version is the version of Instana sensor
const Version = "1.33.0"
<|endoftext|>


<commit_before>package main

const VERSION = "0.7.0-alpha2"
<commit_msg>:+1: Bump up the version to 0.7.0-alpha3<commit_after>package main

const VERSION = "0.7.0-alpha3"
<|endoftext|>


<commit_before>// Package fpm implements the Pipe interface providing FPM bindings.
package fpm

import (
	"errors"
	"fmt"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/apex/log"
	"github.com/goreleaser/goreleaser/context"
	"golang.org/x/sync/errgroup"
)

// ErrNoFPM is shown when fpm cannot be found in $PATH
var ErrNoFPM = errors.New("fpm not present in $PATH")

// Pipe for fpm packaging
type Pipe struct{}

// Description of the pipe
func (Pipe) Description() string {
	return "Creating Linux packages with fpm"
}

// Run the pipe
func (Pipe) Run(ctx *context.Context) error {
	if len(ctx.Config.FPM.Formats) == 0 {
		log.Info("no output formats configured, skipping")
		return nil
	}
	_, err := exec.LookPath("fpm")
	if err != nil {
		return ErrNoFPM
	}

	var g errgroup.Group
	for _, format := range ctx.Config.FPM.Formats {
		for platform, groups := range ctx.Binaries {
			if !strings.Contains(platform, "linux") {
				log.WithField("platform", platform).Debug("skipped non-linux builds for fpm")
				continue
			}
			format := format
			arch := archFor(platform)
			for folder, binaries := range groups {
				g.Go(func() error {
					return create(ctx, format, folder, arch, binaries)
				})
			}
		}
	}
	return g.Wait()
}

func archFor(key string) string {
	if strings.Contains(key, "386") {
		return "i386"
	}
	return "x86_64"
}

func create(ctx *context.Context, format, folder, arch string, binaries []context.Binary) error {
	var path = filepath.Join(ctx.Config.Dist, folder)
	var file = path + "." + format
	log.WithField("file", file).Info("creating fpm archive")

	var options = []string{
		"--input-type", "dir",
		"--output-type", format,
		"--name", ctx.Config.ProjectName,
		"--version", ctx.Version,
		"--architecture", arch,
		// "--chdir", path,
		"--package", file,
		"--force",
	}

	if ctx.Config.FPM.Vendor != "" {
		options = append(options, "--vendor", ctx.Config.FPM.Vendor)
	}
	if ctx.Config.FPM.Homepage != "" {
		options = append(options, "--url", ctx.Config.FPM.Homepage)
	}
	if ctx.Config.FPM.Maintainer != "" {
		options = append(options, "--maintainer", ctx.Config.FPM.Maintainer)
	}
	if ctx.Config.FPM.Description != "" {
		options = append(options, "--description", ctx.Config.FPM.Description)
	}
	if ctx.Config.FPM.License != "" {
		options = append(options, "--license", ctx.Config.FPM.License)
	}
	for _, dep := range ctx.Config.FPM.Dependencies {
		options = append(options, "--depends", dep)
	}
	for _, conflict := range ctx.Config.FPM.Conflicts {
		options = append(options, "--conflicts", conflict)
	}

	for _, binary := range binaries {
		// This basically tells fpm to put the binary in the /usr/local/bin
		// binary=/usr/local/bin/binary
		log.WithField("path", binary.Path).
			WithField("name", binary.Name).
			Info("passed binary to fpm")
		options = append(options, fmt.Sprintf(
			"%s=%s",
			binary.Path,
			filepath.Join("/usr/local/bin", binary.Name),
		))
	}

	for src, dest := range ctx.Config.FPM.Files {
		log.WithField("src", src).
			WithField("dest", dest).
			Info("passed extra file to fpm")
		options = append(options, fmt.Sprintf(
			"%s=%s",
			src,
			dest,
		))
	}

	if out, err := exec.Command("fpm", options...).CombinedOutput(); err != nil {
		return errors.New(string(out))
	}
	ctx.AddArtifact(file)
	return nil
}
<commit_msg>fix RPM cross-compiling (OSX => amazonlinux/CentOS)<commit_after>// Package fpm implements the Pipe interface providing FPM bindings.
package fpm

import (
	"errors"
	"fmt"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/apex/log"
	"github.com/goreleaser/goreleaser/context"
	"golang.org/x/sync/errgroup"
)

// ErrNoFPM is shown when fpm cannot be found in $PATH
var ErrNoFPM = errors.New("fpm not present in $PATH")

// Pipe for fpm packaging
type Pipe struct{}

// Description of the pipe
func (Pipe) Description() string {
	return "Creating Linux packages with fpm"
}

// Run the pipe
func (Pipe) Run(ctx *context.Context) error {
	if len(ctx.Config.FPM.Formats) == 0 {
		log.Info("no output formats configured, skipping")
		return nil
	}
	_, err := exec.LookPath("fpm")
	if err != nil {
		return ErrNoFPM
	}

	var g errgroup.Group
	for _, format := range ctx.Config.FPM.Formats {
		for platform, groups := range ctx.Binaries {
			if !strings.Contains(platform, "linux") {
				log.WithField("platform", platform).Debug("skipped non-linux builds for fpm")
				continue
			}
			format := format
			arch := archFor(platform)
			for folder, binaries := range groups {
				g.Go(func() error {
					return create(ctx, format, folder, arch, platform, binaries)
				})
			}
		}
	}
	return g.Wait()
}

func archFor(key string) string {
	if strings.Contains(key, "386") {
		return "i386"
	}
	return "x86_64"
}

func create(ctx *context.Context, format, folder, arch, platform string, binaries []context.Binary) error {
	var path = filepath.Join(ctx.Config.Dist, folder)
	var file = path + "." + format
	log.WithField("file", file).Info("creating fpm archive")

	var options = []string{
		"--input-type", "dir",
		"--output-type", format,
		"--name", ctx.Config.ProjectName,
		"--version", ctx.Version,
		"--architecture", arch,
		// "--chdir", path,
		"--package", file,
		"--force",
	}

	if ctx.Config.FPM.Vendor != "" {
		options = append(options, "--vendor", ctx.Config.FPM.Vendor)
	}
	if ctx.Config.FPM.Homepage != "" {
		options = append(options, "--url", ctx.Config.FPM.Homepage)
	}
	if ctx.Config.FPM.Maintainer != "" {
		options = append(options, "--maintainer", ctx.Config.FPM.Maintainer)
	}
	if ctx.Config.FPM.Description != "" {
		options = append(options, "--description", ctx.Config.FPM.Description)
	}
	if ctx.Config.FPM.License != "" {
		options = append(options, "--license", ctx.Config.FPM.License)
	}
	for _, dep := range ctx.Config.FPM.Dependencies {
		options = append(options, "--depends", dep)
	}
	for _, conflict := range ctx.Config.FPM.Conflicts {
		options = append(options, "--conflicts", conflict)
	}

	// FPM requires --rpm-os=linux if your rpm target is linux
	if format == "rpm" && strings.Contains(platform, "linux") {
		options = append(options, "--rpm-os", "linux")
	}

	for _, binary := range binaries {
		// This basically tells fpm to put the binary in the /usr/local/bin
		// binary=/usr/local/bin/binary
		log.WithField("path", binary.Path).
			WithField("name", binary.Name).
			Info("passed binary to fpm")
		options = append(options, fmt.Sprintf(
			"%s=%s",
			binary.Path,
			filepath.Join("/usr/local/bin", binary.Name),
		))
	}

	for src, dest := range ctx.Config.FPM.Files {
		log.WithField("src", src).
			WithField("dest", dest).
			Info("passed extra file to fpm")
		options = append(options, fmt.Sprintf(
			"%s=%s",
			src,
			dest,
		))
	}

	if out, err := exec.Command("fpm", options...).CombinedOutput(); err != nil {
		return errors.New(string(out))
	}
	ctx.AddArtifact(file)
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2021 Google LLC
//
// 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 ioutils contains utilities for reading/writing files with the beam pipelines.
package ioutils

import (
	"bufio"
	"bytes"
	"context"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net/http"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"reflect"
	"strings"

	"github.com/apache/beam/sdks/go/pkg/beam"
	"github.com/apache/beam/sdks/go/pkg/beam/io/textio"
	"cloud.google.com/go/secretmanager/apiv1"
	"cloud.google.com/go/storage"
	secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
	"github.com/ugorji/go/codec"
)

func init() {
	beam.RegisterType(reflect.TypeOf((*addShardKeyFn)(nil)).Elem())
	beam.RegisterType(reflect.TypeOf((*getShardFn)(nil)).Elem())
}

type addShardKeyFn struct {
	TotalShards int64
}

func (fn *addShardKeyFn) ProcessElement(line string, emit func(int64, string)) {
	emit(rand.Int63n(fn.TotalShards), line)
}

type getShardFn struct {
	Shard int64
}

func (fn *getShardFn) ProcessElement(key int64, line string, emit func(string)) {
	if fn.Shard == key {
		emit(line)
	}
}

// WriteNShardedFiles writes the text files in shards.
func WriteNShardedFiles(s beam.Scope, outputName string, n int64, lines beam.PCollection) {
	s = s.Scope("WriteNShardedFiles")

	if n == 1 {
		textio.Write(s, outputName, lines)
		return
	}
	keyed := beam.ParDo(s, &addShardKeyFn{TotalShards: n}, lines)
	for i := int64(0); i < n; i++ {
		shard := beam.ParDo(s, &getShardFn{Shard: i}, keyed)
		textio.Write(s, AddStrInPath(outputName, fmt.Sprintf("-%d-%d", i+1, n)), shard)
	}
}

// AddStrInPath adds a string in the file name before the file extension.
//
// For example: addStringInPath("/foo/x.bar", "_baz") = "/foo/x_baz.bar"
func AddStrInPath(path, str string) string {
	ext := filepath.Ext(path)
	return path[:len(path)-len(ext)] + str + ext
}

// ParseGCSPath gets the bucket and object names from the input filename.
func ParseGCSPath(filename string) (bucket, object string, err error) {
	parsed, err := url.Parse(filename)
	if err != nil {
		return
	}
	if parsed.Scheme != "gs" {
		err = fmt.Errorf("object %q must have 'gs' scheme", filename)
		return
	}
	if parsed.Host == "" {
		err = fmt.Errorf("object %q must have bucket", filename)
		return
	}

	bucket = parsed.Host
	if parsed.Path != "" {
		object = parsed.Path[1:]
	}
	return
}

// ReadLines reads the input file line by line and returns the content as a slice of strings.
//
// The file can be stored locally or in the GCS.
func ReadLines(ctx context.Context, filename string) ([]string, error) {
	var scanner *bufio.Scanner
	if strings.HasPrefix(filename, "gs://") {
		client, err := storage.NewClient(ctx)
		if err != nil {
			return nil, err
		}

		bucket, object, err := ParseGCSPath(filename)
		if err != nil {
			return nil, err
		}
		reader, err := client.Bucket(bucket).Object(object).NewReader(ctx)
		if err != nil {
			return nil, err
		}
		defer reader.Close()
		scanner = bufio.NewScanner(reader)
	} else {
		fs, err := os.Open(filename)
		if err != nil {
			return nil, err
		}
		defer fs.Close()
		scanner = bufio.NewScanner(fs)
	}

	var result []string
	for scanner.Scan() {
		result = append(result, scanner.Text())
	}
	return result, scanner.Err()
}

// WriteLines writes the input string slice to the output file, one string per line.
//
// The file can be stored locally or in the GCS.
func WriteLines(ctx context.Context, lines []string, filename string) error {
	var buf *bufio.Writer
	if strings.HasPrefix(filename, "gs://") {
		client, err := storage.NewClient(ctx)
		if err != nil {
			return err
		}

		bucket, object, err := ParseGCSPath(filename)
		if err != nil {
			return err
		}
		cw := client.Bucket(bucket).Object(object).NewWriter(ctx)
		defer cw.Close()
		buf = bufio.NewWriter(cw)
	} else {
		fs, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
		if err != nil {
			return err
		}
		defer fs.Close()
		buf = bufio.NewWriter(fs)
	}

	for _, line := range lines {
		if _, err := buf.WriteString(line + "\n"); err != nil {
			return err
		}
	}
	return buf.Flush()
}

// TODO: Add a unit test for writing and reading files in GCS buckets
func writeGCSObject(ctx context.Context, data []byte, filename string) error {
	client, err := storage.NewClient(ctx)
	if err != nil {
		return err
	}

	bucket, object, err := ParseGCSPath(filename)
	if err != nil {
		return err
	}
	writer := client.Bucket(bucket).Object(object).NewWriter(ctx)
	defer writer.Close()
	_, err = writer.Write(data)
	return err
}

func readGCSObject(ctx context.Context, filename string) ([]byte, error) {
	client, err := storage.NewClient(ctx)
	if err != nil {
		return nil, err
	}

	bucket, object, err := ParseGCSPath(filename)
	if err != nil {
		return nil, err
	}
	reader, err := client.Bucket(bucket).Object(object).NewReader(ctx)
	if err != nil {
		return nil, err
	}
	defer reader.Close()
	return ioutil.ReadAll(reader)
}

// WriteBytes writes bytes into a local or GCS file.
func WriteBytes(ctx context.Context, data []byte, filename string) error {
	if strings.HasPrefix(filename, "gs://") {
		return writeGCSObject(ctx, data, filename)
	}
	return ioutil.WriteFile(filename, data, os.ModePerm)
}

func readBytesFromURL(url string) ([]byte, error) {
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	content := make([]byte, resp.ContentLength)
	if _, err := resp.Body.Read(content); err != nil {
		return nil, err
	}
	return content, nil
}

// ReadBytes reads bytes from a file stored locally, in GCS or served at an URL.
func ReadBytes(ctx context.Context, filename string) ([]byte, error) {
	u, err := url.Parse(filename)
	if err == nil {
		if u.Scheme == "gs" {
			return readGCSObject(ctx, filename)
		} else if u.Scheme == "http" || u.Scheme == "https" {
			return readBytesFromURL(filename)
		}
	}
	return ioutil.ReadFile(filename)
}

// MarshalCBOR serializes the input data in CBOR format.
func MarshalCBOR(v interface{}) ([]byte, error) {
	encBuf := new(bytes.Buffer)
	enc := codec.NewEncoder(encBuf, &codec.CborHandle{})
	if err := enc.Encode(v); err != nil {
		return nil, err
	}
	return encBuf.Bytes(), nil
}

// UnmarshalCBOR parses the bytes in CBOR format.
func UnmarshalCBOR(b []byte, v interface{}) error {
	decBuf := bytes.NewBuffer(b)
	dec := codec.NewDecoder(decBuf, &codec.CborHandle{})
	return dec.Decode(v)
}

// JoinPath joins the directory and the filename to get the full path of a file.
func JoinPath(directory, filename string) string {
	// Function path.Join does not work for GCS files, for example:
	// path.Join("gs://foo", "bar") returns "gs:/foo/bar"
	if strings.HasPrefix(directory, "gs://") {
		if strings.HasSuffix(directory, "/") {
			return fmt.Sprintf("%s%s", directory, filename)
		}
		return fmt.Sprintf("%s/%s", directory, filename)
	}
	return path.Join(directory, filename)
}

// SaveSecret saves the input payload with Google Cloud Secret Manager.
func SaveSecret(ctx context.Context, payload []byte, projectID, secretID string) (string, error) {
	client, err := secretmanager.NewClient(ctx)
	if err != nil {
		return "", err
	}
	defer client.Close()

	createSecretReq := &secretmanagerpb.CreateSecretRequest{
		Parent:   fmt.Sprintf("projects/%s", projectID),
		SecretId: secretID,
		Secret: &secretmanagerpb.Secret{
			Replication: &secretmanagerpb.Replication{
				Replication: &secretmanagerpb.Replication_Automatic_{
					Automatic: &secretmanagerpb.Replication_Automatic{},
				},
			},
		},
	}

	secret, err := client.CreateSecret(ctx, createSecretReq)
	if err != nil {
		return "", err
	}

	addSecretVersionReq := &secretmanagerpb.AddSecretVersionRequest{
		Parent: secret.Name,
		Payload: &secretmanagerpb.SecretPayload{
			Data: payload,
		},
	}

	version, err := client.AddSecretVersion(ctx, addSecretVersionReq)
	if err != nil {
		return "", err
	}
	return version.Name, nil
}

// ReadSecret reads a secret payload from Google Cloud Secret Manager.
func ReadSecret(ctx context.Context, name string) ([]byte, error) {
	client, err := secretmanager.NewClient(ctx)
	if err != nil {
		return nil, err
	}
	defer client.Close()

	accessRequest := &secretmanagerpb.AccessSecretVersionRequest{
		Name: name,
	}

	result, err := client.AccessSecretVersion(ctx, accessRequest)
	if err != nil {
		return nil, err
	}
	return result.Payload.Data, nil
}
<commit_msg>Internal change<commit_after>// Copyright 2021 Google LLC
//
// 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 ioutils contains utilities for reading/writing files with the beam pipelines.
package ioutils

import (
	"bufio"
	"bytes"
	"context"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net/http"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"reflect"
	"strings"

	"github.com/apache/beam/sdks/go/pkg/beam"
	"github.com/apache/beam/sdks/go/pkg/beam/io/textio"
	"cloud.google.com/go/secretmanager/apiv1"
	"cloud.google.com/go/storage"
	secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
	"github.com/ugorji/go/codec"
)

func init() {
	beam.RegisterType(reflect.TypeOf((*addShardKeyFn)(nil)).Elem())
	beam.RegisterType(reflect.TypeOf((*getShardFn)(nil)).Elem())
}

type addShardKeyFn struct {
	TotalShards int64
}

func (fn *addShardKeyFn) ProcessElement(line string, emit func(int64, string)) {
	emit(rand.Int63n(fn.TotalShards), line)
}

type getShardFn struct {
	Shard int64
}

func (fn *getShardFn) ProcessElement(key int64, line string, emit func(string)) {
	if fn.Shard == key {
		emit(line)
	}
}

// WriteNShardedFiles writes the text files in shards.
func WriteNShardedFiles(s beam.Scope, outputName string, n int64, lines beam.PCollection) {
	s = s.Scope("WriteNShardedFiles")

	if n == 1 {
		textio.Write(s, outputName, lines)
		return
	}
	keyed := beam.ParDo(s, &addShardKeyFn{TotalShards: n}, lines)
	for i := int64(0); i < n; i++ {
		shard := beam.ParDo(s, &getShardFn{Shard: i}, keyed)
		textio.Write(s, AddStrInPath(outputName, fmt.Sprintf("-%d-%d", i+1, n)), shard)
	}
}

// AddStrInPath adds a string in the file name before the file extension.
//
// For example: addStringInPath("/foo/x.bar", "_baz") = "/foo/x_baz.bar"
func AddStrInPath(path, str string) string {
	ext := filepath.Ext(path)
	return path[:len(path)-len(ext)] + str + ext
}

// ParseGCSPath gets the bucket and object names from the input filename.
func ParseGCSPath(filename string) (bucket, object string, err error) {
	parsed, err := url.Parse(filename)
	if err != nil {
		return
	}
	if parsed.Scheme != "gs" {
		err = fmt.Errorf("object %q must have 'gs' scheme", filename)
		return
	}
	if parsed.Host == "" {
		err = fmt.Errorf("object %q must have bucket", filename)
		return
	}

	bucket = parsed.Host
	if parsed.Path != "" {
		object = parsed.Path[1:]
	}
	return
}

// ReadLines reads the input file line by line and returns the content as a slice of strings.
//
// The file can be stored locally or in the GCS.
func ReadLines(ctx context.Context, filename string) ([]string, error) {
	var scanner *bufio.Scanner
	if strings.HasPrefix(filename, "gs://") {
		client, err := storage.NewClient(ctx)
		if err != nil {
			return nil, err
		}

		bucket, object, err := ParseGCSPath(filename)
		if err != nil {
			return nil, err
		}
		reader, err := client.Bucket(bucket).Object(object).NewReader(ctx)
		if err != nil {
			return nil, err
		}
		defer reader.Close()
		scanner = bufio.NewScanner(reader)
	} else {
		fs, err := os.Open(filename)
		if err != nil {
			return nil, err
		}
		defer fs.Close()
		scanner = bufio.NewScanner(fs)
	}

	var result []string
	for scanner.Scan() {
		result = append(result, scanner.Text())
	}
	return result, scanner.Err()
}

// WriteLines writes the input string slice to the output file, one string per line.
//
// The file can be stored locally or in the GCS.
func WriteLines(ctx context.Context, lines []string, filename string) error {
	var buf *bufio.Writer
	if strings.HasPrefix(filename, "gs://") {
		client, err := storage.NewClient(ctx)
		if err != nil {
			return err
		}

		bucket, object, err := ParseGCSPath(filename)
		if err != nil {
			return err
		}
		cw := client.Bucket(bucket).Object(object).NewWriter(ctx)
		defer cw.Close()
		buf = bufio.NewWriter(cw)
	} else {
		fs, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
		if err != nil {
			return err
		}
		defer fs.Close()
		buf = bufio.NewWriter(fs)
	}

	for _, line := range lines {
		if _, err := buf.WriteString(line + "\n"); err != nil {
			return err
		}
	}
	return buf.Flush()
}

// TODO: Add a unit test for writing and reading files in GCS buckets
func writeGCSObject(ctx context.Context, data []byte, filename string) error {
	client, err := storage.NewClient(ctx)
	if err != nil {
		return err
	}

	bucket, object, err := ParseGCSPath(filename)
	if err != nil {
		return err
	}
	writer := client.Bucket(bucket).Object(object).NewWriter(ctx)
	if _, err := writer.Write(data); err != nil {
		return err
	}

	return writer.Close()
}

func readGCSObject(ctx context.Context, filename string) ([]byte, error) {
	client, err := storage.NewClient(ctx)
	if err != nil {
		return nil, err
	}

	bucket, object, err := ParseGCSPath(filename)
	if err != nil {
		return nil, err
	}
	reader, err := client.Bucket(bucket).Object(object).NewReader(ctx)
	if err != nil {
		return nil, err
	}
	defer reader.Close()
	return ioutil.ReadAll(reader)
}

// WriteBytes writes bytes into a local or GCS file.
func WriteBytes(ctx context.Context, data []byte, filename string) error {
	if strings.HasPrefix(filename, "gs://") {
		return writeGCSObject(ctx, data, filename)
	}
	return ioutil.WriteFile(filename, data, os.ModePerm)
}

func readBytesFromURL(url string) ([]byte, error) {
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	content := make([]byte, resp.ContentLength)
	if _, err := resp.Body.Read(content); err != nil {
		return nil, err
	}
	return content, nil
}

// ReadBytes reads bytes from a file stored locally, in GCS or served at an URL.
func ReadBytes(ctx context.Context, filename string) ([]byte, error) {
	u, err := url.Parse(filename)
	if err == nil {
		if u.Scheme == "gs" {
			return readGCSObject(ctx, filename)
		} else if u.Scheme == "http" || u.Scheme == "https" {
			return readBytesFromURL(filename)
		}
	}
	return ioutil.ReadFile(filename)
}

// MarshalCBOR serializes the input data in CBOR format.
func MarshalCBOR(v interface{}) ([]byte, error) {
	encBuf := new(bytes.Buffer)
	enc := codec.NewEncoder(encBuf, &codec.CborHandle{})
	if err := enc.Encode(v); err != nil {
		return nil, err
	}
	return encBuf.Bytes(), nil
}

// UnmarshalCBOR parses the bytes in CBOR format.
func UnmarshalCBOR(b []byte, v interface{}) error {
	decBuf := bytes.NewBuffer(b)
	dec := codec.NewDecoder(decBuf, &codec.CborHandle{})
	return dec.Decode(v)
}

// JoinPath joins the directory and the filename to get the full path of a file.
func JoinPath(directory, filename string) string {
	// Function path.Join does not work for GCS files, for example:
	// path.Join("gs://foo", "bar") returns "gs:/foo/bar"
	if strings.HasPrefix(directory, "gs://") {
		if strings.HasSuffix(directory, "/") {
			return fmt.Sprintf("%s%s", directory, filename)
		}
		return fmt.Sprintf("%s/%s", directory, filename)
	}
	return path.Join(directory, filename)
}

// SaveSecret saves the input payload with Google Cloud Secret Manager.
func SaveSecret(ctx context.Context, payload []byte, projectID, secretID string) (string, error) {
	client, err := secretmanager.NewClient(ctx)
	if err != nil {
		return "", err
	}
	defer client.Close()

	createSecretReq := &secretmanagerpb.CreateSecretRequest{
		Parent:   fmt.Sprintf("projects/%s", projectID),
		SecretId: secretID,
		Secret: &secretmanagerpb.Secret{
			Replication: &secretmanagerpb.Replication{
				Replication: &secretmanagerpb.Replication_Automatic_{
					Automatic: &secretmanagerpb.Replication_Automatic{},
				},
			},
		},
	}

	secret, err := client.CreateSecret(ctx, createSecretReq)
	if err != nil {
		return "", err
	}

	addSecretVersionReq := &secretmanagerpb.AddSecretVersionRequest{
		Parent: secret.Name,
		Payload: &secretmanagerpb.SecretPayload{
			Data: payload,
		},
	}

	version, err := client.AddSecretVersion(ctx, addSecretVersionReq)
	if err != nil {
		return "", err
	}
	return version.Name, nil
}

// ReadSecret reads a secret payload from Google Cloud Secret Manager.
func ReadSecret(ctx context.Context, name string) ([]byte, error) {
	client, err := secretmanager.NewClient(ctx)
	if err != nil {
		return nil, err
	}
	defer client.Close()

	accessRequest := &secretmanagerpb.AccessSecretVersionRequest{
		Name: name,
	}

	result, err := client.AccessSecretVersion(ctx, accessRequest)
	if err != nil {
		return nil, err
	}
	return result.Payload.Data, nil
}
<|endoftext|>


<commit_before>package main

import (
	"io/ioutil"
	"log"
	"net/http"
	"os"

	"github.com/gorilla/mux"
)

func main() {
	r := mux.NewRouter()

	file := r.Path("/files/{id}").Subrouter()
	file.Methods("GET").HandlerFunc(GetHandler)
	file.Methods("POST").HandlerFunc(CreateHandler)
	file.Methods("PUT").HandlerFunc(UpdateHandler)
	file.Methods("DELETE").HandlerFunc(DeleteHandler)

	http.ListenAndServe(":"+os.Getenv("PORT"), r)
}

func CreateHandler(rw http.ResponseWriter, r *http.Request) {
	id := mux.Vars(r)["id"]
	path := "/Users/sebastian/" + id
	body, _ := ioutil.ReadAll(r.Body)

	// Check if the file exists
	if _, err := os.Stat(path); err == nil {
		rw.WriteHeader(http.StatusConflict)
		rw.Write([]byte("File exists"))
		return
	}

	file, err := os.Create(path)
	if err != nil {
		rw.WriteHeader(http.StatusInternalServerError)
		rw.Write([]byte("Error: " + err.Error()))
		return
	}
	defer file.Close()

	file.Write(body)
	file.Sync()

	rw.WriteHeader(http.StatusOK)
	log.Printf("Created file with id: %s\n", id)
}

func DeleteHandler(rw http.ResponseWriter, r *http.Request) {
	id := mux.Vars(r)["id"]
	path := "/Users/sebastian/" + id

	err := os.Remove(path)
	if err != nil {
		// Better error handling would be nice..
		rw.WriteHeader(http.StatusNotFound)
		return
	}

	rw.WriteHeader(http.StatusOK)
	log.Printf("Deleted file with id: %s\n", id)
}

func GetHandler(rw http.ResponseWriter, r *http.Request) {
	id := mux.Vars(r)["id"]
	path := "/Users/sebastian/" + id

	data, err := ioutil.ReadFile(path)
	if err != nil {
		rw.WriteHeader(http.StatusNotFound)
		return
	}

	rw.WriteHeader(http.StatusOK)
	rw.Write(data)
}

func UpdateHandler(rw http.ResponseWriter, r *http.Request) {
	id := mux.Vars(r)["id"]
	path := "/Users/sebastian/" + id
	body, _ := ioutil.ReadAll(r.Body)

	// Ensure that the file exists
	if _, err := os.Stat(path); err != nil {
		rw.WriteHeader(http.StatusNotFound)
		rw.Write([]byte("Error: File not found"))
		return
	}

	// Delete the file
	err := os.Remove(path)
	if err != nil {
		rw.WriteHeader(http.StatusInternalServerError)
		rw.Write([]byte("Error: " + err.Error()))
		return
	}

	// Create the file
	file, err := os.Create(path)
	if err != nil {
		rw.WriteHeader(http.StatusInternalServerError)
		rw.Write([]byte("Error: " + err.Error()))
		return
	}
	defer file.Close()

	file.Write(body)
	file.Sync()

	rw.WriteHeader(http.StatusOK)
	log.Printf("Updated file with id: %s\n", id)
}
<commit_msg>Removed hard coded home dir<commit_after>package main

import (
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path"

	"github.com/gorilla/mux"
)

var basePath string = os.Getenv("HOME")

func main() {
	r := mux.NewRouter()

	file := r.Path("/files/{id}").Subrouter()
	file.Methods("GET").HandlerFunc(GetHandler)
	file.Methods("POST").HandlerFunc(CreateHandler)
	file.Methods("PUT").HandlerFunc(UpdateHandler)
	file.Methods("DELETE").HandlerFunc(DeleteHandler)

	http.ListenAndServe(":"+os.Getenv("PORT"), r)
}

func CreateHandler(rw http.ResponseWriter, r *http.Request) {
	id := mux.Vars(r)["id"]
	filePath := path.Join(basePath, id)
	body, _ := ioutil.ReadAll(r.Body)

	// Check if the file exists
	if _, err := os.Stat(filePath); err == nil {
		rw.WriteHeader(http.StatusConflict)
		rw.Write([]byte("File exists"))
		return
	}

	file, err := os.Create(filePath)
	if err != nil {
		rw.WriteHeader(http.StatusInternalServerError)
		rw.Write([]byte("Error: " + err.Error()))
		return
	}
	defer file.Close()

	file.Write(body)
	file.Sync()

	rw.WriteHeader(http.StatusOK)
	log.Printf("Created file with id: %s\n", id)
}

func DeleteHandler(rw http.ResponseWriter, r *http.Request) {
	id := mux.Vars(r)["id"]
	filePath := path.Join(basePath, id)

	err := os.Remove(filePath)
	if err != nil {
		// Better error handling would be nice..
		rw.WriteHeader(http.StatusNotFound)
		return
	}

	rw.WriteHeader(http.StatusOK)
	log.Printf("Deleted file with id: %s\n", id)
}

func GetHandler(rw http.ResponseWriter, r *http.Request) {
	id := mux.Vars(r)["id"]
	filePath := path.Join(basePath, id)

	data, err := ioutil.ReadFile(filePath)
	if err != nil {
		rw.WriteHeader(http.StatusNotFound)
		return
	}

	rw.WriteHeader(http.StatusOK)
	rw.Write(data)
}

func UpdateHandler(rw http.ResponseWriter, r *http.Request) {
	id := mux.Vars(r)["id"]
	filePath := path.Join(basePath, id)
	body, _ := ioutil.ReadAll(r.Body)

	// Ensure that the file exists
	if _, err := os.Stat(filePath); err != nil {
		rw.WriteHeader(http.StatusNotFound)
		rw.Write([]byte("Error: File not found"))
		return
	}

	// Delete the file
	err := os.Remove(filePath)
	if err != nil {
		rw.WriteHeader(http.StatusInternalServerError)
		rw.Write([]byte("Error: " + err.Error()))
		return
	}

	// Create the file
	file, err := os.Create(filePath)
	if err != nil {
		rw.WriteHeader(http.StatusInternalServerError)
		rw.Write([]byte("Error: " + err.Error()))
		return
	}
	defer file.Close()

	file.Write(body)
	file.Sync()

	rw.WriteHeader(http.StatusOK)
	log.Printf("Updated file with id: %s\n", id)
}
<|endoftext|>


<commit_before>// Copyright 2017 Google LLC
//
// 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 firestore

import (
	"context"
	"errors"

	"cloud.google.com/go/internal/trace"
	gax "github.com/googleapis/gax-go/v2"
	pb "google.golang.org/genproto/googleapis/firestore/v1"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

// Transaction represents a Firestore transaction.
type Transaction struct {
	c              *Client
	ctx            context.Context
	id             []byte
	writes         []*pb.Write
	maxAttempts    int
	readOnly       bool
	readAfterWrite bool
}

// A TransactionOption is an option passed to Client.Transaction.
type TransactionOption interface {
	config(t *Transaction)
}

// MaxAttempts is a TransactionOption that configures the maximum number of times to
// try a transaction. In defaults to DefaultTransactionMaxAttempts.
func MaxAttempts(n int) maxAttempts { return maxAttempts(n) }

type maxAttempts int

func (m maxAttempts) config(t *Transaction) { t.maxAttempts = int(m) }

// DefaultTransactionMaxAttempts is the default number of times to attempt a transaction.
const DefaultTransactionMaxAttempts = 5

// ReadOnly is a TransactionOption that makes the transaction read-only. Read-only
// transactions cannot issue write operations, but are more efficient.
var ReadOnly = ro{}

type ro struct{}

func (ro) config(t *Transaction) { t.readOnly = true }

var (
	// Defined here for testing.
	errReadAfterWrite    = errors.New("firestore: read after write in transaction")
	errWriteReadOnly     = errors.New("firestore: write in read-only transaction")
	errNestedTransaction = errors.New("firestore: nested transaction")
)

type transactionInProgressKey struct{}

// RunTransaction runs f in a transaction. f should use the transaction it is given
// for all Firestore operations. For any operation requiring a context, f should use
// the context it is passed, not the first argument to RunTransaction.
//
// f must not call Commit or Rollback on the provided Transaction.
//
// If f returns nil, RunTransaction commits the transaction. If the commit fails due
// to a conflicting transaction, RunTransaction retries f. It gives up and returns an
// error after a number of attempts that can be configured with the MaxAttempts
// option. If the commit succeeds, RunTransaction returns a nil error.
//
// If f returns non-nil, then the transaction will be rolled back and
// this method will return the same error. The function f is not retried.
//
// Note that when f returns, the transaction is not committed. Calling code
// must not assume that any of f's changes have been committed until
// RunTransaction returns nil.
//
// Since f may be called more than once, f should usually be idempotent – that is, it
// should have the same result when called multiple times.
func (c *Client) RunTransaction(ctx context.Context, f func(context.Context, *Transaction) error, opts ...TransactionOption) (err error) {
	ctx = trace.StartSpan(ctx, "cloud.google.com/go/firestore.Client.RunTransaction")
	defer func() { trace.EndSpan(ctx, err) }()

	if ctx.Value(transactionInProgressKey{}) != nil {
		return errNestedTransaction
	}
	db := c.path()
	t := &Transaction{
		c:           c,
		ctx:         withResourceHeader(ctx, db),
		maxAttempts: DefaultTransactionMaxAttempts,
	}
	for _, opt := range opts {
		opt.config(t)
	}
	var txOpts *pb.TransactionOptions
	if t.readOnly {
		txOpts = &pb.TransactionOptions{
			Mode: &pb.TransactionOptions_ReadOnly_{&pb.TransactionOptions_ReadOnly{}},
		}
	}
	var backoff gax.Backoff
	// TODO(jba): use other than the standard backoff parameters?
	// TODO(jba): get backoff time from gRPC trailer metadata? See
	// extractRetryDelay in https://code.googlesource.com/gocloud/+/master/spanner/retry.go.
	for i := 0; i < t.maxAttempts; i++ {
		t.ctx = trace.StartSpan(t.ctx, "cloud.google.com/go/firestore.Client.BeginTransaction")
		var res *pb.BeginTransactionResponse
		res, err = t.c.c.BeginTransaction(t.ctx, &pb.BeginTransactionRequest{
			Database: db,
			Options:  txOpts,
		})
		trace.EndSpan(t.ctx, err)
		if err != nil {
			return err
		}
		t.id = res.Transaction
		err = f(context.WithValue(ctx, transactionInProgressKey{}, 1), t)
		// Read after write can only be checked client-side, so we make sure to check
		// even if the user does not.
		if err == nil && t.readAfterWrite {
			err = errReadAfterWrite
		}
		if err != nil {
			t.rollback()
			// Prefer f's returned error to rollback error.
			return err
		}
		t.ctx = trace.StartSpan(t.ctx, "cloud.google.com/go/firestore.Client.Commit")
		_, err = t.c.c.Commit(t.ctx, &pb.CommitRequest{
			Database:    t.c.path(),
			Writes:      t.writes,
			Transaction: t.id,
		})
		trace.EndSpan(t.ctx, err)

		// If a read-write transaction returns Aborted, retry.
		// On success or other failures, return here.
		if t.readOnly || status.Code(err) != codes.Aborted {
			// According to the Firestore team, we should not roll back here
			// if err != nil. But spanner does.
			// See https://code.googlesource.com/gocloud/+/master/spanner/transaction.go#740.
			return err
		}

		if txOpts == nil {
			// txOpts can only be nil if is the first retry of a read-write transaction.
			// (It is only set here and in the body of "if t.readOnly" above.)
			// Mention the transaction ID in BeginTransaction so the service
			// knows it is a retry.
			txOpts = &pb.TransactionOptions{
				Mode: &pb.TransactionOptions_ReadWrite_{
					&pb.TransactionOptions_ReadWrite{RetryTransaction: t.id},
				},
			}
		}
		// Use exponential backoff to avoid contention with other running
		// transactions.
		if cerr := sleep(ctx, backoff.Pause()); cerr != nil {
			err = cerr
			break
		}

		// Reset state for the next attempt.
		t.writes = nil
	}
	// If we run out of retries, return the last error we saw (which should
	// be the Aborted from Commit, or a context error).
	if err != nil {
		t.rollback()
	}
	return err
}

func (t *Transaction) rollback() {
	_ = t.c.c.Rollback(t.ctx, &pb.RollbackRequest{
		Database:    t.c.path(),
		Transaction: t.id,
	})
	// Ignore the rollback error.
	// TODO(jba): Log it?
	// Note: Rollback is idempotent so it will be retried by the gapic layer.
}

// Get gets the document in the context of the transaction. The transaction holds a
// pessimistic lock on the returned document.
func (t *Transaction) Get(dr *DocumentRef) (*DocumentSnapshot, error) {
	docsnaps, err := t.GetAll([]*DocumentRef{dr})
	if err != nil {
		return nil, err
	}
	ds := docsnaps[0]
	if !ds.Exists() {
		return ds, status.Errorf(codes.NotFound, "%q not found", dr.Path)
	}
	return ds, nil
}

// GetAll retrieves multiple documents with a single call. The DocumentSnapshots are
// returned in the order of the given DocumentRefs. If a document is not present, the
// corresponding DocumentSnapshot's Exists method will return false. The transaction
// holds a pessimistic lock on all of the returned documents.
func (t *Transaction) GetAll(drs []*DocumentRef) ([]*DocumentSnapshot, error) {
	if len(t.writes) > 0 {
		t.readAfterWrite = true
		return nil, errReadAfterWrite
	}
	return t.c.getAll(t.ctx, drs, t.id)
}

// A Queryer is a Query or a CollectionRef. CollectionRefs act as queries whose
// results are all the documents in the collection.
type Queryer interface {
	query() *Query
}

// Documents returns a DocumentIterator based on given Query or CollectionRef. The
// results will be in the context of the transaction.
func (t *Transaction) Documents(q Queryer) *DocumentIterator {
	if len(t.writes) > 0 {
		t.readAfterWrite = true
		return &DocumentIterator{err: errReadAfterWrite}
	}
	query := q.query()
	return &DocumentIterator{
		iter: newQueryDocumentIterator(t.ctx, query, t.id), q: query,
	}
}

// DocumentRefs returns references to all the documents in the collection, including
// missing documents. A missing document is a document that does not exist but has
// sub-documents.
func (t *Transaction) DocumentRefs(cr *CollectionRef) *DocumentRefIterator {
	if len(t.writes) > 0 {
		t.readAfterWrite = true
		return &DocumentRefIterator{err: errReadAfterWrite}
	}
	return newDocumentRefIterator(t.ctx, cr, t.id)
}

// Create adds a Create operation to the Transaction.
// See DocumentRef.Create for details.
func (t *Transaction) Create(dr *DocumentRef, data interface{}) error {
	return t.addWrites(dr.newCreateWrites(data))
}

// Set adds a Set operation to the Transaction.
// See DocumentRef.Set for details.
func (t *Transaction) Set(dr *DocumentRef, data interface{}, opts ...SetOption) error {
	return t.addWrites(dr.newSetWrites(data, opts))
}

// Delete adds a Delete operation to the Transaction.
// See DocumentRef.Delete for details.
func (t *Transaction) Delete(dr *DocumentRef, opts ...Precondition) error {
	return t.addWrites(dr.newDeleteWrites(opts))
}

// Update adds a new Update operation to the Transaction.
// See DocumentRef.Update for details.
func (t *Transaction) Update(dr *DocumentRef, data []Update, opts ...Precondition) error {
	return t.addWrites(dr.newUpdatePathWrites(data, opts))
}

func (t *Transaction) addWrites(ws []*pb.Write, err error) error {
	if t.readOnly {
		return errWriteReadOnly
	}
	if err != nil {
		return err
	}
	t.writes = append(t.writes, ws...)
	return nil
}
<commit_msg>chore(firestore): Clarify Transactioln.Get error code (#6121)<commit_after>// Copyright 2017 Google LLC
//
// 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 firestore

import (
	"context"
	"errors"

	"cloud.google.com/go/internal/trace"
	gax "github.com/googleapis/gax-go/v2"
	pb "google.golang.org/genproto/googleapis/firestore/v1"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

// Transaction represents a Firestore transaction.
type Transaction struct {
	c              *Client
	ctx            context.Context
	id             []byte
	writes         []*pb.Write
	maxAttempts    int
	readOnly       bool
	readAfterWrite bool
}

// A TransactionOption is an option passed to Client.Transaction.
type TransactionOption interface {
	config(t *Transaction)
}

// MaxAttempts is a TransactionOption that configures the maximum number of times to
// try a transaction. In defaults to DefaultTransactionMaxAttempts.
func MaxAttempts(n int) maxAttempts { return maxAttempts(n) }

type maxAttempts int

func (m maxAttempts) config(t *Transaction) { t.maxAttempts = int(m) }

// DefaultTransactionMaxAttempts is the default number of times to attempt a transaction.
const DefaultTransactionMaxAttempts = 5

// ReadOnly is a TransactionOption that makes the transaction read-only. Read-only
// transactions cannot issue write operations, but are more efficient.
var ReadOnly = ro{}

type ro struct{}

func (ro) config(t *Transaction) { t.readOnly = true }

var (
	// Defined here for testing.
	errReadAfterWrite    = errors.New("firestore: read after write in transaction")
	errWriteReadOnly     = errors.New("firestore: write in read-only transaction")
	errNestedTransaction = errors.New("firestore: nested transaction")
)

type transactionInProgressKey struct{}

// RunTransaction runs f in a transaction. f should use the transaction it is given
// for all Firestore operations. For any operation requiring a context, f should use
// the context it is passed, not the first argument to RunTransaction.
//
// f must not call Commit or Rollback on the provided Transaction.
//
// If f returns nil, RunTransaction commits the transaction. If the commit fails due
// to a conflicting transaction, RunTransaction retries f. It gives up and returns an
// error after a number of attempts that can be configured with the MaxAttempts
// option. If the commit succeeds, RunTransaction returns a nil error.
//
// If f returns non-nil, then the transaction will be rolled back and
// this method will return the same error. The function f is not retried.
//
// Note that when f returns, the transaction is not committed. Calling code
// must not assume that any of f's changes have been committed until
// RunTransaction returns nil.
//
// Since f may be called more than once, f should usually be idempotent – that is, it
// should have the same result when called multiple times.
func (c *Client) RunTransaction(ctx context.Context, f func(context.Context, *Transaction) error, opts ...TransactionOption) (err error) {
	ctx = trace.StartSpan(ctx, "cloud.google.com/go/firestore.Client.RunTransaction")
	defer func() { trace.EndSpan(ctx, err) }()

	if ctx.Value(transactionInProgressKey{}) != nil {
		return errNestedTransaction
	}
	db := c.path()
	t := &Transaction{
		c:           c,
		ctx:         withResourceHeader(ctx, db),
		maxAttempts: DefaultTransactionMaxAttempts,
	}
	for _, opt := range opts {
		opt.config(t)
	}
	var txOpts *pb.TransactionOptions
	if t.readOnly {
		txOpts = &pb.TransactionOptions{
			Mode: &pb.TransactionOptions_ReadOnly_{&pb.TransactionOptions_ReadOnly{}},
		}
	}
	var backoff gax.Backoff
	// TODO(jba): use other than the standard backoff parameters?
	// TODO(jba): get backoff time from gRPC trailer metadata? See
	// extractRetryDelay in https://code.googlesource.com/gocloud/+/master/spanner/retry.go.
	for i := 0; i < t.maxAttempts; i++ {
		t.ctx = trace.StartSpan(t.ctx, "cloud.google.com/go/firestore.Client.BeginTransaction")
		var res *pb.BeginTransactionResponse
		res, err = t.c.c.BeginTransaction(t.ctx, &pb.BeginTransactionRequest{
			Database: db,
			Options:  txOpts,
		})
		trace.EndSpan(t.ctx, err)
		if err != nil {
			return err
		}
		t.id = res.Transaction
		err = f(context.WithValue(ctx, transactionInProgressKey{}, 1), t)
		// Read after write can only be checked client-side, so we make sure to check
		// even if the user does not.
		if err == nil && t.readAfterWrite {
			err = errReadAfterWrite
		}
		if err != nil {
			t.rollback()
			// Prefer f's returned error to rollback error.
			return err
		}
		t.ctx = trace.StartSpan(t.ctx, "cloud.google.com/go/firestore.Client.Commit")
		_, err = t.c.c.Commit(t.ctx, &pb.CommitRequest{
			Database:    t.c.path(),
			Writes:      t.writes,
			Transaction: t.id,
		})
		trace.EndSpan(t.ctx, err)

		// If a read-write transaction returns Aborted, retry.
		// On success or other failures, return here.
		if t.readOnly || status.Code(err) != codes.Aborted {
			// According to the Firestore team, we should not roll back here
			// if err != nil. But spanner does.
			// See https://code.googlesource.com/gocloud/+/master/spanner/transaction.go#740.
			return err
		}

		if txOpts == nil {
			// txOpts can only be nil if is the first retry of a read-write transaction.
			// (It is only set here and in the body of "if t.readOnly" above.)
			// Mention the transaction ID in BeginTransaction so the service
			// knows it is a retry.
			txOpts = &pb.TransactionOptions{
				Mode: &pb.TransactionOptions_ReadWrite_{
					&pb.TransactionOptions_ReadWrite{RetryTransaction: t.id},
				},
			}
		}
		// Use exponential backoff to avoid contention with other running
		// transactions.
		if cerr := sleep(ctx, backoff.Pause()); cerr != nil {
			err = cerr
			break
		}

		// Reset state for the next attempt.
		t.writes = nil
	}
	// If we run out of retries, return the last error we saw (which should
	// be the Aborted from Commit, or a context error).
	if err != nil {
		t.rollback()
	}
	return err
}

func (t *Transaction) rollback() {
	_ = t.c.c.Rollback(t.ctx, &pb.RollbackRequest{
		Database:    t.c.path(),
		Transaction: t.id,
	})
	// Ignore the rollback error.
	// TODO(jba): Log it?
	// Note: Rollback is idempotent so it will be retried by the gapic layer.
}

// Get gets the document in the context of the transaction. The transaction holds a
// pessimistic lock on the returned document. If the document does not exist, Get
// returns a NotFound error, which can be checked with
//    status.Code(err) == codes.NotFound
func (t *Transaction) Get(dr *DocumentRef) (*DocumentSnapshot, error) {
	docsnaps, err := t.GetAll([]*DocumentRef{dr})
	if err != nil {
		return nil, err
	}
	ds := docsnaps[0]
	if !ds.Exists() {
		return ds, status.Errorf(codes.NotFound, "%q not found", dr.Path)
	}
	return ds, nil
}

// GetAll retrieves multiple documents with a single call. The DocumentSnapshots are
// returned in the order of the given DocumentRefs. If a document is not present, the
// corresponding DocumentSnapshot's Exists method will return false. The transaction
// holds a pessimistic lock on all of the returned documents.
func (t *Transaction) GetAll(drs []*DocumentRef) ([]*DocumentSnapshot, error) {
	if len(t.writes) > 0 {
		t.readAfterWrite = true
		return nil, errReadAfterWrite
	}
	return t.c.getAll(t.ctx, drs, t.id)
}

// A Queryer is a Query or a CollectionRef. CollectionRefs act as queries whose
// results are all the documents in the collection.
type Queryer interface {
	query() *Query
}

// Documents returns a DocumentIterator based on given Query or CollectionRef. The
// results will be in the context of the transaction.
func (t *Transaction) Documents(q Queryer) *DocumentIterator {
	if len(t.writes) > 0 {
		t.readAfterWrite = true
		return &DocumentIterator{err: errReadAfterWrite}
	}
	query := q.query()
	return &DocumentIterator{
		iter: newQueryDocumentIterator(t.ctx, query, t.id), q: query,
	}
}

// DocumentRefs returns references to all the documents in the collection, including
// missing documents. A missing document is a document that does not exist but has
// sub-documents.
func (t *Transaction) DocumentRefs(cr *CollectionRef) *DocumentRefIterator {
	if len(t.writes) > 0 {
		t.readAfterWrite = true
		return &DocumentRefIterator{err: errReadAfterWrite}
	}
	return newDocumentRefIterator(t.ctx, cr, t.id)
}

// Create adds a Create operation to the Transaction.
// See DocumentRef.Create for details.
func (t *Transaction) Create(dr *DocumentRef, data interface{}) error {
	return t.addWrites(dr.newCreateWrites(data))
}

// Set adds a Set operation to the Transaction.
// See DocumentRef.Set for details.
func (t *Transaction) Set(dr *DocumentRef, data interface{}, opts ...SetOption) error {
	return t.addWrites(dr.newSetWrites(data, opts))
}

// Delete adds a Delete operation to the Transaction.
// See DocumentRef.Delete for details.
func (t *Transaction) Delete(dr *DocumentRef, opts ...Precondition) error {
	return t.addWrites(dr.newDeleteWrites(opts))
}

// Update adds a new Update operation to the Transaction.
// See DocumentRef.Update for details.
func (t *Transaction) Update(dr *DocumentRef, data []Update, opts ...Precondition) error {
	return t.addWrites(dr.newUpdatePathWrites(data, opts))
}

func (t *Transaction) addWrites(ws []*pb.Write, err error) error {
	if t.readOnly {
		return errWriteReadOnly
	}
	if err != nil {
		return err
	}
	t.writes = append(t.writes, ws...)
	return nil
}
<|endoftext|>