<commit_before>package buildconfiginstantiate

import (
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"time"

	"github.com/golang/glog"

	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/util/httpstream/spdy"
	knet "k8s.io/apimachinery/pkg/util/net"
	kubeletremotecommand "k8s.io/apimachinery/pkg/util/remotecommand"
	"k8s.io/apimachinery/pkg/util/wait"
	apirequest "k8s.io/apiserver/pkg/endpoints/request"
	"k8s.io/apiserver/pkg/registry/rest"
	"k8s.io/client-go/tools/remotecommand"
	kapi "k8s.io/kubernetes/pkg/api"
	kcoreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
	kubeletclient "k8s.io/kubernetes/pkg/kubelet/client"
	"k8s.io/kubernetes/pkg/registry/core/pod"

	buildapi "github.com/openshift/origin/pkg/build/apis/build"
	buildapiv1 "github.com/openshift/origin/pkg/build/apis/build/v1"
	buildstrategy "github.com/openshift/origin/pkg/build/controller/strategy"
	buildtypedclient "github.com/openshift/origin/pkg/build/generated/internalclientset/typed/build/internalversion"
	"github.com/openshift/origin/pkg/build/generator"
	"github.com/openshift/origin/pkg/build/registry"
	buildutil "github.com/openshift/origin/pkg/build/util"
)

var (
	cancelPollInterval = 500 * time.Millisecond
	cancelPollDuration = 30 * time.Second
)

// NewStorage creates a new storage object for build generation
func NewStorage(generator *generator.BuildGenerator) *InstantiateREST {
	return &InstantiateREST{generator: generator}
}

// InstantiateREST is a RESTStorage implementation for a BuildGenerator which supports only
// the Create operation (as the generator has no underlying storage object).
type InstantiateREST struct {
	generator *generator.BuildGenerator
}

var _ rest.Creater = &InstantiateREST{}

// New creates a new build generation request
func (s *InstantiateREST) New() runtime.Object {
	return &buildapi.BuildRequest{}
}

// Create instantiates a new build from a build configuration
func (s *InstantiateREST) Create(ctx apirequest.Context, obj runtime.Object, _ bool) (runtime.Object, error) {
	if err := rest.BeforeCreate(Strategy, ctx, obj); err != nil {
		return nil, err
	}

	request := obj.(*buildapi.BuildRequest)
	if request.TriggeredBy == nil {
		buildTriggerCauses := []buildapi.BuildTriggerCause{}
		request.TriggeredBy = append(buildTriggerCauses,
			buildapi.BuildTriggerCause{
				Message: buildapi.BuildTriggerCauseManualMsg,
			},
		)
	}
	return s.generator.Instantiate(ctx, request)
}

func (s *InstantiateREST) ProducesObject(verb string) interface{} {
	// for documentation purposes
	return buildapiv1.Build{}
}

func (s *InstantiateREST) ProducesMIMETypes(verb string) []string {
	return nil // no additional mime types
}

var _ rest.StorageMetadata = &InstantiateREST{}

func NewBinaryStorage(generator *generator.BuildGenerator, buildClient buildtypedclient.BuildsGetter, podClient kcoreclient.PodsGetter, info kubeletclient.ConnectionInfoGetter) *BinaryInstantiateREST {
	return &BinaryInstantiateREST{
		Generator:      generator,
		BuildClient:    buildClient,
		PodGetter:      &podGetter{podClient},
		ConnectionInfo: info,
		Timeout:        5 * time.Minute,
	}
}

type BinaryInstantiateREST struct {
	Generator      *generator.BuildGenerator
	BuildClient    buildtypedclient.BuildsGetter
	PodGetter      pod.ResourceGetter
	ConnectionInfo kubeletclient.ConnectionInfoGetter
	Timeout        time.Duration
}

var _ rest.Connecter = &BinaryInstantiateREST{}

// New creates a new build generation request
func (r *BinaryInstantiateREST) New() runtime.Object {
	return &buildapi.BinaryBuildRequestOptions{}
}

// Connect returns a ConnectHandler that will handle the request/response for a request
func (r *BinaryInstantiateREST) Connect(ctx apirequest.Context, name string, options runtime.Object, responder rest.Responder) (http.Handler, error) {
	return &binaryInstantiateHandler{
		r:         r,
		responder: responder,
		ctx:       ctx,
		name:      name,
		options:   options.(*buildapi.BinaryBuildRequestOptions),
	}, nil
}

// NewConnectOptions prepares a binary build request.
func (r *BinaryInstantiateREST) NewConnectOptions() (runtime.Object, bool, string) {
	return &buildapi.BinaryBuildRequestOptions{}, false, ""
}

// ConnectMethods returns POST, the only supported binary method.
func (r *BinaryInstantiateREST) ConnectMethods() []string {
	return []string{"POST"}
}

func (r *BinaryInstantiateREST) ProducesObject(verb string) interface{} {
	// for documentation purposes
	return buildapiv1.Build{}
}

func (r *BinaryInstantiateREST) ProducesMIMETypes(verb string) []string {
	return nil // no additional mime types
}

var _ rest.StorageMetadata = &BinaryInstantiateREST{}

// binaryInstantiateHandler responds to upload requests
type binaryInstantiateHandler struct {
	r *BinaryInstantiateREST

	responder rest.Responder
	ctx       apirequest.Context
	name      string
	options   *buildapi.BinaryBuildRequestOptions
}

var _ http.Handler = &binaryInstantiateHandler{}

func (h *binaryInstantiateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()
	build, err := h.handle(r.Body)
	if err != nil {
		h.responder.Error(err)
		return
	}
	h.responder.Object(http.StatusCreated, build)
}

func (h *binaryInstantiateHandler) handle(r io.Reader) (runtime.Object, error) {
	h.options.Name = h.name
	if err := rest.BeforeCreate(BinaryStrategy, h.ctx, h.options); err != nil {
		glog.Infof("failed to validate binary: %#v", h.options)
		return nil, err
	}

	request := &buildapi.BuildRequest{}
	request.Name = h.name
	if len(h.options.Commit) > 0 {
		request.Revision = &buildapi.SourceRevision{
			Git: &buildapi.GitSourceRevision{
				Committer: buildapi.SourceControlUser{
					Name:  h.options.CommitterName,
					Email: h.options.CommitterEmail,
				},
				Author: buildapi.SourceControlUser{
					Name:  h.options.AuthorName,
					Email: h.options.AuthorEmail,
				},
				Message: h.options.Message,
				Commit:  h.options.Commit,
			},
		}
	}
	request.Binary = &buildapi.BinaryBuildSource{
		AsFile: h.options.AsFile,
	}

	var build *buildapi.Build
	start := time.Now()
	if err := wait.Poll(time.Second, h.r.Timeout, func() (bool, error) {
		result, err := h.r.Generator.Instantiate(h.ctx, request)
		if err != nil {
			if errors.IsNotFound(err) {
				if s, ok := err.(errors.APIStatus); ok {
					if s.Status().Kind == "imagestreamtags" {
						return false, nil
					}
				}
			}
			glog.V(2).Infof("failed to instantiate: %#v", request)
			return false, err
		}
		build = result
		return true, nil
	}); err != nil {
		return nil, err
	}
	remaining := h.r.Timeout - time.Since(start)

	// Attempt to cancel the build if it did not start running
	// before we gave up.
	cancel := true
	defer func() {
		if !cancel {
			return
		}
		h.cancelBuild(build)
	}()

	latest, ok, err := registry.WaitForRunningBuild(h.r.BuildClient, build, remaining)

	switch {
	// err checks, no ok check, needs to occur before ref to latest
	case err == registry.ErrBuildDeleted:
		return nil, errors.NewBadRequest(fmt.Sprintf("build %s was deleted before it started: %s", build.Name, buildutil.NoBuildLogsMessage))
	case err != nil:
		return nil, errors.NewBadRequest(fmt.Sprintf("unable to wait for build %s to run: %v", build.Name, err))
	case !ok:
		return nil, errors.NewTimeoutError(fmt.Sprintf("timed out waiting for build %s to start after %s", build.Name, h.r.Timeout), 0)
	case latest.Status.Phase == buildapi.BuildPhaseError:
		// don't cancel the build if it reached a terminal state on its own
		cancel = false
		return nil, errors.NewBadRequest(fmt.Sprintf("build %s encountered an error: %s", build.Name, buildutil.NoBuildLogsMessage))
	case latest.Status.Phase == buildapi.BuildPhaseFailed:
		// don't cancel the build if it reached a terminal state on its own
		cancel = false
		return nil, errors.NewBadRequest(fmt.Sprintf("build %s failed: %s: %s", build.Name, build.Status.Reason, build.Status.Message))
	case latest.Status.Phase == buildapi.BuildPhaseCancelled:
		// don't cancel the build if it reached a terminal state on its own
		cancel = false
		return nil, errors.NewBadRequest(fmt.Sprintf("build %s was cancelled: %s", build.Name, buildutil.NoBuildLogsMessage))
	case latest.Status.Phase != buildapi.BuildPhaseRunning:
		return nil, errors.NewBadRequest(fmt.Sprintf("cannot upload file to build %s with status %s", build.Name, latest.Status.Phase))
	}

	buildPodName := buildapi.GetBuildPodName(build)
	opts := &kapi.PodAttachOptions{
		Stdin: true,
		// TODO remove Stdout and Stderr once https://github.com/kubernetes/kubernetes/issues/44448 is
		// fixed
		Stdout:    true,
		Stderr:    true,
		Container: buildstrategy.GitCloneContainer,
	}
	location, transport, err := pod.AttachLocation(h.r.PodGetter, h.r.ConnectionInfo, h.ctx, buildPodName, opts)
	if err != nil {
		if errors.IsNotFound(err) {
			return nil, errors.NewNotFound(kapi.Resource("pod"), buildPodName)
		}
		return nil, errors.NewBadRequest(err.Error())
	}
	tlsClientConfig, err := knet.TLSClientConfig(transport)
	if err != nil {
		return nil, errors.NewInternalError(fmt.Errorf("unable to connect to node, could not retrieve TLS client config: %v", err))
	}
	upgrader := spdy.NewRoundTripper(tlsClientConfig, false)
	exec, err := remotecommand.NewStreamExecutor(upgrader, nil, "POST", location)
	if err != nil {
		return nil, errors.NewInternalError(fmt.Errorf("unable to connect to server: %v", err))
	}
	streamOptions := remotecommand.StreamOptions{
		SupportedProtocols: kubeletremotecommand.SupportedStreamingProtocols,
		Stdin:              r,
		// TODO remove Stdout and Stderr once https://github.com/kubernetes/kubernetes/issues/44448 is
		// fixed
		Stdout: ioutil.Discard,
		Stderr: ioutil.Discard,
	}
	if err := exec.Stream(streamOptions); err != nil {
		return nil, errors.NewInternalError(err)
	}
	cancel = false
	return latest, nil
}

// cancelBuild will mark a build for cancellation unless
// cancel is false in which case it is a no-op.
func (h *binaryInstantiateHandler) cancelBuild(build *buildapi.Build) {
	build.Status.Cancelled = true
	h.r.Generator.Client.UpdateBuild(h.ctx, build)
	wait.Poll(cancelPollInterval, cancelPollDuration, func() (bool, error) {
		build.Status.Cancelled = true
		err := h.r.Generator.Client.UpdateBuild(h.ctx, build)
		switch {
		case err != nil && errors.IsConflict(err):
			build, err = h.r.Generator.Client.GetBuild(h.ctx, build.Name, &metav1.GetOptions{})
			return false, err
		default:
			return true, err
		}
	})
}

type podGetter struct {
	podsNamespacer kcoreclient.PodsGetter
}

func (g *podGetter) Get(ctx apirequest.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
	ns, ok := apirequest.NamespaceFrom(ctx)
	if !ok {
		return nil, errors.NewBadRequest("namespace parameter required.")
	}
	return g.podsNamespacer.Pods(ns).Get(name, *options)
}
<commit_msg>interesting: buildconfig/instantiate spdy executor change<commit_after>package buildconfiginstantiate

import (
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"time"

	"github.com/golang/glog"

	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/util/httpstream/spdy"
	knet "k8s.io/apimachinery/pkg/util/net"
	"k8s.io/apimachinery/pkg/util/wait"
	apirequest "k8s.io/apiserver/pkg/endpoints/request"
	"k8s.io/apiserver/pkg/registry/rest"
	"k8s.io/client-go/tools/remotecommand"
	kapi "k8s.io/kubernetes/pkg/api"
	kcoreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
	kubeletclient "k8s.io/kubernetes/pkg/kubelet/client"
	"k8s.io/kubernetes/pkg/registry/core/pod"

	buildapi "github.com/openshift/origin/pkg/build/apis/build"
	buildapiv1 "github.com/openshift/origin/pkg/build/apis/build/v1"
	buildstrategy "github.com/openshift/origin/pkg/build/controller/strategy"
	buildtypedclient "github.com/openshift/origin/pkg/build/generated/internalclientset/typed/build/internalversion"
	"github.com/openshift/origin/pkg/build/generator"
	"github.com/openshift/origin/pkg/build/registry"
	buildutil "github.com/openshift/origin/pkg/build/util"
)

var (
	cancelPollInterval = 500 * time.Millisecond
	cancelPollDuration = 30 * time.Second
)

// NewStorage creates a new storage object for build generation
func NewStorage(generator *generator.BuildGenerator) *InstantiateREST {
	return &InstantiateREST{generator: generator}
}

// InstantiateREST is a RESTStorage implementation for a BuildGenerator which supports only
// the Create operation (as the generator has no underlying storage object).
type InstantiateREST struct {
	generator *generator.BuildGenerator
}

var _ rest.Creater = &InstantiateREST{}

// New creates a new build generation request
func (s *InstantiateREST) New() runtime.Object {
	return &buildapi.BuildRequest{}
}

// Create instantiates a new build from a build configuration
func (s *InstantiateREST) Create(ctx apirequest.Context, obj runtime.Object, _ bool) (runtime.Object, error) {
	if err := rest.BeforeCreate(Strategy, ctx, obj); err != nil {
		return nil, err
	}

	request := obj.(*buildapi.BuildRequest)
	if request.TriggeredBy == nil {
		buildTriggerCauses := []buildapi.BuildTriggerCause{}
		request.TriggeredBy = append(buildTriggerCauses,
			buildapi.BuildTriggerCause{
				Message: buildapi.BuildTriggerCauseManualMsg,
			},
		)
	}
	return s.generator.Instantiate(ctx, request)
}

func (s *InstantiateREST) ProducesObject(verb string) interface{} {
	// for documentation purposes
	return buildapiv1.Build{}
}

func (s *InstantiateREST) ProducesMIMETypes(verb string) []string {
	return nil // no additional mime types
}

var _ rest.StorageMetadata = &InstantiateREST{}

func NewBinaryStorage(generator *generator.BuildGenerator, buildClient buildtypedclient.BuildsGetter, podClient kcoreclient.PodsGetter, info kubeletclient.ConnectionInfoGetter) *BinaryInstantiateREST {
	return &BinaryInstantiateREST{
		Generator:      generator,
		BuildClient:    buildClient,
		PodGetter:      &podGetter{podClient},
		ConnectionInfo: info,
		Timeout:        5 * time.Minute,
	}
}

type BinaryInstantiateREST struct {
	Generator      *generator.BuildGenerator
	BuildClient    buildtypedclient.BuildsGetter
	PodGetter      pod.ResourceGetter
	ConnectionInfo kubeletclient.ConnectionInfoGetter
	Timeout        time.Duration
}

var _ rest.Connecter = &BinaryInstantiateREST{}

// New creates a new build generation request
func (r *BinaryInstantiateREST) New() runtime.Object {
	return &buildapi.BinaryBuildRequestOptions{}
}

// Connect returns a ConnectHandler that will handle the request/response for a request
func (r *BinaryInstantiateREST) Connect(ctx apirequest.Context, name string, options runtime.Object, responder rest.Responder) (http.Handler, error) {
	return &binaryInstantiateHandler{
		r:         r,
		responder: responder,
		ctx:       ctx,
		name:      name,
		options:   options.(*buildapi.BinaryBuildRequestOptions),
	}, nil
}

// NewConnectOptions prepares a binary build request.
func (r *BinaryInstantiateREST) NewConnectOptions() (runtime.Object, bool, string) {
	return &buildapi.BinaryBuildRequestOptions{}, false, ""
}

// ConnectMethods returns POST, the only supported binary method.
func (r *BinaryInstantiateREST) ConnectMethods() []string {
	return []string{"POST"}
}

func (r *BinaryInstantiateREST) ProducesObject(verb string) interface{} {
	// for documentation purposes
	return buildapiv1.Build{}
}

func (r *BinaryInstantiateREST) ProducesMIMETypes(verb string) []string {
	return nil // no additional mime types
}

var _ rest.StorageMetadata = &BinaryInstantiateREST{}

// binaryInstantiateHandler responds to upload requests
type binaryInstantiateHandler struct {
	r *BinaryInstantiateREST

	responder rest.Responder
	ctx       apirequest.Context
	name      string
	options   *buildapi.BinaryBuildRequestOptions
}

var _ http.Handler = &binaryInstantiateHandler{}

func (h *binaryInstantiateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()
	build, err := h.handle(r.Body)
	if err != nil {
		h.responder.Error(err)
		return
	}
	h.responder.Object(http.StatusCreated, build)
}

func (h *binaryInstantiateHandler) handle(r io.Reader) (runtime.Object, error) {
	h.options.Name = h.name
	if err := rest.BeforeCreate(BinaryStrategy, h.ctx, h.options); err != nil {
		glog.Infof("failed to validate binary: %#v", h.options)
		return nil, err
	}

	request := &buildapi.BuildRequest{}
	request.Name = h.name
	if len(h.options.Commit) > 0 {
		request.Revision = &buildapi.SourceRevision{
			Git: &buildapi.GitSourceRevision{
				Committer: buildapi.SourceControlUser{
					Name:  h.options.CommitterName,
					Email: h.options.CommitterEmail,
				},
				Author: buildapi.SourceControlUser{
					Name:  h.options.AuthorName,
					Email: h.options.AuthorEmail,
				},
				Message: h.options.Message,
				Commit:  h.options.Commit,
			},
		}
	}
	request.Binary = &buildapi.BinaryBuildSource{
		AsFile: h.options.AsFile,
	}

	var build *buildapi.Build
	start := time.Now()
	if err := wait.Poll(time.Second, h.r.Timeout, func() (bool, error) {
		result, err := h.r.Generator.Instantiate(h.ctx, request)
		if err != nil {
			if errors.IsNotFound(err) {
				if s, ok := err.(errors.APIStatus); ok {
					if s.Status().Kind == "imagestreamtags" {
						return false, nil
					}
				}
			}
			glog.V(2).Infof("failed to instantiate: %#v", request)
			return false, err
		}
		build = result
		return true, nil
	}); err != nil {
		return nil, err
	}
	remaining := h.r.Timeout - time.Since(start)

	// Attempt to cancel the build if it did not start running
	// before we gave up.
	cancel := true
	defer func() {
		if !cancel {
			return
		}
		h.cancelBuild(build)
	}()

	latest, ok, err := registry.WaitForRunningBuild(h.r.BuildClient, build, remaining)

	switch {
	// err checks, no ok check, needs to occur before ref to latest
	case err == registry.ErrBuildDeleted:
		return nil, errors.NewBadRequest(fmt.Sprintf("build %s was deleted before it started: %s", build.Name, buildutil.NoBuildLogsMessage))
	case err != nil:
		return nil, errors.NewBadRequest(fmt.Sprintf("unable to wait for build %s to run: %v", build.Name, err))
	case !ok:
		return nil, errors.NewTimeoutError(fmt.Sprintf("timed out waiting for build %s to start after %s", build.Name, h.r.Timeout), 0)
	case latest.Status.Phase == buildapi.BuildPhaseError:
		// don't cancel the build if it reached a terminal state on its own
		cancel = false
		return nil, errors.NewBadRequest(fmt.Sprintf("build %s encountered an error: %s", build.Name, buildutil.NoBuildLogsMessage))
	case latest.Status.Phase == buildapi.BuildPhaseFailed:
		// don't cancel the build if it reached a terminal state on its own
		cancel = false
		return nil, errors.NewBadRequest(fmt.Sprintf("build %s failed: %s: %s", build.Name, build.Status.Reason, build.Status.Message))
	case latest.Status.Phase == buildapi.BuildPhaseCancelled:
		// don't cancel the build if it reached a terminal state on its own
		cancel = false
		return nil, errors.NewBadRequest(fmt.Sprintf("build %s was cancelled: %s", build.Name, buildutil.NoBuildLogsMessage))
	case latest.Status.Phase != buildapi.BuildPhaseRunning:
		return nil, errors.NewBadRequest(fmt.Sprintf("cannot upload file to build %s with status %s", build.Name, latest.Status.Phase))
	}

	buildPodName := buildapi.GetBuildPodName(build)
	opts := &kapi.PodAttachOptions{
		Stdin: true,
		// TODO remove Stdout and Stderr once https://github.com/kubernetes/kubernetes/issues/44448 is
		// fixed
		Stdout:    true,
		Stderr:    true,
		Container: buildstrategy.GitCloneContainer,
	}
	location, transport, err := pod.AttachLocation(h.r.PodGetter, h.r.ConnectionInfo, h.ctx, buildPodName, opts)
	if err != nil {
		if errors.IsNotFound(err) {
			return nil, errors.NewNotFound(kapi.Resource("pod"), buildPodName)
		}
		return nil, errors.NewBadRequest(err.Error())
	}
	tlsClientConfig, err := knet.TLSClientConfig(transport)
	if err != nil {
		return nil, errors.NewInternalError(fmt.Errorf("unable to connect to node, could not retrieve TLS client config: %v", err))
	}
	upgrader := spdy.NewRoundTripper(tlsClientConfig, true)
	exec, err := remotecommand.NewSPDYExecutorForTransports(upgrader, upgrader, "POST", location)
	if err != nil {
		return nil, errors.NewInternalError(fmt.Errorf("unable to connect to server: %v", err))
	}
	streamOptions := remotecommand.StreamOptions{
		Stdin: r,
		// TODO remove Stdout and Stderr once https://github.com/kubernetes/kubernetes/issues/44448 is
		// fixed
		Stdout: ioutil.Discard,
		Stderr: ioutil.Discard,
	}
	if err := exec.Stream(streamOptions); err != nil {
		return nil, errors.NewInternalError(err)
	}
	cancel = false
	return latest, nil
}

// cancelBuild will mark a build for cancellation unless
// cancel is false in which case it is a no-op.
func (h *binaryInstantiateHandler) cancelBuild(build *buildapi.Build) {
	build.Status.Cancelled = true
	h.r.Generator.Client.UpdateBuild(h.ctx, build)
	wait.Poll(cancelPollInterval, cancelPollDuration, func() (bool, error) {
		build.Status.Cancelled = true
		err := h.r.Generator.Client.UpdateBuild(h.ctx, build)
		switch {
		case err != nil && errors.IsConflict(err):
			build, err = h.r.Generator.Client.GetBuild(h.ctx, build.Name, &metav1.GetOptions{})
			return false, err
		default:
			return true, err
		}
	})
}

type podGetter struct {
	podsNamespacer kcoreclient.PodsGetter
}

func (g *podGetter) Get(ctx apirequest.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
	ns, ok := apirequest.NamespaceFrom(ctx)
	if !ok {
		return nil, errors.NewBadRequest("namespace parameter required.")
	}
	return g.podsNamespacer.Pods(ns).Get(name, *options)
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors 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 ktmpl

import "text/template"

// V1Beta3 is kubeadm config template for Kubernetes v1.23.0+
var V1Beta3 = template.Must(template.New("configTmpl-v1beta3").Funcs(template.FuncMap{
	"printMapInOrder": printMapInOrder,
}).Parse(`apiVersion: kubeadm.k8s.io/v1beta3
kind: InitConfiguration
localAPIEndpoint:
  advertiseAddress: {{.AdvertiseAddress}}
  bindPort: {{.APIServerPort}}
bootstrapTokens:
  - groups:
      - system:bootstrappers:kubeadm:default-node-token
    ttl: 24h0m0s
    usages:
      - signing
      - authentication
nodeRegistration:
  criSocket: {{if .CRISocket}}{{.CRISocket}}{{else}}/var/run/dockershim.sock{{end}}
  name: "{{.NodeName}}"
  kubeletExtraArgs:
    node-ip: {{.NodeIP}}
  taints: []
---
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
{{ if .ImageRepository}}imageRepository: {{.ImageRepository}}
{{end}}{{range .ComponentOptions}}{{.Component}}:
{{- range $k, $v := .Pairs }}
  {{$k}}: {{$v}}
{{- end}}
  extraArgs:
{{- range $i, $val := printMapInOrder .ExtraArgs ": " }}
    {{$val}}
{{- end}}
{{end -}}
{{if .FeatureArgs}}featureGates:
{{range $i, $val := .FeatureArgs}}{{$i}}: {{$val}}
{{end -}}{{end -}}
certificatesDir: {{.CertDir}}
clusterName: mk
controlPlaneEndpoint: {{.ControlPlaneAddress}}:{{.APIServerPort}}
etcd:
  local:
    dataDir: {{.EtcdDataDir}}
    extraArgs:
      proxy-refresh-interval: "70000"
{{- range $i, $val := printMapInOrder .EtcdExtraArgs ": " }}
      {{$val}}
{{- end}}
kubernetesVersion: {{.KubernetesVersion}}
networking:
  dnsDomain: {{if .DNSDomain}}{{.DNSDomain}}{{else}}cluster.local{{end}}
  podSubnet: "{{.PodSubnet }}"
  serviceSubnet: {{.ServiceCIDR}}
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
authentication:
  x509:
    clientCAFile: {{.ClientCAFile}}
cgroupDriver: {{.CgroupDriver}}
clusterDomain: "{{if .DNSDomain}}{{.DNSDomain}}{{else}}cluster.local{{end}}"
# disable disk resource management by default
imageGCHighThresholdPercent: 100
evictionHard:
  nodefs.available: "0%"
  nodefs.inodesFree: "0%"
  imagefs.available: "0%"
failSwapOn: false
staticPodPath: {{.StaticPodPath}}
---
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
clusterCIDR: "{{.PodSubnet }}"
metricsBindAddress: 0.0.0.0:10249
conntrack:
  maxPerCore: 0
# Skip setting "net.netfilter.nf_conntrack_tcp_timeout_established"
  tcpEstablishedTimeout: 0s
# Skip setting "net.netfilter.nf_conntrack_tcp_timeout_close"
  tcpCloseWaitTimeout: 0s
{{- range $i, $val := printMapInOrder .KubeProxyOptions ": " }}
{{$val}}
{{- end}}
`))
<commit_msg>address comments<commit_after>/*
Copyright 2022 The Kubernetes Authors 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 ktmpl

import "text/template"

// V1Beta3 is kubeadm config template for Kubernetes v1.23.0+
var V1Beta3 = template.Must(template.New("configTmpl-v1beta3").Funcs(template.FuncMap{
	"printMapInOrder": printMapInOrder,
}).Parse(`apiVersion: kubeadm.k8s.io/v1beta3
kind: InitConfiguration
localAPIEndpoint:
  advertiseAddress: {{.AdvertiseAddress}}
  bindPort: {{.APIServerPort}}
bootstrapTokens:
  - groups:
      - system:bootstrappers:kubeadm:default-node-token
    ttl: 24h0m0s
    usages:
      - signing
      - authentication
nodeRegistration:
  criSocket: {{if .CRISocket}}{{.CRISocket}}{{else}}/var/run/dockershim.sock{{end}}
  name: "{{.NodeName}}"
  kubeletExtraArgs:
    node-ip: {{.NodeIP}}
  taints: []
---
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
{{ if .ImageRepository}}imageRepository: {{.ImageRepository}}
{{end}}{{range .ComponentOptions}}{{.Component}}:
{{- range $k, $v := .Pairs }}
  {{$k}}: {{$v}}
{{- end}}
  extraArgs:
{{- range $i, $val := printMapInOrder .ExtraArgs ": " }}
    {{$val}}
{{- end}}
{{end -}}
{{if .FeatureArgs}}featureGates:
{{range $i, $val := .FeatureArgs}}{{$i}}: {{$val}}
{{end -}}{{end -}}
certificatesDir: {{.CertDir}}
clusterName: mk
controlPlaneEndpoint: {{.ControlPlaneAddress}}:{{.APIServerPort}}
etcd:
  local:
    dataDir: {{.EtcdDataDir}}
    extraArgs:
      proxy-refresh-interval: "70000"
{{- range $i, $val := printMapInOrder .EtcdExtraArgs ": " }}
      {{$val}}
{{- end}}
kubernetesVersion: {{.KubernetesVersion}}
networking:
  dnsDomain: {{if .DNSDomain}}{{.DNSDomain}}{{else}}cluster.local{{end}}
  podSubnet: "{{.PodSubnet }}"
  serviceSubnet: {{.ServiceCIDR}}
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
authentication:
  x509:
    clientCAFile: {{.ClientCAFile}}
cgroupDriver: {{.CgroupDriver}}
clusterDomain: "{{if .DNSDomain}}{{.DNSDomain}}{{else}}cluster.local{{end}}"
# disable disk resource management by default
imageGCHighThresholdPercent: 100
evictionHard:
  nodefs.available: "0%"
  nodefs.inodesFree: "0%"
  imagefs.available: "0%"
failSwapOn: false
staticPodPath: {{.StaticPodPath}}
---
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
clusterCIDR: "{{.PodSubnet }}"
metricsBindAddress: 0.0.0.0:10249
conntrack:
  maxPerCore: 0
# Skip setting "net.netfilter.nf_conntrack_tcp_timeout_established"
  tcpEstablishedTimeout: 0s
# Skip setting "net.netfilter.nf_conntrack_tcp_timeout_close"
  tcpCloseWaitTimeout: 0s
{{- range $i, $val := printMapInOrder .KubeProxyOptions ": " }}
{{$val}}
{{- end}}
`))
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2013, 2014 Conformal Systems LLC <info@conformal.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

package main

import (
	"fmt"
	"github.com/conformal/btcwire"
	"io/ioutil"
	"os"
	"path/filepath"
	"sync"
	"time"
)

var (
	// dirtyAccounts holds a set of accounts that include dirty components.
	dirtyAccounts = struct {
		sync.Mutex
		m map[*Account]bool
	}{
		m: make(map[*Account]bool),
	}
)

// networkDir returns the directory name of a network directory to hold account
// files.
func networkDir(net btcwire.BitcoinNet) string {
	var netname string
	if net == btcwire.MainNet {
		netname = "mainnet"
	} else {
		netname = "testnet"
	}
	return filepath.Join(cfg.DataDir, netname)
}

// tmpNetworkDir returns the temporary directory name for a given network.
func tmpNetworkDir(net btcwire.BitcoinNet) string {
	return networkDir(net) + "_tmp"
}

// checkCreateDir checks that the path exists and is a directory.
// If path does not exist, it is created.
func checkCreateDir(path string) error {
	if fi, err := os.Stat(path); err != nil {
		if os.IsNotExist(err) {
			// Attempt data directory creation
			if err = os.MkdirAll(path, 0700); err != nil {
				return fmt.Errorf("cannot create directory: %s", err)
			}
		} else {
			return fmt.Errorf("error checking directory: %s", err)
		}
	} else {
		if !fi.IsDir() {
			return fmt.Errorf("path '%s' is not a directory", path)
		}
	}

	return nil
}

// accountFilename returns the filepath of an account file given the
// filename suffix ("wallet.bin", "tx.bin", or "utxo.bin"), account
// name and the network directory holding the file.
func accountFilename(suffix, account, netdir string) string {
	if account == "" {
		// default account
		return filepath.Join(netdir, suffix)
	}

	// non-default account
	return filepath.Join(netdir, fmt.Sprintf("%v-%v", account, suffix))
}

// DirtyAccountSyncer synces dirty accounts for cases where the updated
// information was not required to be immediately written to disk.  Accounts
// may be added to dirtyAccounts and will be checked and processed every 10
// seconds by this function.
//
// This never returns and is meant to be called from a goroutine.
func DirtyAccountSyncer() {
	ticker := time.Tick(10 * time.Second)
	for {
		select {
		case <-ticker:
			dirtyAccounts.Lock()
			for a := range dirtyAccounts.m {
				log.Debugf("Syncing account '%v' to disk",
					a.Wallet.Name())
				if err := a.writeDirtyToDisk(); err != nil {
					log.Errorf("cannot sync dirty wallet: %v",
						err)
				} else {
					delete(dirtyAccounts.m, a)
				}
			}
			dirtyAccounts.Unlock()
		}
	}
}

// freshDir creates a new directory specified by path if it does not
// exist.  If the directory already exists, all files contained in the
// directory are removed.
func freshDir(path string) error {
	if err := checkCreateDir(path); err != nil {
		return err
	}

	// Remove all files in the directory.
	fd, err := os.Open(path)
	if err != nil {
		return err
	}
	defer fd.Close()
	names, err := fd.Readdirnames(0)
	if err != nil {
		return err
	}
	for _, name := range names {
		if err := os.RemoveAll(name); err != nil {
			return err
		}
	}

	return nil
}

// writeAllToFreshDir writes all account files to the specified directory.
// If dir already exists, any old files are removed.  If dir does not
// exist, it is created.
//
// It is a runtime error to call this function while not holding each
// wallet, tx store, and utxo store writer lock.
func (a *Account) writeAllToFreshDir(dir string) error {
	if err := freshDir(dir); err != nil {
		return err
	}

	wfilepath := accountFilename("wallet.bin", a.name, dir)
	txfilepath := accountFilename("tx.bin", a.name, dir)
	utxofilepath := accountFilename("utxo.bin", a.name, dir)

	wfile, err := os.Create(wfilepath)
	if err != nil {
		return err
	}
	defer wfile.Close()
	txfile, err := os.Create(txfilepath)
	if err != nil {
		return err
	}
	defer txfile.Close()
	utxofile, err := os.Create(utxofilepath)
	if err != nil {
		return err
	}
	defer utxofile.Close()

	if _, err := a.Wallet.WriteTo(wfile); err != nil {
		return err
	}
	a.dirty = false

	if _, err := a.TxStore.s.WriteTo(txfile); err != nil {
		return err
	}
	a.TxStore.dirty = false

	if _, err := a.UtxoStore.s.WriteTo(utxofile); err != nil {
		return err
	}
	a.UtxoStore.dirty = false

	return nil
}

// writeDirtyToDisk checks for the dirty flag on an account's wallet,
// txstore, and utxostore, writing them to disk if any are dirty.
func (a *Account) writeDirtyToDisk() error {
	netdir := networkDir(cfg.Net())
	if err := checkCreateDir(netdir); err != nil {
		return err
	}

	wfilepath := accountFilename("wallet.bin", a.name, netdir)
	txfilepath := accountFilename("tx.bin", a.name, netdir)
	utxofilepath := accountFilename("utxo.bin", a.name, netdir)

	// UTXOs and transactions are synced to disk first.  This prevents
	// any races from saving a wallet marked to be synced with block N
	// and btcwallet closing while the UTXO and Tx files are only synced
	// with block N-1.

	// UTXOs
	a.UtxoStore.RLock()
	dirty := a.TxStore.dirty
	a.UtxoStore.RUnlock()
	if dirty {
		netdir, filename := filepath.Split(utxofilepath)
		tmpfile, err := ioutil.TempFile(netdir, filename)
		if err != nil {
			return err
		}

		a.UtxoStore.Lock()
		defer a.UtxoStore.Unlock()

		if _, err = a.UtxoStore.s.WriteTo(tmpfile); err != nil {
			return err
		}
		tmpfile.Close()

		if err = Rename(tmpfile.Name(), utxofilepath); err != nil {
			return err
		}

		a.UtxoStore.dirty = false
	}

	// Transactions
	a.TxStore.RLock()
	dirty = a.TxStore.dirty
	a.TxStore.RUnlock()
	if dirty {
		netdir, filename := filepath.Split(txfilepath)
		tmpfile, err := ioutil.TempFile(netdir, filename)
		if err != nil {
			return err
		}

		a.TxStore.Lock()
		defer a.TxStore.Unlock()

		if _, err = a.TxStore.s.WriteTo(tmpfile); err != nil {
			return err
		}
		tmpfile.Close()

		if err = Rename(tmpfile.Name(), txfilepath); err != nil {
			return err
		}

		a.TxStore.dirty = false
	}

	// Wallet
	a.mtx.RLock()
	dirty = a.dirty
	a.mtx.RUnlock()
	if dirty {
		netdir, filename := filepath.Split(wfilepath)
		tmpfile, err := ioutil.TempFile(netdir, filename)
		if err != nil {
			return err
		}

		a.mtx.Lock()
		defer a.mtx.Unlock()

		if _, err = a.WriteTo(tmpfile); err != nil {
			return err
		}
		tmpfile.Close()

		if err = Rename(tmpfile.Name(), wfilepath); err != nil {
			return err
		}

		a.dirty = false
	}

	return nil
}

// WriteExport writes an account to a special export directory named
// by dirName.  Any previous files are overwritten.
func (a *Account) WriteExport(dirName string) error {
	exportPath := filepath.Join(networkDir(cfg.Net()), dirName)
	if err := checkCreateDir(exportPath); err != nil {
		return err
	}

	aname := a.Name()
	wfilepath := accountFilename("wallet.bin", aname, exportPath)
	txfilepath := accountFilename("tx.bin", aname, exportPath)
	utxofilepath := accountFilename("utxo.bin", aname, exportPath)

	a.UtxoStore.RLock()
	defer a.UtxoStore.RUnlock()
	utxofile, err := os.Create(utxofilepath)
	if err != nil {
		return err
	}
	defer utxofile.Close()
	if _, err := a.UtxoStore.s.WriteTo(utxofile); err != nil {
		return err
	}

	a.TxStore.RLock()
	defer a.TxStore.RUnlock()
	txfile, err := os.Create(txfilepath)
	if err != nil {
		return err
	}
	defer txfile.Close()
	if _, err := a.TxStore.s.WriteTo(txfile); err != nil {
		return err
	}

	a.mtx.RLock()
	defer a.mtx.RUnlock()
	wfile, err := os.Create(wfilepath)
	if err != nil {
		return err
	}
	defer wfile.Close()
	if _, err := a.Wallet.WriteTo(wfile); err != nil {
		return err
	}

	return nil
}
<commit_msg>Fixes for writeDirtyToDisk.<commit_after>/*
 * Copyright (c) 2013, 2014 Conformal Systems LLC <info@conformal.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

package main

import (
	"fmt"
	"github.com/conformal/btcwire"
	"io/ioutil"
	"os"
	"path/filepath"
	"sync"
	"time"
)

var (
	// dirtyAccounts holds a set of accounts that include dirty components.
	dirtyAccounts = struct {
		sync.Mutex
		m map[*Account]bool
	}{
		m: make(map[*Account]bool),
	}
)

// networkDir returns the directory name of a network directory to hold account
// files.
func networkDir(net btcwire.BitcoinNet) string {
	var netname string
	if net == btcwire.MainNet {
		netname = "mainnet"
	} else {
		netname = "testnet"
	}
	return filepath.Join(cfg.DataDir, netname)
}

// tmpNetworkDir returns the temporary directory name for a given network.
func tmpNetworkDir(net btcwire.BitcoinNet) string {
	return networkDir(net) + "_tmp"
}

// checkCreateDir checks that the path exists and is a directory.
// If path does not exist, it is created.
func checkCreateDir(path string) error {
	if fi, err := os.Stat(path); err != nil {
		if os.IsNotExist(err) {
			// Attempt data directory creation
			if err = os.MkdirAll(path, 0700); err != nil {
				return fmt.Errorf("cannot create directory: %s", err)
			}
		} else {
			return fmt.Errorf("error checking directory: %s", err)
		}
	} else {
		if !fi.IsDir() {
			return fmt.Errorf("path '%s' is not a directory", path)
		}
	}

	return nil
}

// accountFilename returns the filepath of an account file given the
// filename suffix ("wallet.bin", "tx.bin", or "utxo.bin"), account
// name and the network directory holding the file.
func accountFilename(suffix, account, netdir string) string {
	if account == "" {
		// default account
		return filepath.Join(netdir, suffix)
	}

	// non-default account
	return filepath.Join(netdir, fmt.Sprintf("%v-%v", account, suffix))
}

// DirtyAccountSyncer synces dirty accounts for cases where the updated
// information was not required to be immediately written to disk.  Accounts
// may be added to dirtyAccounts and will be checked and processed every 10
// seconds by this function.
//
// This never returns and is meant to be called from a goroutine.
func DirtyAccountSyncer() {
	ticker := time.Tick(10 * time.Second)
	for {
		select {
		case <-ticker:
			dirtyAccounts.Lock()
			for a := range dirtyAccounts.m {
				log.Debugf("Syncing account '%v' to disk",
					a.Wallet.Name())
				if err := a.writeDirtyToDisk(); err != nil {
					log.Errorf("cannot sync dirty wallet: %v",
						err)
				} else {
					delete(dirtyAccounts.m, a)
				}
			}
			dirtyAccounts.Unlock()
		}
	}
}

// freshDir creates a new directory specified by path if it does not
// exist.  If the directory already exists, all files contained in the
// directory are removed.
func freshDir(path string) error {
	if err := checkCreateDir(path); err != nil {
		return err
	}

	// Remove all files in the directory.
	fd, err := os.Open(path)
	if err != nil {
		return err
	}
	defer fd.Close()
	names, err := fd.Readdirnames(0)
	if err != nil {
		return err
	}
	for _, name := range names {
		if err := os.RemoveAll(name); err != nil {
			return err
		}
	}

	return nil
}

// writeAllToFreshDir writes all account files to the specified directory.
// If dir already exists, any old files are removed.  If dir does not
// exist, it is created.
//
// It is a runtime error to call this function while not holding each
// wallet, tx store, and utxo store writer lock.
func (a *Account) writeAllToFreshDir(dir string) error {
	if err := freshDir(dir); err != nil {
		return err
	}

	wfilepath := accountFilename("wallet.bin", a.name, dir)
	txfilepath := accountFilename("tx.bin", a.name, dir)
	utxofilepath := accountFilename("utxo.bin", a.name, dir)

	wfile, err := os.Create(wfilepath)
	if err != nil {
		return err
	}
	defer wfile.Close()
	txfile, err := os.Create(txfilepath)
	if err != nil {
		return err
	}
	defer txfile.Close()
	utxofile, err := os.Create(utxofilepath)
	if err != nil {
		return err
	}
	defer utxofile.Close()

	if _, err := a.Wallet.WriteTo(wfile); err != nil {
		return err
	}
	a.dirty = false

	if _, err := a.TxStore.s.WriteTo(txfile); err != nil {
		return err
	}
	a.TxStore.dirty = false

	if _, err := a.UtxoStore.s.WriteTo(utxofile); err != nil {
		return err
	}
	a.UtxoStore.dirty = false

	return nil
}

// writeDirtyToDisk checks for the dirty flag on an account's wallet,
// txstore, and utxostore, writing them to disk if any are dirty.
func (a *Account) writeDirtyToDisk() error {
	netdir := networkDir(cfg.Net())
	if err := checkCreateDir(netdir); err != nil {
		return err
	}

	wfilepath := accountFilename("wallet.bin", a.name, netdir)
	txfilepath := accountFilename("tx.bin", a.name, netdir)
	utxofilepath := accountFilename("utxo.bin", a.name, netdir)

	// UTXOs and transactions are synced to disk first.  This prevents
	// any races from saving a wallet marked to be synced with block N
	// and btcwallet closing while the UTXO and Tx files are only synced
	// with block N-1.

	// UTXOs
	a.UtxoStore.RLock()
	dirty := a.TxStore.dirty
	a.UtxoStore.RUnlock()
	if dirty {
		netdir, filename := filepath.Split(utxofilepath)
		tmpfile, err := ioutil.TempFile(netdir, filename)
		if err != nil {
			return err
		}
		defer tmpfile.Close()

		a.UtxoStore.RLock()
		_, err = a.UtxoStore.s.WriteTo(tmpfile)
		a.UtxoStore.RUnlock()
		if err != nil {
			return err
		}

		if err = Rename(tmpfile.Name(), utxofilepath); err != nil {
			return err
		}

		a.UtxoStore.Lock()
		a.UtxoStore.dirty = false
		a.UtxoStore.Unlock()
	}

	// Transactions
	a.TxStore.RLock()
	dirty = a.TxStore.dirty
	a.TxStore.RUnlock()
	if dirty {
		netdir, filename := filepath.Split(txfilepath)
		tmpfile, err := ioutil.TempFile(netdir, filename)
		if err != nil {
			return err
		}
		defer tmpfile.Close()

		a.TxStore.RLock()
		_, err = a.TxStore.s.WriteTo(tmpfile)
		a.TxStore.RUnlock()
		if err != nil {
			return err
		}

		if err = Rename(tmpfile.Name(), txfilepath); err != nil {
			return err
		}

		a.TxStore.Lock()
		a.TxStore.dirty = false
		a.TxStore.Unlock()
	}

	// Wallet
	a.mtx.RLock()
	dirty = a.dirty
	a.mtx.RUnlock()
	if dirty {
		netdir, filename := filepath.Split(wfilepath)
		tmpfile, err := ioutil.TempFile(netdir, filename)
		if err != nil {
			return err
		}
		defer tmpfile.Close()

		a.mtx.RLock()
		_, err = a.Wallet.WriteTo(tmpfile)
		a.mtx.RUnlock()
		if err != nil {
			return err
		}

		if err = Rename(tmpfile.Name(), wfilepath); err != nil {
			return err
		}

		a.mtx.Lock()
		a.dirty = false
		a.mtx.Unlock()
	}

	return nil
}

// WriteExport writes an account to a special export directory named
// by dirName.  Any previous files are overwritten.
func (a *Account) WriteExport(dirName string) error {
	exportPath := filepath.Join(networkDir(cfg.Net()), dirName)
	if err := checkCreateDir(exportPath); err != nil {
		return err
	}

	aname := a.Name()
	wfilepath := accountFilename("wallet.bin", aname, exportPath)
	txfilepath := accountFilename("tx.bin", aname, exportPath)
	utxofilepath := accountFilename("utxo.bin", aname, exportPath)

	a.UtxoStore.RLock()
	defer a.UtxoStore.RUnlock()
	utxofile, err := os.Create(utxofilepath)
	if err != nil {
		return err
	}
	defer utxofile.Close()
	if _, err := a.UtxoStore.s.WriteTo(utxofile); err != nil {
		return err
	}

	a.TxStore.RLock()
	defer a.TxStore.RUnlock()
	txfile, err := os.Create(txfilepath)
	if err != nil {
		return err
	}
	defer txfile.Close()
	if _, err := a.TxStore.s.WriteTo(txfile); err != nil {
		return err
	}

	a.mtx.RLock()
	defer a.mtx.RUnlock()
	wfile, err := os.Create(wfilepath)
	if err != nil {
		return err
	}
	defer wfile.Close()
	if _, err := a.Wallet.WriteTo(wfile); err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package cobra_test

import (
	. "cobra"
	"strings"
	"testing"
)

var tp, te, tt, t1 []string
var flagb1, flagb2, flagb3 bool
var flags1, flags2, flags3 string
var flagi1, flagi2, flagi3 int
var globalFlag1 bool
var flagEcho bool

var cmdPrint = &Command{
	Use:   "print [string to print]",
	Short: "Print anything to the screen",
	Long:  `an utterly useless command for testing.`,
	Run: func(cmd *Command, args []string) {
		tp = args
	},
}

var cmdEcho = &Command{
	Use:   "echo [string to echo]",
	Short: "Echo anything to the screen",
	Long:  `an utterly useless command for testing.`,
	Run: func(cmd *Command, args []string) {
		te = args
	},
}

var cmdTimes = &Command{
	Use:   "times [string to echo]",
	Short: "Echo anything to the screen more times",
	Long:  `an slightly useless command for testing.`,
	Run:   timesRunner,
}

func timesRunner(cmd *Command, args []string) {
	tt = args
}

func flagInit() {
	cmdEcho.ResetFlags()
	cmdPrint.ResetFlags()
	cmdTimes.ResetFlags()
	cmdEcho.Flags().IntVarP(&flagi1, "intone", "i", 123, "help message for flag intone")
	cmdTimes.Flags().IntVarP(&flagi2, "inttwo", "j", 234, "help message for flag inttwo")
	cmdPrint.Flags().IntVarP(&flagi3, "intthree", "i", 345, "help message for flag intthree")
	cmdEcho.PersistentFlags().StringVarP(&flags1, "strone", "s", "one", "help message for flag strone")
	cmdTimes.PersistentFlags().StringVarP(&flags2, "strtwo", "t", "two", "help message for flag strtwo")
	cmdPrint.PersistentFlags().StringVarP(&flags3, "strthree", "s", "three", "help message for flag strthree")
	cmdEcho.Flags().BoolVarP(&flagb1, "boolone", "b", true, "help message for flag boolone")
	cmdTimes.Flags().BoolVarP(&flagb2, "booltwo", "c", false, "help message for flag booltwo")
	cmdPrint.Flags().BoolVarP(&flagb3, "boolthree", "b", true, "help message for flag boolthree")
}

func commandInit() {
	cmdEcho.ResetCommands()
	cmdPrint.ResetCommands()
	cmdTimes.ResetCommands()
}

func initialize() *Commander {
	tt, tp, te = nil, nil, nil
	var c = NewCommander()
	c.SetName("cobra test")
	flagInit()
	commandInit()
	return c
}

func TestSingleCommand(t *testing.T) {
	c := initialize()
	c.AddCommand(cmdPrint, cmdEcho)
	c.SetArgs(strings.Split("print one two", " "))
	c.Execute()

	if te != nil || tt != nil {
		t.Error("Wrong command called")
	}
	if tp == nil {
		t.Error("Wrong command called")
	}
	if strings.Join(tp, " ") != "one two" {
		t.Error("Command didn't parse correctly")
	}
}

func TestChildCommand(t *testing.T) {
	c := initialize()
	cmdEcho.AddCommand(cmdTimes)
	c.AddCommand(cmdPrint, cmdEcho)
	c.SetArgs(strings.Split("echo times one two", " "))
	c.Execute()

	if te != nil || tp != nil {
		t.Error("Wrong command called")
	}
	if tt == nil {
		t.Error("Wrong command called")
	}
	if strings.Join(tt, " ") != "one two" {
		t.Error("Command didn't parse correctly")
	}
}

func TestFlagLong(t *testing.T) {
	c := initialize()
	c.AddCommand(cmdPrint, cmdEcho, cmdTimes)
	c.SetArgs(strings.Split("echo --intone=13 something here", " "))
	c.Execute()

	if strings.Join(te, " ") != "something here" {
		t.Errorf("flags didn't leave proper args remaining..%s given", te)
	}
	if flagi1 != 13 {
		t.Errorf("int flag didn't get correct value, had %d", flagi1)
	}
	if flagi2 != 234 {
		t.Errorf("default flag value changed, 234 expected, %d given", flagi2)
	}
}

func TestFlagShort(t *testing.T) {
	c := initialize()
	c.AddCommand(cmdPrint, cmdEcho, cmdTimes)
	c.SetArgs(strings.Split("echo -i13 something here", " "))
	c.Execute()

	if strings.Join(te, " ") != "something here" {
		t.Errorf("flags didn't leave proper args remaining..%s given", te)
	}
	if flagi1 != 13 {
		t.Errorf("int flag didn't get correct value, had %d", flagi1)
	}
	if flagi2 != 234 {
		t.Errorf("default flag value changed, 234 expected, %d given", flagi2)
	}

	c = initialize()
	c.AddCommand(cmdPrint, cmdEcho, cmdTimes)
	c.SetArgs(strings.Split("echo -i 13 something here", " "))
	c.Execute()

	if strings.Join(te, " ") != "something here" {
		t.Errorf("flags didn't leave proper args remaining..%s given", te)
	}
	if flagi1 != 13 {
		t.Errorf("int flag didn't get correct value, had %d", flagi1)
	}
	if flagi2 != 234 {
		t.Errorf("default flag value changed, 234 expected, %d given", flagi2)
	}

	// Testing same shortcode, different command
	c = initialize()
	c.AddCommand(cmdPrint, cmdEcho, cmdTimes)
	c.SetArgs(strings.Split("print -i99 one two", " "))
	c.Execute()

	if strings.Join(tp, " ") != "one two" {
		t.Errorf("flags didn't leave proper args remaining..%s given", tp)
	}
	if flagi3 != 99 {
		t.Errorf("int flag didn't get correct value, had %d", flagi3)
	}
	if flagi1 != 123 {
		t.Errorf("default flag value changed on different comamnd with same shortname, 234 expected, %d given", flagi2)
	}
}

func TestChildCommandFlags(t *testing.T) {
	c := initialize()
	cmdEcho.AddCommand(cmdTimes)
	c.AddCommand(cmdPrint, cmdEcho)
	c.SetArgs(strings.Split("echo times -j 99 one two", " "))
	c.Execute()

	if strings.Join(tt, " ") != "one two" {
		t.Errorf("flags didn't leave proper args remaining..%s given", tt)
	}

	//c = initialize()
	//cmdEcho.AddCommand(cmdTimes)
	//c.AddCommand(cmdPrint, cmdEcho)
	//c.SetArgs(strings.Split("echo times -j 99 -i 77 one two", " "))
	//c.Execute()

	//if strings.Join(tt, " ") != "one two" {
	//t.Errorf("flags didn't leave proper args remaining..%s given", tt)
	//}
}

func TestPersistentFlags(t *testing.T) {
	c := initialize()
	cmdEcho.AddCommand(cmdTimes)
	c.AddCommand(cmdPrint, cmdEcho)
	flagInit()
	c.SetArgs(strings.Split("echo -s something more here", " "))
	c.Execute()

	// persistentFlag should act like normal flag on it's own command
	if strings.Join(te, " ") != "more here" {
		t.Errorf("flags didn't leave proper args remaining..%s given", te)
	}

	// persistentFlag should act like normal flag on it's own command
	if flags1 != "something" {
		t.Errorf("string flag didn't get correct value, had %v", flags1)
	}

}
<commit_msg>Testing when given missing flag<commit_after>package cobra_test

import (
	. "cobra"
	"fmt"
	"strings"
	"testing"
)

var _ = fmt.Println

var tp, te, tt, t1 []string
var flagb1, flagb2, flagb3 bool
var flags1, flags2, flags3 string
var flagi1, flagi2, flagi3 int
var globalFlag1 bool
var flagEcho bool

var cmdPrint = &Command{
	Use:   "print [string to print]",
	Short: "Print anything to the screen",
	Long:  `an utterly useless command for testing.`,
	Run: func(cmd *Command, args []string) {
		tp = args
	},
}

var cmdEcho = &Command{
	Use:   "echo [string to echo]",
	Short: "Echo anything to the screen",
	Long:  `an utterly useless command for testing.`,
	Run: func(cmd *Command, args []string) {
		te = args
	},
}

var cmdTimes = &Command{
	Use:   "times [string to echo]",
	Short: "Echo anything to the screen more times",
	Long:  `an slightly useless command for testing.`,
	Run:   timesRunner,
}

func timesRunner(cmd *Command, args []string) {
	tt = args
}

func flagInit() {
	cmdEcho.ResetFlags()
	cmdPrint.ResetFlags()
	cmdTimes.ResetFlags()
	cmdEcho.Flags().IntVarP(&flagi1, "intone", "i", 123, "help message for flag intone")
	cmdTimes.Flags().IntVarP(&flagi2, "inttwo", "j", 234, "help message for flag inttwo")
	cmdPrint.Flags().IntVarP(&flagi3, "intthree", "i", 345, "help message for flag intthree")
	cmdEcho.PersistentFlags().StringVarP(&flags1, "strone", "s", "one", "help message for flag strone")
	cmdTimes.PersistentFlags().StringVarP(&flags2, "strtwo", "t", "two", "help message for flag strtwo")
	cmdPrint.PersistentFlags().StringVarP(&flags3, "strthree", "s", "three", "help message for flag strthree")
	cmdEcho.Flags().BoolVarP(&flagb1, "boolone", "b", true, "help message for flag boolone")
	cmdTimes.Flags().BoolVarP(&flagb2, "booltwo", "c", false, "help message for flag booltwo")
	cmdPrint.Flags().BoolVarP(&flagb3, "boolthree", "b", true, "help message for flag boolthree")
}

func commandInit() {
	cmdEcho.ResetCommands()
	cmdPrint.ResetCommands()
	cmdTimes.ResetCommands()
}

func initialize() *Commander {
	tt, tp, te = nil, nil, nil
	var c = NewCommander()
	c.SetName("cobra test")
	flagInit()
	commandInit()
	return c
}

func TestSingleCommand(t *testing.T) {
	c := initialize()
	c.AddCommand(cmdPrint, cmdEcho)
	c.SetArgs(strings.Split("print one two", " "))
	c.Execute()

	if te != nil || tt != nil {
		t.Error("Wrong command called")
	}
	if tp == nil {
		t.Error("Wrong command called")
	}
	if strings.Join(tp, " ") != "one two" {
		t.Error("Command didn't parse correctly")
	}
}

func TestChildCommand(t *testing.T) {
	c := initialize()
	cmdEcho.AddCommand(cmdTimes)
	c.AddCommand(cmdPrint, cmdEcho)
	c.SetArgs(strings.Split("echo times one two", " "))
	c.Execute()

	if te != nil || tp != nil {
		t.Error("Wrong command called")
	}
	if tt == nil {
		t.Error("Wrong command called")
	}
	if strings.Join(tt, " ") != "one two" {
		t.Error("Command didn't parse correctly")
	}
}

func TestFlagLong(t *testing.T) {
	c := initialize()
	c.AddCommand(cmdPrint, cmdEcho, cmdTimes)
	c.SetArgs(strings.Split("echo --intone=13 something here", " "))
	c.Execute()

	if strings.Join(te, " ") != "something here" {
		t.Errorf("flags didn't leave proper args remaining..%s given", te)
	}
	if flagi1 != 13 {
		t.Errorf("int flag didn't get correct value, had %d", flagi1)
	}
	if flagi2 != 234 {
		t.Errorf("default flag value changed, 234 expected, %d given", flagi2)
	}
}

func TestFlagShort(t *testing.T) {
	c := initialize()
	c.AddCommand(cmdPrint, cmdEcho, cmdTimes)
	c.SetArgs(strings.Split("echo -i13 something here", " "))
	c.Execute()

	if strings.Join(te, " ") != "something here" {
		t.Errorf("flags didn't leave proper args remaining..%s given", te)
	}
	if flagi1 != 13 {
		t.Errorf("int flag didn't get correct value, had %d", flagi1)
	}
	if flagi2 != 234 {
		t.Errorf("default flag value changed, 234 expected, %d given", flagi2)
	}

	c = initialize()
	c.AddCommand(cmdPrint, cmdEcho, cmdTimes)
	c.SetArgs(strings.Split("echo -i 13 something here", " "))
	c.Execute()

	if strings.Join(te, " ") != "something here" {
		t.Errorf("flags didn't leave proper args remaining..%s given", te)
	}
	if flagi1 != 13 {
		t.Errorf("int flag didn't get correct value, had %d", flagi1)
	}
	if flagi2 != 234 {
		t.Errorf("default flag value changed, 234 expected, %d given", flagi2)
	}

	// Testing same shortcode, different command
	c = initialize()
	c.AddCommand(cmdPrint, cmdEcho, cmdTimes)
	c.SetArgs(strings.Split("print -i99 one two", " "))
	c.Execute()

	if strings.Join(tp, " ") != "one two" {
		t.Errorf("flags didn't leave proper args remaining..%s given", tp)
	}
	if flagi3 != 99 {
		t.Errorf("int flag didn't get correct value, had %d", flagi3)
	}
	if flagi1 != 123 {
		t.Errorf("default flag value changed on different command with same shortname, 234 expected, %d given", flagi2)
	}
}

func TestChildCommandFlags(t *testing.T) {
	c := initialize()
	cmdEcho.AddCommand(cmdTimes)
	c.AddCommand(cmdPrint, cmdEcho)
	c.SetArgs(strings.Split("echo times -j 99 one two", " "))
	c.Execute()

	if strings.Join(tt, " ") != "one two" {
		t.Errorf("flags didn't leave proper args remaining..%s given", tt)
	}

	// Testing with flag that shouldn't be persistent
	c = initialize()
	cmdEcho.AddCommand(cmdTimes)
	c.AddCommand(cmdPrint, cmdEcho)
	c.SetArgs(strings.Split("echo times -j 99 -i77 one two", " "))
	e := c.Execute()

	if e == nil {
		t.Errorf("invalid flag should generate error")
	}

	if flagi2 != 99 {
		t.Errorf("flag value should be 99, %d given", flagi2)
	}

	if flagi1 != 123 {
		t.Errorf("unset flag should have default value, expecting 123, given %d", flagi1)
	}
}

func TestPersistentFlags(t *testing.T) {
	c := initialize()
	cmdEcho.AddCommand(cmdTimes)
	c.AddCommand(cmdPrint, cmdEcho)
	c.SetArgs(strings.Split("echo -s something more here", " "))
	c.Execute()

	// persistentFlag should act like normal flag on it's own command
	if strings.Join(te, " ") != "more here" {
		t.Errorf("flags didn't leave proper args remaining..%s given", te)
	}

	// persistentFlag should act like normal flag on it's own command
	if flags1 != "something" {
		t.Errorf("string flag didn't get correct value, had %v", flags1)
	}

}
<|endoftext|>


<commit_before>package libvirt

import (
	"encoding/xml"
	"fmt"
	"log"
	"strconv"
	"time"
	//"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
	//libvirt "gopkg.in/alexzorin/libvirt-go.v2"
	libvirt "github.com/dmacvicar/libvirt-go"
)

func resourceLibvirtDomain() *schema.Resource {
	return &schema.Resource{
		Create: resourceLibvirtDomainCreate,
		Read:   resourceLibvirtDomainRead,
		Update: resourceLibvirtDomainUpdate,
		Delete: resourceLibvirtDomainDelete,
		Schema: map[string]*schema.Schema{
			"name": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},
			"vcpu": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Default:  1,
			},
			"memory": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Default:  512,
			},
			"disk": &schema.Schema{
				Type:     schema.TypeList,
				Optional: true,
				Required: false,
				ForceNew: true,
				Elem: &schema.Resource{
					Schema: diskCommonSchema(),
				},
			},
		},
	}
}

func resourceLibvirtDomainCreate(d *schema.ResourceData, meta interface{}) error {
	virConn := meta.(*Client).libvirt
	if virConn == nil {
		return fmt.Errorf("The libvirt connection was nil.")
	}

	disksCount := d.Get("disk.#").(int)
	disks := make([]defDisk, 0, disksCount)
	for i := 0; i < disksCount; i++ {
		prefix := fmt.Sprintf("disk.%d", i)
		disk := newDefDisk()

		volumeKey := d.Get(prefix + ".volume_id").(string)
		diskVolume, err := virConn.LookupStorageVolByKey(volumeKey)
		if err != nil {
			return fmt.Errorf("Can't retrieve volume %s", volumeKey)
		}
		diskVolumeName, err := diskVolume.GetName()
		if err != nil {
			return fmt.Errorf("Error retrieving volume name: %s", err)
		}
		diskPool, err := diskVolume.LookupPoolByVolume()
		if err != nil {
			return fmt.Errorf("Error retrieving pool for volume: %s", err)
		}
		diskPoolName, err := diskPool.GetName()
		if err != nil {
			return fmt.Errorf("Error retrieving pool name: %s", err)
		}

		disk.Source.Volume = diskVolumeName
	    disk.Source.Pool = diskPoolName

		disks = append(disks, disk)
	}

	domainDef := newDomainDef()
	domainDef.Name = d.Get("name").(string)
	domainDef.Memory.Amount = d.Get("memory").(int)
	domainDef.VCpu.Amount = d.Get("vcpu").(int)
	domainDef.Devices.Disks = disks

	connectURI, err := virConn.GetURI()
	if err != nil {
		return fmt.Errorf("Error retrieving libvirt connection URI: %s", err)
	}
	log.Printf("[INFO] Creating libvirt domain at %s", connectURI)

	data, err := xml.Marshal(domainDef)
	if err != nil {
		return fmt.Errorf("Error serializing libvirt domain: %s", err)
	}

	domain, err := virConn.DomainCreateXML(string(data), libvirt.VIR_DOMAIN_NONE)
	if err != nil {
		return fmt.Errorf("Error crearing libvirt domain: %s", err)
	}

	id, err := domain.GetID()
	if err != nil {
		return fmt.Errorf("Error retrieving libvirt domain id: %s", err)
	}
	d.SetId(fmt.Sprintf("%d", id))

	log.Printf("[INFO] Domain ID: %s", d.Id())

	err = waitForDomainUp(domain)
	if err != nil {
		return fmt.Errorf("Error waiting for domain to reach RUNNING state: %s", err)
	}

	return resourceLibvirtDomainRead(d, meta)
}

func resourceLibvirtDomainRead(d *schema.ResourceData, meta interface{}) error {
	virConn := meta.(*Client).libvirt
	if virConn == nil {
		return fmt.Errorf("The libvirt connection was nil.")
	}

	id, err := strconv.Atoi(d.Id())
	if err != nil {
		return fmt.Errorf("Not a valid ID, must be an integer: %s", err)
	}

	domain, err := virConn.LookupDomainById(uint32(id))
	if err != nil {
		return fmt.Errorf("Error retrieving libvirt domain: %s", err)
	}

	name, err := domain.GetName()
	if err != nil {
		return fmt.Errorf("Error retrieving libvirt domain name: %s", err)
	}
	d.Set("name", name)

	return nil
}

func resourceLibvirtDomainUpdate(d *schema.ResourceData, meta interface{}) error {
	return fmt.Errorf("Couldn't update libvirt domain")
}

func resourceLibvirtDomainDelete(d *schema.ResourceData, meta interface{}) error {
	virConn := meta.(*Client).libvirt
	if virConn == nil {
		return fmt.Errorf("The libvirt connection was nil.")
	}

	id, err := strconv.Atoi(d.Id())
	if err != nil {
		return fmt.Errorf("Not a valid ID, must be an integer: %s", err)
	}

	domain, err := virConn.LookupDomainById(uint32(id))
	if err != nil {
		return fmt.Errorf("Error retrieving libvirt domain: %s", err)
	}

	if err := domain.Destroy(); err != nil {
		return fmt.Errorf("Couldn't destroy libvirt domain: %s", err)
	}

	return nil
}

// wait for domain to be up and timeout after 5 minutes.
func waitForDomainUp(domain libvirt.VirDomain) error {
	start := time.Now()
	for {
		state, err := domain.GetState()
		if err != nil {
			return err
		}

		running := true
		if state[0] != libvirt.VIR_DOMAIN_RUNNING {
			running = false
		}

		if running {
			return nil
		}
		time.Sleep(1 * time.Second)
		if time.Since(start) > 5*time.Minute {
			return fmt.Errorf("Domain didn't switch to state RUNNING in 5 minutes")
		}
	}
}
<commit_msg>implement resourceLibvirtDomainRead for real<commit_after>package libvirt

import (
	"encoding/xml"
	"fmt"
	"log"
	"strconv"
	"time"
	//"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
	//libvirt "gopkg.in/alexzorin/libvirt-go.v2"
	libvirt "github.com/dmacvicar/libvirt-go"
)

func resourceLibvirtDomain() *schema.Resource {
	return &schema.Resource{
		Create: resourceLibvirtDomainCreate,
		Read:   resourceLibvirtDomainRead,
		Update: resourceLibvirtDomainUpdate,
		Delete: resourceLibvirtDomainDelete,
		Schema: map[string]*schema.Schema{
			"name": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},
			"vcpu": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Default:  1,
			},
			"memory": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Default:  512,
			},
			"disk": &schema.Schema{
				Type:     schema.TypeList,
				Optional: true,
				Required: false,
				ForceNew: true,
				Elem: &schema.Resource{
					Schema: diskCommonSchema(),
				},
			},
		},
	}
}

func resourceLibvirtDomainCreate(d *schema.ResourceData, meta interface{}) error {
	virConn := meta.(*Client).libvirt
	if virConn == nil {
		return fmt.Errorf("The libvirt connection was nil.")
	}

	disksCount := d.Get("disk.#").(int)
	disks := make([]defDisk, 0, disksCount)
	for i := 0; i < disksCount; i++ {
		prefix := fmt.Sprintf("disk.%d", i)
		disk := newDefDisk()

		volumeKey := d.Get(prefix + ".volume_id").(string)
		diskVolume, err := virConn.LookupStorageVolByKey(volumeKey)
		if err != nil {
			return fmt.Errorf("Can't retrieve volume %s", volumeKey)
		}
		diskVolumeName, err := diskVolume.GetName()
		if err != nil {
			return fmt.Errorf("Error retrieving volume name: %s", err)
		}
		diskPool, err := diskVolume.LookupPoolByVolume()
		if err != nil {
			return fmt.Errorf("Error retrieving pool for volume: %s", err)
		}
		diskPoolName, err := diskPool.GetName()
		if err != nil {
			return fmt.Errorf("Error retrieving pool name: %s", err)
		}

		disk.Source.Volume = diskVolumeName
	    disk.Source.Pool = diskPoolName

		disks = append(disks, disk)
	}

	domainDef := newDomainDef()
	domainDef.Name = d.Get("name").(string)
	domainDef.Memory.Amount = d.Get("memory").(int)
	domainDef.VCpu.Amount = d.Get("vcpu").(int)
	domainDef.Devices.Disks = disks

	connectURI, err := virConn.GetURI()
	if err != nil {
		return fmt.Errorf("Error retrieving libvirt connection URI: %s", err)
	}
	log.Printf("[INFO] Creating libvirt domain at %s", connectURI)

	data, err := xml.Marshal(domainDef)
	if err != nil {
		return fmt.Errorf("Error serializing libvirt domain: %s", err)
	}

	domain, err := virConn.DomainCreateXML(string(data), libvirt.VIR_DOMAIN_NONE)
	if err != nil {
		return fmt.Errorf("Error crearing libvirt domain: %s", err)
	}

	id, err := domain.GetID()
	if err != nil {
		return fmt.Errorf("Error retrieving libvirt domain id: %s", err)
	}
	d.SetId(fmt.Sprintf("%d", id))

	log.Printf("[INFO] Domain ID: %s", d.Id())

	err = waitForDomainUp(domain)
	if err != nil {
		return fmt.Errorf("Error waiting for domain to reach RUNNING state: %s", err)
	}

	return resourceLibvirtDomainRead(d, meta)
}

func resourceLibvirtDomainRead(d *schema.ResourceData, meta interface{}) error {
	virConn := meta.(*Client).libvirt
	if virConn == nil {
		return fmt.Errorf("The libvirt connection was nil.")
	}

	id, err := strconv.Atoi(d.Id())
	if err != nil {
		return fmt.Errorf("Not a valid ID, must be an integer: %s", err)
	}

	domain, err := virConn.LookupDomainById(uint32(id))
	if err != nil {
		return fmt.Errorf("Error retrieving libvirt domain: %s", err)
	}

	xmlDesc, err := domain.GetXMLDesc(0)
	if err != nil {
		return fmt.Errorf("Error retrieving libvirt domain XML description: %s", err)
	}

	domainDef := newDomainDef()
	err = xml.Unmarshal([]byte(xmlDesc), &domainDef)
	if err != nil {
		return fmt.Errorf("Error reading libvirt domain XML description: %s", err)
	}

	d.Set("name", domainDef.Name)
	d.Set("vpu", domainDef.VCpu)
	d.Set("memory", domainDef.Memory)

	disks := make([]map[string]interface{}, 0)
	for _, diskDef := range domainDef.Devices.Disks {
		virPool, err := virConn.LookupStoragePoolByName(diskDef.Source.Pool)
		if err != nil {
			return fmt.Errorf("Error retrieving pool for disk: %s", err)
		}

		virVol, err := virPool.LookupStorageVolByName(diskDef.Source.Volume)
		if err != nil {
			return fmt.Errorf("Error retrieving volume for disk: %s", err)
		}

		virVolKey, err := virVol.GetKey()
		if err != nil {
			return fmt.Errorf("Error retrieving volume ke for disk: %s", err)
		}

		disk := map[string]interface{}{
			"volume_id": virVolKey,
		}
		disks = append(disks, disk)
	}

	d.Set("disks", disks)
	return nil
}

func resourceLibvirtDomainUpdate(d *schema.ResourceData, meta interface{}) error {
	return fmt.Errorf("Couldn't update libvirt domain")
}

func resourceLibvirtDomainDelete(d *schema.ResourceData, meta interface{}) error {
	virConn := meta.(*Client).libvirt
	if virConn == nil {
		return fmt.Errorf("The libvirt connection was nil.")
	}

	id, err := strconv.Atoi(d.Id())
	if err != nil {
		return fmt.Errorf("Not a valid ID, must be an integer: %s", err)
	}

	domain, err := virConn.LookupDomainById(uint32(id))
	if err != nil {
		return fmt.Errorf("Error retrieving libvirt domain: %s", err)
	}

	if err := domain.Destroy(); err != nil {
		return fmt.Errorf("Couldn't destroy libvirt domain: %s", err)
	}

	return nil
}

// wait for domain to be up and timeout after 5 minutes.
func waitForDomainUp(domain libvirt.VirDomain) error {
	start := time.Now()
	for {
		state, err := domain.GetState()
		if err != nil {
			return err
		}

		running := true
		if state[0] != libvirt.VIR_DOMAIN_RUNNING {
			running = false
		}

		if running {
			return nil
		}
		time.Sleep(1 * time.Second)
		if time.Since(start) > 5*time.Minute {
			return fmt.Errorf("Domain didn't switch to state RUNNING in 5 minutes")
		}
	}
}
<|endoftext|>


<commit_before>package parse

import (
	"bufio"
	"fmt"
	"io"
	"unicode"
)

// Pos is a position in source text.
type Pos struct {
	Name   string
	Offset int
	Line   int
	Col    int
}

func (p *Pos) Copy() *Pos {
	var p2 Pos
	p2 = *p
	return &p2
}

func (p *Pos) String() string {
	return fmt.Sprintf("%s:%d:%d", p.Name, p.Line, p.Col)
}

func (p *Pos) FormatError(tag string, msg string) error {
	return fmt.Errorf("%s error at %s: %s", tag, p, msg)
}

// A token is a single lexeme produced by the scanner.
type token struct {
	typ tokType
	pos *Pos
	val string
}

func (t token) AsError() error {
	if t.typ != tokError {
		panic("AsError called on non-error token")
	}
	return t.pos.FormatError("lex", t.val)
}

type tokType int

const (
	tokEOF tokType = iota

	tokApostrophe   // '
	tokAtSign       // @
	tokBacktick     // `
	tokBool         // true, false
	tokCharLiteral  // \c, \newline, etc
	tokCircumflex   // ^
	tokComment      // ; foobar
	tokDispatch     // any dispatch macro token: #{, #(, #_, etc. Does not include tags.
	tokKeyword      // :foo
	tokLeftBrace    // {
	tokLeftBracket  // [
	tokLeftParen    // (
	tokNil          // nil
	tokNumber       // any numeric literal; may be invalid (parser will determine)
	tokOctothorpe   // # (only used for tags; dispatch tokens are separate)
	tokRightBrace   // }
	tokRightBracket // ]
	tokRightParen   // )
	tokString       // string literal (java escapes)
	tokSymbol       // foo, also lambda args (%, %N)
	tokTilde        // ~
	tokNewline

	tokError // error; val is the error text
)

var tokTypeToName = map[tokType]string{
	tokApostrophe:   "apostrophe",
	tokAtSign:       "at-sign",
	tokBacktick:     "backtick",
	tokBool:         "bool",
	tokCharLiteral:  "char-literal",
	tokCircumflex:   "circumflex",
	tokComment:      "comment",
	tokDispatch:     "dispatch",
	tokEOF:          "eof",
	tokError:        "error",
	tokKeyword:      "keyword",
	tokLeftBrace:    "left-brace",
	tokLeftBracket:  "left-bracket",
	tokLeftParen:    "left-paren",
	tokNil:          "nil",
	tokNumber:       "number",
	tokOctothorpe:   "octothorpe",
	tokRightBrace:   "right-brace",
	tokRightBracket: "right-bracket",
	tokRightParen:   "right-paren",
	tokString:       "string",
	tokSymbol:       "symbol",
	tokTilde:        "tilde",
	tokNewline:      "newline",
}

func (t tokType) String() string {
	name, ok := tokTypeToName[t]
	if !ok {
		panic("bad token type")
	}
	return name
}

func (t token) String() string {
	switch t.typ {
	case tokError, tokBool, tokCharLiteral, tokComment, tokKeyword, tokNumber, tokDispatch, tokString, tokSymbol:
		return fmt.Sprintf("<%s@%s>(%q)", t.typ, t.pos, t.val)
	}
	return fmt.Sprintf("<%s@%s>", t.typ, t.pos)
}

// lexer holds the state of the scanner. A single rune of backup is supported.
type lexer struct {
	name    string // the name of the input source
	input   *bufio.Reader
	pos     *Pos // the current position in the input
	start   *Pos // the start position of the token being scanned
	lastPos *Pos // the position before the most recent next() call
	tokens  chan token
	val     []rune // the literal contents of the token
}

func lex(name string, input *bufio.Reader) *lexer {
	l := &lexer{
		name:   name,
		input:  input,
		pos:    &Pos{Name: name, Line: 1, Col: 1},
		start:  &Pos{Name: name, Line: 1, Col: 1},
		tokens: make(chan token),
	}
	go l.run()
	return l
}

type inputReadErr struct {
	err error
}

func (l *lexer) next() (r rune, eof bool) {
	r, w, err := l.input.ReadRune()
	if err != nil {
		if err == io.EOF {
			return 0, true
		}
		panic(inputReadErr{err})
	}
	l.lastPos = l.pos.Copy()
	l.pos.Offset += w
	l.pos.Col += w
	if r == '\n' {
		l.pos.Line++
		l.pos.Col = 1
	}
	l.val = append(l.val, r)
	return r, false
}

func (l *lexer) back() {
	if l.lastPos == nil {
		panic("back() call not preceded by a next()")
	}
	if err := l.input.UnreadRune(); err != nil {
		panic("should not happen")
	}
	l.pos = l.lastPos
	l.val = l.val[:len(l.val)-1]
	l.lastPos = nil
}

// scanWhile scans while f(current rune) is true. It does not include the first value for which the predicate
// returns false.
func (l *lexer) scanWhile(f func(r rune) bool) {
	for {
		r, eof := l.next()
		if eof {
			return
		}
		if !f(r) {
			l.back()
			return
		}
	}
}

// scanUntil scans until a rune in set is reached (or EOF). It does not consume the discovered rune.
func (l *lexer) scanUntil(set string) {
	runes := []rune(set)
	for {
		r, eof := l.next()
		if eof {
			return
		}
		for _, r2 := range runes {
			if r == r2 {
				l.back()
				return
			}
		}
	}
}

func (l *lexer) emit(typ tokType) {
	l.tokens <- token{typ, l.start, string(l.val)}
	l.skip()
}

func (l *lexer) skip() {
	l.start = l.pos.Copy()
	l.val = l.val[:0]
}

func (l *lexer) synth(typ tokType, val string) {
	l.tokens <- token{typ, l.start, val}
}

func (l *lexer) nextToken() token {
	return <-l.tokens
}

func (l *lexer) errorf(format string, args ...interface{}) stateFn {
	l.tokens <- token{tokError, l.start, fmt.Sprintf(format, args...)}
	return nil
}

func (l *lexer) scanError(err error) stateFn {
	l.tokens <- token{tokError, l.start, fmt.Sprintf("error while scanning: %s", err)}
	return nil
}

func (l *lexer) eof() stateFn {
	l.emit(tokEOF)
	return nil
}

// stateFn represents a single state in the scanner.
type stateFn func(*lexer) stateFn

func (l *lexer) run() {
	defer func() {
		if e := recover(); e != nil {
			if e2, ok := e.(inputReadErr); ok {
				l.scanError(e2.err)
				return
			}
			panic(e)
		}
	}()

	for state := lexOuter; state != nil; state = state(l) {
	}
	close(l.tokens)
}

func lexOuter(l *lexer) stateFn {
	r, eof := l.next()
	if eof {
		return l.eof()
	}

	switch r {
	case ';':
		return lexComment
	case '"':
		return lexString
	case '\\':
		return lexCharLiteral
	case ':':
		return lexKeyword
	case '%':
		// Symbols can only begin with %; not allowed in the middle.
		return lexSymbol
	case '#':
		return lexDispatch
	case '+', '-':
		r2, eof := l.next()
		if eof {
			l.emit(tokSymbol)
			return l.eof()
		}
		l.back()
		if r2 >= '0' && r2 <= '9' {
			return lexNumber
		}
		return lexSymbol
	}

	// Recognize single-char tokens
	switch r {
	case '\'':
		l.emit(tokApostrophe)
	case '@':
		l.emit(tokAtSign)
	case '`':
		l.emit(tokBacktick)
	case '^':
		l.emit(tokCircumflex)
	case '{':
		l.emit(tokLeftBrace)
	case '[':
		l.emit(tokLeftBracket)
	case '(':
		l.emit(tokLeftParen)
	case '}':
		l.emit(tokRightBrace)
	case ']':
		l.emit(tokRightBracket)
	case ')':
		l.emit(tokRightParen)
	case '~':
		l.emit(tokTilde)
	case '\n':
		l.emit(tokNewline)
	default:
		goto afterSingles
	}
	return lexOuter
afterSingles:

	switch {
	case isWhitespace(r):
		return lexWhitespace
	case r >= '0' && r <= '9':
		return lexNumber
	case isSymbolChar(r):
		return lexSymbol
	}
	return l.errorf("unrecognized token starting with %c", r)
}

func lexWhitespace(l *lexer) stateFn {
	l.scanWhile(isWhitespace)
	l.skip()
	return lexOuter
}

func lexComment(l *lexer) stateFn {
	l.scanUntil("\n")
	l.emit(tokComment)
	return lexOuter
}

func lexString(l *lexer) stateFn {
	escaped := false
	for {
		r, eof := l.next()
		if eof {
			return l.errorf("reached EOF before string closing quote")
		}
		switch r {
		case '"':
			if !escaped {
				l.emit(tokString)
				return lexOuter
			}
			escaped = false
		case '\\':
			escaped = !escaped
		default:
			escaped = false
		}
	}
}

func lexCharLiteral(l *lexer) stateFn {
	_, eof := l.next()
	if eof {
		return l.errorf("invalid character literal")
	}
	l.scanWhile(isSymbolChar)
	l.emit(tokCharLiteral)
	return lexOuter
}

func lexKeyword(l *lexer) stateFn {
	l.scanWhile(isSymbolChar)
	l.emit(tokKeyword)
	return lexOuter
}

func lexDispatch(l *lexer) stateFn {
	// Dispatch is tricky. '#foo" and '# foo' are both interpeted as the tag 'foo'.
	// However, '# _' is not interpreted as the ignore macro -- it is the tag '_'.
	// (So the whitespace matters when tokenizing a dispatch macro.)
	// Here's how we navigate this:
	//
	// If it's a tag, we'll emit an octothorpe token and move on (the subsequent symbol is the tag value).
	//
	// If it's a paired delimiter dispatch form -- #{...}, #(...), or #"..." -- the dispatch token we emit
	// will have two chars. The second char will be repeated in the following token.
	// (for instance, "#{1}" will be tokenized as "#{", "{", "1", "}".
	//
	// Otherwise, the dispatch token is two chars and the following token is distinct.
	r, eof := l.next()
	if eof {
		l.emit(tokOctothorpe)
		return nil
	}
	val := string(l.val)
	switch r {
	case '{', '(', '"':
		l.back()
		l.skip()
		l.synth(tokDispatch, val)
		return lexOuter
	case '\'', '_':
		l.skip()
		l.synth(tokDispatch, val)
		return lexOuter
	default:
		l.back()
		l.skip()
		l.emit(tokOctothorpe)
	}
	return lexOuter
}

func lexNumber(l *lexer) stateFn {
	// There are many different chars that can appear in a number, but it is a subset of symbol chars. Tokenize
	// this way to match the behavior of the clojure compiler. For example: '(+ 3foo)' produces the invalid
	// number '3foo' rather than parsing the same way as '(+ 3 foo)'.
	l.scanWhile(isSymbolChar)
	l.emit(tokNumber)
	return lexOuter
}

func lexSymbol(l *lexer) stateFn {
	l.scanWhile(isSymbolChar)
	l.emit(tokSymbol)
	return lexOuter
}

func isWhitespace(r rune) bool {
	return (unicode.IsSpace(r) && r != '\n') || r == ','
}

// Decent approximation for now
func isSymbolChar(r rune) bool {
	if unicode.IsLetter(r) || unicode.IsDigit(r) {
		return true
	}
	switch r {
	case '*', '+', '!', '-', '_', '?', '/', '.', ':', '$', '=', '>', '<', '&', '#':
		return true
	}
	return false
}

//func isNumberChar(r rune) bool {
//// There are many ways to write number literals in Clojure. Read http://clojure.org/reader and
//// http://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html for more information.
////
//// Notes: the following are supported in Java but not Clojure:
//// - Binary literals (0b101)
//// - f/F/d/D suffixes
//// - Underscores inside numbers

//if r >= '0' && r <= '9' {
//return true
//}
//switch r {
//case '.':
//return true
//case '+', '-': // signs as a prefix or on the exponent
//return true
//case 'e', 'E': // exponent
//return true
//case 'r': // specify radix
//return true
//case 'M', 'N': // suffixes for BigDecimal or BigInt
//return true
//case '/': // ratios
//return true
//case 'x': // hex
//return true
//}
//return false
//}
<commit_msg>[parse] Misc cleanup<commit_after>package parse

import (
	"bufio"
	"fmt"
	"io"
	"unicode"
)

// Pos is a position in source text.
type Pos struct {
	Name   string
	Offset int
	Line   int
	Col    int
}

func (p *Pos) Copy() *Pos {
	var p2 Pos
	p2 = *p
	return &p2
}

func (p *Pos) String() string {
	return fmt.Sprintf("%s:%d:%d", p.Name, p.Line, p.Col)
}

func (p *Pos) FormatError(tag string, msg string) error {
	return fmt.Errorf("%s error at %s: %s", tag, p, msg)
}

// A token is a single lexeme produced by the scanner.
type token struct {
	typ tokType
	pos *Pos
	val string
}

func (t token) AsError() error {
	if t.typ != tokError {
		panic("AsError called on non-error token")
	}
	return t.pos.FormatError("lex", t.val)
}

type tokType int

const (
	tokEOF tokType = iota

	tokApostrophe   // '
	tokAtSign       // @
	tokBacktick     // `
	tokCharLiteral  // \c, \newline, etc
	tokCircumflex   // ^
	tokComment      // ; foobar
	tokDispatch     // any dispatch macro token: #{, #(, #_, etc. Does not include tags.
	tokKeyword      // :foo
	tokLeftBrace    // {
	tokLeftBracket  // [
	tokLeftParen    // (
	tokNumber       // any numeric literal; may be invalid (parser will determine)
	tokOctothorpe   // # (only used for tags; dispatch tokens are separate)
	tokRightBrace   // }
	tokRightBracket // ]
	tokRightParen   // )
	tokString       // string literal (java escapes)
	tokSymbol       // foo, also lambda args (%, %N)
	tokTilde        // ~
	tokNewline

	tokError // error; val is the error text
)

var tokTypeToName = map[tokType]string{
	tokApostrophe:   "apostrophe",
	tokAtSign:       "at-sign",
	tokBacktick:     "backtick",
	tokCharLiteral:  "char-literal",
	tokCircumflex:   "circumflex",
	tokComment:      "comment",
	tokDispatch:     "dispatch",
	tokEOF:          "eof",
	tokError:        "error",
	tokKeyword:      "keyword",
	tokLeftBrace:    "left-brace",
	tokLeftBracket:  "left-bracket",
	tokLeftParen:    "left-paren",
	tokNumber:       "number",
	tokOctothorpe:   "octothorpe",
	tokRightBrace:   "right-brace",
	tokRightBracket: "right-bracket",
	tokRightParen:   "right-paren",
	tokString:       "string",
	tokSymbol:       "symbol",
	tokTilde:        "tilde",
	tokNewline:      "newline",
}

func (t tokType) String() string {
	name, ok := tokTypeToName[t]
	if !ok {
		panic("bad token type")
	}
	return name
}

func (t token) String() string {
	switch t.typ {
	case tokError, tokCharLiteral, tokComment, tokKeyword, tokNumber, tokDispatch, tokString, tokSymbol:
		return fmt.Sprintf("<%s@%s>(%q)", t.typ, t.pos, t.val)
	}
	return fmt.Sprintf("<%s@%s>", t.typ, t.pos)
}

// lexer holds the state of the scanner. A single rune of backup is supported.
type lexer struct {
	name    string // the name of the input source
	input   *bufio.Reader
	pos     *Pos // the current position in the input
	start   *Pos // the start position of the token being scanned
	lastPos *Pos // the position before the most recent next() call
	tokens  chan token
	val     []rune // the literal contents of the token
}

func lex(name string, input *bufio.Reader) *lexer {
	l := &lexer{
		name:   name,
		input:  input,
		pos:    &Pos{Name: name, Line: 1, Col: 1},
		start:  &Pos{Name: name, Line: 1, Col: 1},
		tokens: make(chan token),
	}
	go l.run()
	return l
}

type inputReadErr struct {
	err error
}

func (l *lexer) next() (r rune, eof bool) {
	r, w, err := l.input.ReadRune()
	if err != nil {
		if err == io.EOF {
			return 0, true
		}
		panic(inputReadErr{err})
	}
	l.lastPos = l.pos.Copy()
	l.pos.Offset += w
	l.pos.Col += w
	if r == '\n' {
		l.pos.Line++
		l.pos.Col = 1
	}
	l.val = append(l.val, r)
	return r, false
}

func (l *lexer) back() {
	if l.lastPos == nil {
		panic("back() call not preceded by a next()")
	}
	if err := l.input.UnreadRune(); err != nil {
		panic("should not happen")
	}
	l.pos = l.lastPos
	l.val = l.val[:len(l.val)-1]
	l.lastPos = nil
}

// scanWhile scans while f(current rune) is true. It does not include the first value for which the predicate
// returns false.
func (l *lexer) scanWhile(f func(r rune) bool) {
	for {
		r, eof := l.next()
		if eof {
			return
		}
		if !f(r) {
			l.back()
			return
		}
	}
}

// scanUntil scans until a rune in set is reached (or EOF). It does not consume the discovered rune.
func (l *lexer) scanUntil(set string) {
	runes := []rune(set)
	for {
		r, eof := l.next()
		if eof {
			return
		}
		for _, r2 := range runes {
			if r == r2 {
				l.back()
				return
			}
		}
	}
}

func (l *lexer) emit(typ tokType) {
	l.tokens <- token{typ, l.start, string(l.val)}
	l.skip()
}

func (l *lexer) skip() {
	l.start = l.pos.Copy()
	l.val = l.val[:0]
}

func (l *lexer) synth(typ tokType, val string) {
	l.tokens <- token{typ, l.start, val}
}

func (l *lexer) nextToken() token {
	return <-l.tokens
}

func (l *lexer) errorf(format string, args ...interface{}) stateFn {
	l.tokens <- token{tokError, l.start, fmt.Sprintf(format, args...)}
	return nil
}

func (l *lexer) scanError(err error) stateFn {
	l.tokens <- token{tokError, l.start, fmt.Sprintf("error while scanning: %s", err)}
	return nil
}

func (l *lexer) eof() stateFn {
	l.emit(tokEOF)
	return nil
}

// stateFn represents a single state in the scanner.
type stateFn func(*lexer) stateFn

func (l *lexer) run() {
	defer func() {
		if e := recover(); e != nil {
			if e2, ok := e.(inputReadErr); ok {
				l.scanError(e2.err)
				return
			}
			panic(e)
		}
	}()

	for state := lexOuter; state != nil; state = state(l) {
	}
	close(l.tokens)
}

func lexOuter(l *lexer) stateFn {
	r, eof := l.next()
	if eof {
		return l.eof()
	}

	switch r {
	case ';':
		return lexComment
	case '"':
		return lexString
	case '\\':
		return lexCharLiteral
	case ':':
		return lexKeyword
	case '%':
		// Symbols can only begin with %; not allowed in the middle.
		return lexSymbol
	case '#':
		return lexDispatch
	case '+', '-':
		r2, eof := l.next()
		if eof {
			l.emit(tokSymbol)
			return l.eof()
		}
		l.back()
		if r2 >= '0' && r2 <= '9' {
			return lexNumber
		}
		return lexSymbol
	}

	// Recognize single-char tokens
	switch r {
	case '\'':
		l.emit(tokApostrophe)
	case '@':
		l.emit(tokAtSign)
	case '`':
		l.emit(tokBacktick)
	case '^':
		l.emit(tokCircumflex)
	case '{':
		l.emit(tokLeftBrace)
	case '[':
		l.emit(tokLeftBracket)
	case '(':
		l.emit(tokLeftParen)
	case '}':
		l.emit(tokRightBrace)
	case ']':
		l.emit(tokRightBracket)
	case ')':
		l.emit(tokRightParen)
	case '~':
		l.emit(tokTilde)
	case '\n':
		l.emit(tokNewline)
	default:
		goto afterSingles
	}
	return lexOuter
afterSingles:

	switch {
	case isWhitespace(r):
		return lexWhitespace
	case r >= '0' && r <= '9':
		return lexNumber
	case isSymbolChar(r):
		return lexSymbol
	}
	return l.errorf("unrecognized token starting with %c", r)
}

func lexWhitespace(l *lexer) stateFn {
	l.scanWhile(isWhitespace)
	l.skip()
	return lexOuter
}

func lexComment(l *lexer) stateFn {
	l.scanUntil("\n")
	l.emit(tokComment)
	return lexOuter
}

func lexString(l *lexer) stateFn {
	escaped := false
	for {
		r, eof := l.next()
		if eof {
			return l.errorf("reached EOF before string closing quote")
		}
		switch r {
		case '"':
			if !escaped {
				l.emit(tokString)
				return lexOuter
			}
			escaped = false
		case '\\':
			escaped = !escaped
		default:
			escaped = false
		}
	}
}

func lexCharLiteral(l *lexer) stateFn {
	_, eof := l.next()
	if eof {
		return l.errorf("invalid character literal")
	}
	l.scanWhile(isSymbolChar)
	l.emit(tokCharLiteral)
	return lexOuter
}

func lexKeyword(l *lexer) stateFn {
	l.scanWhile(isSymbolChar)
	l.emit(tokKeyword)
	return lexOuter
}

func lexDispatch(l *lexer) stateFn {
	// Dispatch is tricky. '#foo" and '# foo' are both interpeted as the tag 'foo'.
	// However, '# _' is not interpreted as the ignore macro -- it is the tag '_'.
	// (So the whitespace matters when tokenizing a dispatch macro.)
	// Here's how we navigate this:
	//
	// If it's a tag, we'll emit an octothorpe token and move on (the subsequent symbol is the tag value).
	//
	// If it's a paired delimiter dispatch form -- #{...}, #(...), or #"..." -- the dispatch token we emit
	// will have two chars. The second char will be repeated in the following token.
	// (for instance, "#{1}" will be tokenized as "#{", "{", "1", "}".
	//
	// Otherwise, the dispatch token is two chars and the following token is distinct.
	r, eof := l.next()
	if eof {
		l.emit(tokOctothorpe)
		return nil
	}
	val := string(l.val)
	switch r {
	case '{', '(', '"':
		l.back()
		l.skip()
		l.synth(tokDispatch, val)
		return lexOuter
	case '\'', '_':
		l.skip()
		l.synth(tokDispatch, val)
		return lexOuter
	default:
		l.back()
		l.skip()
		l.emit(tokOctothorpe)
	}
	return lexOuter
}

func lexNumber(l *lexer) stateFn {
	// There are many different chars that can appear in a number, but it is a subset of symbol chars. Tokenize
	// this way to match the behavior of the clojure compiler. For example: '(+ 3foo)' produces the invalid
	// number '3foo' rather than parsing the same way as '(+ 3 foo)'.
	l.scanWhile(isSymbolChar)
	l.emit(tokNumber)
	return lexOuter
}

func lexSymbol(l *lexer) stateFn {
	l.scanWhile(isSymbolChar)
	l.emit(tokSymbol)
	return lexOuter
}

func isWhitespace(r rune) bool {
	return (unicode.IsSpace(r) && r != '\n') || r == ','
}

// Decent approximation for now
func isSymbolChar(r rune) bool {
	if unicode.IsLetter(r) || unicode.IsDigit(r) {
		return true
	}
	switch r {
	case '*', '+', '!', '-', '_', '?', '/', '.', ':', '$', '=', '>', '<', '&', '#':
		return true
	}
	return false
}
<|endoftext|>