<commit_before>// Copyright 2018 Google LLC 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 tarball

import (
	"archive/tar"
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"sync"

	"github.com/google/go-containerregistry/internal/gzip"
	"github.com/google/go-containerregistry/pkg/name"
	v1 "github.com/google/go-containerregistry/pkg/v1"
	"github.com/google/go-containerregistry/pkg/v1/partial"
	"github.com/google/go-containerregistry/pkg/v1/types"
)

type image struct {
	opener        Opener
	manifest      *Manifest
	config        []byte
	imgDescriptor *Descriptor

	tag *name.Tag
}

type uncompressedImage struct {
	*image
}

type compressedImage struct {
	*image
	manifestLock sync.Mutex // Protects manifest
	manifest     *v1.Manifest
}

var _ partial.UncompressedImageCore = (*uncompressedImage)(nil)
var _ partial.CompressedImageCore = (*compressedImage)(nil)

// Opener is a thunk for opening a tar file.
type Opener func() (io.ReadCloser, error)

func pathOpener(path string) Opener {
	return func() (io.ReadCloser, error) {
		return os.Open(path)
	}
}

// ImageFromPath returns a v1.Image from a tarball located on path.
func ImageFromPath(path string, tag *name.Tag) (v1.Image, error) {
	return Image(pathOpener(path), tag)
}

// Image exposes an image from the tarball at the provided path.
func Image(opener Opener, tag *name.Tag) (v1.Image, error) {
	img := &image{
		opener: opener,
		tag:    tag,
	}
	if err := img.loadTarDescriptorAndConfig(); err != nil {
		return nil, err
	}

	// Peek at the first layer and see if it's compressed.
	if len(img.imgDescriptor.Layers) > 0 {
		compressed, err := img.areLayersCompressed()
		if err != nil {
			return nil, err
		}
		if compressed {
			c := compressedImage{
				image: img,
			}
			return partial.CompressedToImage(&c)
		}
	}

	uc := uncompressedImage{
		image: img,
	}
	return partial.UncompressedToImage(&uc)
}

func (i *image) MediaType() (types.MediaType, error) {
	return types.DockerManifestSchema2, nil
}

// Descriptor stores the manifest data for a single image inside a `docker save` tarball.
type Descriptor struct {
	Config   string
	RepoTags []string
	Layers   []string

	// Tracks foreign layer info. Key is DiffID.
	LayerSources map[v1.Hash]v1.Descriptor `json:",omitempty"`
}

// Manifest represents the manifests of all images as the `manifest.json` file in a `docker save` tarball.
type Manifest []Descriptor

// LoadManifest load manifest
func LoadManifest(opener Opener) (Manifest, error) {
	m, err := opener()
	if err != nil {
		return nil, err
	}
	defer m.Close()

	var manifest Manifest

	if err := json.NewDecoder(m).Decode(&manifest); err != nil {
		return nil, err
	}
	return manifest, nil
}

func (m Manifest) findDescriptor(tag *name.Tag) (*Descriptor, error) {
	if tag == nil {
		if len(m) != 1 {
			return nil, errors.New("tarball must contain only a single image to be used with tarball.Image")
		}
		return &(m)[0], nil
	}
	for _, img := range m {
		for _, tagStr := range img.RepoTags {
			repoTag, err := name.NewTag(tagStr)
			if err != nil {
				return nil, err
			}

			// Compare the resolved names, since there are several ways to specify the same tag.
			if repoTag.Name() == tag.Name() {
				return &img, nil
			}
		}
	}
	return nil, fmt.Errorf("tag %s not found in tarball", tag)
}

func (i *image) areLayersCompressed() (bool, error) {
	if len(i.imgDescriptor.Layers) == 0 {
		return false, errors.New("0 layers found in image")
	}
	layer := i.imgDescriptor.Layers[0]
	blob, err := extractFileFromTar(i.opener, layer)
	if err != nil {
		return false, err
	}
	defer blob.Close()
	return gzip.Is(blob)
}

func (i *image) loadTarDescriptorAndConfig() error {
	m, err := extractFileFromTar(i.opener, "manifest.json")
	if err != nil {
		return err
	}
	defer m.Close()

	if err := json.NewDecoder(m).Decode(&i.manifest); err != nil {
		return err
	}

	if i.manifest == nil {
		return errors.New("no valid manifest.json in tarball")
	}

	i.imgDescriptor, err = i.manifest.findDescriptor(i.tag)
	if err != nil {
		return err
	}

	cfg, err := extractFileFromTar(i.opener, i.imgDescriptor.Config)
	if err != nil {
		return err
	}
	defer cfg.Close()

	i.config, err = ioutil.ReadAll(cfg)
	if err != nil {
		return err
	}
	return nil
}

func (i *image) RawConfigFile() ([]byte, error) {
	return i.config, nil
}

// tarFile represents a single file inside a tar. Closing it closes the tar itself.
type tarFile struct {
	io.Reader
	io.Closer
}

func extractFileFromTar(opener Opener, filePath string) (io.ReadCloser, error) {
	f, err := opener()
	if err != nil {
		return nil, err
	}
	close := true
	defer func() {
		if close {
			f.Close()
		}
	}()

	tf := tar.NewReader(f)
	for {
		hdr, err := tf.Next()
		if err == io.EOF {
			break
		}
		if err != nil {
			return nil, err
		}
		if hdr.Name == filePath {
			if hdr.Typeflag == tar.TypeSymlink || hdr.Typeflag == tar.TypeLink {
				currentDir := filepath.Dir(filePath)
				return extractFileFromTar(opener, path.Join(currentDir, hdr.Linkname))
			}
			close = false
			return tarFile{
				Reader: tf,
				Closer: f,
			}, nil
		}
	}
	return nil, fmt.Errorf("file %s not found in tar", filePath)
}

// uncompressedLayerFromTarball implements partial.UncompressedLayer
type uncompressedLayerFromTarball struct {
	diffID    v1.Hash
	mediaType types.MediaType
	opener    Opener
	filePath  string
}

// foreignUncompressedLayer implements partial.UncompressedLayer but returns
// a custom descriptor. This allows the foreign layer URLs to be included in
// the generated image manifest for uncompressed layers.
type foreignUncompressedLayer struct {
	uncompressedLayerFromTarball
	desc v1.Descriptor
}

func (fl *foreignUncompressedLayer) Descriptor() (*v1.Descriptor, error) {
	return &fl.desc, nil
}

// DiffID implements partial.UncompressedLayer
func (ulft *uncompressedLayerFromTarball) DiffID() (v1.Hash, error) {
	return ulft.diffID, nil
}

// Uncompressed implements partial.UncompressedLayer
func (ulft *uncompressedLayerFromTarball) Uncompressed() (io.ReadCloser, error) {
	return extractFileFromTar(ulft.opener, ulft.filePath)
}

func (ulft *uncompressedLayerFromTarball) MediaType() (types.MediaType, error) {
	return ulft.mediaType, nil
}

func (i *uncompressedImage) LayerByDiffID(h v1.Hash) (partial.UncompressedLayer, error) {
	cfg, err := partial.ConfigFile(i)
	if err != nil {
		return nil, err
	}
	for idx, diffID := range cfg.RootFS.DiffIDs {
		if diffID == h {
			// Technically the media type should be 'application/tar' but given that our
			// v1.Layer doesn't force consumers to care about whether the layer is compressed
			// we should be fine returning the DockerLayer media type
			mt := types.DockerLayer
			if bd, ok := i.imgDescriptor.LayerSources[h]; ok {
				// Overwrite the mediaType for foreign layers.
				return &foreignUncompressedLayer{
					uncompressedLayerFromTarball: uncompressedLayerFromTarball{
						diffID:    diffID,
						mediaType: bd.MediaType,
						opener:    i.opener,
						filePath:  i.imgDescriptor.Layers[idx],
					},
					desc: bd,
				}, nil
			}
			return &uncompressedLayerFromTarball{
				diffID:    diffID,
				mediaType: mt,
				opener:    i.opener,
				filePath:  i.imgDescriptor.Layers[idx],
			}, nil
		}
	}
	return nil, fmt.Errorf("diff id %q not found", h)
}

func (c *compressedImage) Manifest() (*v1.Manifest, error) {
	c.manifestLock.Lock()
	defer c.manifestLock.Unlock()
	if c.manifest != nil {
		return c.manifest, nil
	}

	b, err := c.RawConfigFile()
	if err != nil {
		return nil, err
	}

	cfgHash, cfgSize, err := v1.SHA256(bytes.NewReader(b))
	if err != nil {
		return nil, err
	}

	c.manifest = &v1.Manifest{
		SchemaVersion: 2,
		MediaType:     types.DockerManifestSchema2,
		Config: v1.Descriptor{
			MediaType: types.DockerConfigJSON,
			Size:      cfgSize,
			Digest:    cfgHash,
		},
	}

	for i, p := range c.imgDescriptor.Layers {
		cfg, err := partial.ConfigFile(c)
		if err != nil {
			return nil, err
		}
		diffid := cfg.RootFS.DiffIDs[i]
		if d, ok := c.imgDescriptor.LayerSources[diffid]; ok {
			// If it's a foreign layer, just append the descriptor so we can avoid
			// reading the entire file.
			c.manifest.Layers = append(c.manifest.Layers, d)
		} else {
			l, err := extractFileFromTar(c.opener, p)
			if err != nil {
				return nil, err
			}
			defer l.Close()
			sha, size, err := v1.SHA256(l)
			if err != nil {
				return nil, err
			}
			c.manifest.Layers = append(c.manifest.Layers, v1.Descriptor{
				MediaType: types.DockerLayer,
				Size:      size,
				Digest:    sha,
			})
		}
	}
	return c.manifest, nil
}

func (c *compressedImage) RawManifest() ([]byte, error) {
	return partial.RawManifest(c)
}

// compressedLayerFromTarball implements partial.CompressedLayer
type compressedLayerFromTarball struct {
	desc     v1.Descriptor
	opener   Opener
	filePath string
}

// Digest implements partial.CompressedLayer
func (clft *compressedLayerFromTarball) Digest() (v1.Hash, error) {
	return clft.desc.Digest, nil
}

// Compressed implements partial.CompressedLayer
func (clft *compressedLayerFromTarball) Compressed() (io.ReadCloser, error) {
	return extractFileFromTar(clft.opener, clft.filePath)
}

// MediaType implements partial.CompressedLayer
func (clft *compressedLayerFromTarball) MediaType() (types.MediaType, error) {
	return clft.desc.MediaType, nil
}

// Size implements partial.CompressedLayer
func (clft *compressedLayerFromTarball) Size() (int64, error) {
	return clft.desc.Size, nil
}

func (c *compressedImage) LayerByDigest(h v1.Hash) (partial.CompressedLayer, error) {
	m, err := c.Manifest()
	if err != nil {
		return nil, err
	}
	for i, l := range m.Layers {
		if l.Digest == h {
			fp := c.imgDescriptor.Layers[i]
			return &compressedLayerFromTarball{
				desc:     l,
				opener:   c.opener,
				filePath: fp,
			}, nil
		}
	}
	return nil, fmt.Errorf("blob %v not found", h)
}
<commit_msg>Revert "export manifest for tar file (#1033)" (#1043)<commit_after>// Copyright 2018 Google LLC 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 tarball

import (
	"archive/tar"
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"sync"

	"github.com/google/go-containerregistry/internal/gzip"
	"github.com/google/go-containerregistry/pkg/name"
	v1 "github.com/google/go-containerregistry/pkg/v1"
	"github.com/google/go-containerregistry/pkg/v1/partial"
	"github.com/google/go-containerregistry/pkg/v1/types"
)

type image struct {
	opener        Opener
	manifest      *Manifest
	config        []byte
	imgDescriptor *Descriptor

	tag *name.Tag
}

type uncompressedImage struct {
	*image
}

type compressedImage struct {
	*image
	manifestLock sync.Mutex // Protects manifest
	manifest     *v1.Manifest
}

var _ partial.UncompressedImageCore = (*uncompressedImage)(nil)
var _ partial.CompressedImageCore = (*compressedImage)(nil)

// Opener is a thunk for opening a tar file.
type Opener func() (io.ReadCloser, error)

func pathOpener(path string) Opener {
	return func() (io.ReadCloser, error) {
		return os.Open(path)
	}
}

// ImageFromPath returns a v1.Image from a tarball located on path.
func ImageFromPath(path string, tag *name.Tag) (v1.Image, error) {
	return Image(pathOpener(path), tag)
}

// Image exposes an image from the tarball at the provided path.
func Image(opener Opener, tag *name.Tag) (v1.Image, error) {
	img := &image{
		opener: opener,
		tag:    tag,
	}
	if err := img.loadTarDescriptorAndConfig(); err != nil {
		return nil, err
	}

	// Peek at the first layer and see if it's compressed.
	if len(img.imgDescriptor.Layers) > 0 {
		compressed, err := img.areLayersCompressed()
		if err != nil {
			return nil, err
		}
		if compressed {
			c := compressedImage{
				image: img,
			}
			return partial.CompressedToImage(&c)
		}
	}

	uc := uncompressedImage{
		image: img,
	}
	return partial.UncompressedToImage(&uc)
}

func (i *image) MediaType() (types.MediaType, error) {
	return types.DockerManifestSchema2, nil
}

// Descriptor stores the manifest data for a single image inside a `docker save` tarball.
type Descriptor struct {
	Config   string
	RepoTags []string
	Layers   []string

	// Tracks foreign layer info. Key is DiffID.
	LayerSources map[v1.Hash]v1.Descriptor `json:",omitempty"`
}

// Manifest represents the manifests of all images as the `manifest.json` file in a `docker save` tarball.
type Manifest []Descriptor

func (m Manifest) findDescriptor(tag *name.Tag) (*Descriptor, error) {
	if tag == nil {
		if len(m) != 1 {
			return nil, errors.New("tarball must contain only a single image to be used with tarball.Image")
		}
		return &(m)[0], nil
	}
	for _, img := range m {
		for _, tagStr := range img.RepoTags {
			repoTag, err := name.NewTag(tagStr)
			if err != nil {
				return nil, err
			}

			// Compare the resolved names, since there are several ways to specify the same tag.
			if repoTag.Name() == tag.Name() {
				return &img, nil
			}
		}
	}
	return nil, fmt.Errorf("tag %s not found in tarball", tag)
}

func (i *image) areLayersCompressed() (bool, error) {
	if len(i.imgDescriptor.Layers) == 0 {
		return false, errors.New("0 layers found in image")
	}
	layer := i.imgDescriptor.Layers[0]
	blob, err := extractFileFromTar(i.opener, layer)
	if err != nil {
		return false, err
	}
	defer blob.Close()
	return gzip.Is(blob)
}

func (i *image) loadTarDescriptorAndConfig() error {
	m, err := extractFileFromTar(i.opener, "manifest.json")
	if err != nil {
		return err
	}
	defer m.Close()

	if err := json.NewDecoder(m).Decode(&i.manifest); err != nil {
		return err
	}

	if i.manifest == nil {
		return errors.New("no valid manifest.json in tarball")
	}

	i.imgDescriptor, err = i.manifest.findDescriptor(i.tag)
	if err != nil {
		return err
	}

	cfg, err := extractFileFromTar(i.opener, i.imgDescriptor.Config)
	if err != nil {
		return err
	}
	defer cfg.Close()

	i.config, err = ioutil.ReadAll(cfg)
	if err != nil {
		return err
	}
	return nil
}

func (i *image) RawConfigFile() ([]byte, error) {
	return i.config, nil
}

// tarFile represents a single file inside a tar. Closing it closes the tar itself.
type tarFile struct {
	io.Reader
	io.Closer
}

func extractFileFromTar(opener Opener, filePath string) (io.ReadCloser, error) {
	f, err := opener()
	if err != nil {
		return nil, err
	}
	close := true
	defer func() {
		if close {
			f.Close()
		}
	}()

	tf := tar.NewReader(f)
	for {
		hdr, err := tf.Next()
		if err == io.EOF {
			break
		}
		if err != nil {
			return nil, err
		}
		if hdr.Name == filePath {
			if hdr.Typeflag == tar.TypeSymlink || hdr.Typeflag == tar.TypeLink {
				currentDir := filepath.Dir(filePath)
				return extractFileFromTar(opener, path.Join(currentDir, hdr.Linkname))
			}
			close = false
			return tarFile{
				Reader: tf,
				Closer: f,
			}, nil
		}
	}
	return nil, fmt.Errorf("file %s not found in tar", filePath)
}

// uncompressedLayerFromTarball implements partial.UncompressedLayer
type uncompressedLayerFromTarball struct {
	diffID    v1.Hash
	mediaType types.MediaType
	opener    Opener
	filePath  string
}

// foreignUncompressedLayer implements partial.UncompressedLayer but returns
// a custom descriptor. This allows the foreign layer URLs to be included in
// the generated image manifest for uncompressed layers.
type foreignUncompressedLayer struct {
	uncompressedLayerFromTarball
	desc v1.Descriptor
}

func (fl *foreignUncompressedLayer) Descriptor() (*v1.Descriptor, error) {
	return &fl.desc, nil
}

// DiffID implements partial.UncompressedLayer
func (ulft *uncompressedLayerFromTarball) DiffID() (v1.Hash, error) {
	return ulft.diffID, nil
}

// Uncompressed implements partial.UncompressedLayer
func (ulft *uncompressedLayerFromTarball) Uncompressed() (io.ReadCloser, error) {
	return extractFileFromTar(ulft.opener, ulft.filePath)
}

func (ulft *uncompressedLayerFromTarball) MediaType() (types.MediaType, error) {
	return ulft.mediaType, nil
}

func (i *uncompressedImage) LayerByDiffID(h v1.Hash) (partial.UncompressedLayer, error) {
	cfg, err := partial.ConfigFile(i)
	if err != nil {
		return nil, err
	}
	for idx, diffID := range cfg.RootFS.DiffIDs {
		if diffID == h {
			// Technically the media type should be 'application/tar' but given that our
			// v1.Layer doesn't force consumers to care about whether the layer is compressed
			// we should be fine returning the DockerLayer media type
			mt := types.DockerLayer
			if bd, ok := i.imgDescriptor.LayerSources[h]; ok {
				// Overwrite the mediaType for foreign layers.
				return &foreignUncompressedLayer{
					uncompressedLayerFromTarball: uncompressedLayerFromTarball{
						diffID:    diffID,
						mediaType: bd.MediaType,
						opener:    i.opener,
						filePath:  i.imgDescriptor.Layers[idx],
					},
					desc: bd,
				}, nil
			}
			return &uncompressedLayerFromTarball{
				diffID:    diffID,
				mediaType: mt,
				opener:    i.opener,
				filePath:  i.imgDescriptor.Layers[idx],
			}, nil
		}
	}
	return nil, fmt.Errorf("diff id %q not found", h)
}

func (c *compressedImage) Manifest() (*v1.Manifest, error) {
	c.manifestLock.Lock()
	defer c.manifestLock.Unlock()
	if c.manifest != nil {
		return c.manifest, nil
	}

	b, err := c.RawConfigFile()
	if err != nil {
		return nil, err
	}

	cfgHash, cfgSize, err := v1.SHA256(bytes.NewReader(b))
	if err != nil {
		return nil, err
	}

	c.manifest = &v1.Manifest{
		SchemaVersion: 2,
		MediaType:     types.DockerManifestSchema2,
		Config: v1.Descriptor{
			MediaType: types.DockerConfigJSON,
			Size:      cfgSize,
			Digest:    cfgHash,
		},
	}

	for i, p := range c.imgDescriptor.Layers {
		cfg, err := partial.ConfigFile(c)
		if err != nil {
			return nil, err
		}
		diffid := cfg.RootFS.DiffIDs[i]
		if d, ok := c.imgDescriptor.LayerSources[diffid]; ok {
			// If it's a foreign layer, just append the descriptor so we can avoid
			// reading the entire file.
			c.manifest.Layers = append(c.manifest.Layers, d)
		} else {
			l, err := extractFileFromTar(c.opener, p)
			if err != nil {
				return nil, err
			}
			defer l.Close()
			sha, size, err := v1.SHA256(l)
			if err != nil {
				return nil, err
			}
			c.manifest.Layers = append(c.manifest.Layers, v1.Descriptor{
				MediaType: types.DockerLayer,
				Size:      size,
				Digest:    sha,
			})
		}
	}
	return c.manifest, nil
}

func (c *compressedImage) RawManifest() ([]byte, error) {
	return partial.RawManifest(c)
}

// compressedLayerFromTarball implements partial.CompressedLayer
type compressedLayerFromTarball struct {
	desc     v1.Descriptor
	opener   Opener
	filePath string
}

// Digest implements partial.CompressedLayer
func (clft *compressedLayerFromTarball) Digest() (v1.Hash, error) {
	return clft.desc.Digest, nil
}

// Compressed implements partial.CompressedLayer
func (clft *compressedLayerFromTarball) Compressed() (io.ReadCloser, error) {
	return extractFileFromTar(clft.opener, clft.filePath)
}

// MediaType implements partial.CompressedLayer
func (clft *compressedLayerFromTarball) MediaType() (types.MediaType, error) {
	return clft.desc.MediaType, nil
}

// Size implements partial.CompressedLayer
func (clft *compressedLayerFromTarball) Size() (int64, error) {
	return clft.desc.Size, nil
}

func (c *compressedImage) LayerByDigest(h v1.Hash) (partial.CompressedLayer, error) {
	m, err := c.Manifest()
	if err != nil {
		return nil, err
	}
	for i, l := range m.Layers {
		if l.Digest == h {
			fp := c.imgDescriptor.Layers[i]
			return &compressedLayerFromTarball{
				desc:     l,
				opener:   c.opener,
				filePath: fp,
			}, nil
		}
	}
	return nil, fmt.Errorf("blob %v not found", h)
}
<|endoftext|>


<commit_before>package command

import (
	"bufio"
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"sort"
	"strings"

	flags "github.com/jessevdk/go-flags"
	"github.com/ondevice/ondevice/api"
	"github.com/ondevice/ondevice/logg"
)

type DeviceOpts struct {
	Yes bool `long:"yes" short:"y" description:"Confirm deletion noninteractively"`
}

func deviceRun(args []string) int {
	var opts DeviceOpts
	var err error

	if args, err = flags.ParseArgs(&opts, args); err != nil {
		logg.Fatal(err)
	}

	if len(args) < 1 {
		err = errors.New("missing deviceId")
	} else if len(args) < 2 {
		err = errors.New("missing device command")
	} else if args[1] == "set" {
		err = deviceSetProperties(args[0], args[2:])
	} else if args[1] == "rm" {
		err = deviceRemoveProperties(args[0], args[2:], opts)
	} else if args[1] == "props" || args[1] == "properties" || args[1] == "list" {
		err = deviceListProperties(args[0])
	} else {
		err = fmt.Errorf("Unknown device command: '%s'", args[1])
	}

	if err != nil {
		logg.Fatal("Error: ", err)
		return 1
	}
	return 0
}

func deviceListProperties(devID string) error {
	return _printProperties(api.ListProperties(devID))
}

func deviceRemoveProperties(devID string, args []string, opts DeviceOpts) error {
	if len(args) == 0 {
		logg.Error("Too few arguments")
	}

	// check if the user wants to delete the device ("on:id" present)
	var wantsDelete = false
	for _, key := range args {
		if key == "on:id" {
			wantsDelete = true
			break
		}
	}

	if wantsDelete {
		if len(args) != 1 {
			logg.Fatal("To delete a device, remove its 'on:id' property (and nothing else)")
		}

		var confirmed = opts.Yes
		if !confirmed {
			var reader = bufio.NewReader(os.Stdin)
			var input string
			var err error

			for input == "" {
				fmt.Printf("Do you really want to delete the device '%s' (y/N): ", devID)
				input, err = reader.ReadString('\n')
				if err != nil {
					logg.Fatal(err)
				}

				switch strings.TrimSpace(strings.ToLower(input)) {
				case "y", "yes":
					confirmed = true
				case "n", "no", "":
					confirmed = false
					input = "no"
				default:
					input = ""
				}
			}
		}

		if confirmed {
			if err := api.DeleteDevice(devID); err != nil {
				return err
			}
		} else {
			return errors.New("Aborted delete")
		}
		return nil
	}

	return _printProperties(api.RemoveProperties(devID, args))
}

func deviceSetProperties(devID string, args []string) error {
	var props = make(map[string]string)

	for _, arg := range args {
		s := strings.SplitN(arg, "=", 2)
		if _, ok := props[s[0]]; ok {
			return fmt.Errorf("Duplicate value for property '%s'", s[0])
		}
		props[s[0]] = s[1]
	}

	return _printProperties(api.SetProperties(devID, props))
}

func _printProperties(props map[string]interface{}, err error) error {
	if err != nil {
		return err
	}

	// get list of keys and sort them
	var keys = make([]string, 0, len(props))
	for k := range props {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	for _, k := range keys {
		var v = props[k]
		var repr string

		if s, ok := v.(string); ok {
			repr = s
		} else {
			var reprBytes, _ = json.Marshal(v)
			repr = string(reprBytes)
		}

		fmt.Printf("%s=%s\n", k, repr)
	}

	return nil
}

// DeviceCommand -- implemnts `ondevice device`
var DeviceCommand = BaseCommand{
	Arguments: "<devId> <props/set/rm> [key1=val1 ...]",
	ShortHelp: "List/manipulate device properties",
	RunFn:     deviceRun,
	LongHelp: `$ ondevice device <devId> props
$ ondevice device <devId> set [key1=val1 ...]
$ ondevice device <devId> rm [--yes/-y] [--delete] [key1 key2 ...]

This command allows you to change all your devices' properties.
It requires a client key with the 'manage' authorization.

Properties can be used to keep track of your devices, to manage their characteristics,
keep tracks of running maintenance scripts, etc.

- ondevice device $devId props
  lists that device's properties, one per line, as 'key=value' pairs
- ondevice device $devId set [key=val...]
  sets one or more device properties, again as 'key=value' pairs
- ondevice device $devId rm [key ...]
  removes one or more device properties by name

Some special cases are:
- ondevice device $devId set on:id=newId
  Rename (= change devId of) a device
- ondevice device $devId rm on:id
  Removing the special property 'on:id' will attempt to delete the device
  (will ask for confirmation unless you also specify --yes)
  Only devices that have been offline for at least an hour can be deleted.

Options:
--yes -y
  Don't ask before deleting a device

Each invocation will print the resulting property list.

Examples:
  $ ondevice device q5dkpm props
  $ ondevice device q5dkpm set test=1234 foo=bar
  test=1234
  foo=bar
  $ ondevice device q5dkpm rm foo
  test=1234

  # rename and then delete the device
  $ ondevice device q5dkpm set on:id=rpi
  $ ondevice device rpi rm on:id
  Do you really want to delete the device 'uyqsn4' (y/N):
`,
}
<commit_msg>slightly restructured command.deviceRun()<commit_after>package command

import (
	"bufio"
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"sort"
	"strings"

	flags "github.com/jessevdk/go-flags"
	"github.com/ondevice/ondevice/api"
	"github.com/ondevice/ondevice/logg"
)

type DeviceOpts struct {
	Yes bool `long:"yes" short:"y" description:"Confirm deletion noninteractively"`
}

func deviceRun(args []string) int {
	var opts DeviceOpts
	var err error

	if args, err = flags.ParseArgs(&opts, args); err != nil {
		logg.Fatal(err)
	}

	if len(args) < 1 {
		err = errors.New("missing deviceId")
	} else if len(args) < 2 {
		err = errors.New("missing device command")
	}

	if err != nil {
		logg.Fatal("Error: ", err)
		return 1
	}

	var devID = args[0]
	var cmd = args[1]

	switch cmd {
	case "set":
		err = deviceSetProperties(devID, args[2:])
	case "rm":
		err = deviceRemoveProperties(devID, args[2:], opts)
	case "props", "properties", "list":
		err = deviceListProperties(devID)
	default:
		err = fmt.Errorf("Unknown device command: '%s'", cmd)
	}

	if err != nil {
		logg.Fatal("Error: ", err)
		return 1
	}
	return 0
}

func deviceListProperties(devID string) error {
	return _printProperties(api.ListProperties(devID))
}

func deviceRemoveProperties(devID string, args []string, opts DeviceOpts) error {
	if len(args) == 0 {
		logg.Error("Too few arguments")
	}

	// check if the user wants to delete the device ("on:id" present)
	var wantsDelete = false
	for _, key := range args {
		if key == "on:id" {
			wantsDelete = true
			break
		}
	}

	if wantsDelete {
		if len(args) != 1 {
			logg.Fatal("To delete a device, remove its 'on:id' property (and nothing else)")
		}

		var confirmed = opts.Yes
		if !confirmed {
			var reader = bufio.NewReader(os.Stdin)
			var input string
			var err error

			for input == "" {
				fmt.Printf("Do you really want to delete the device '%s' (y/N): ", devID)
				input, err = reader.ReadString('\n')
				if err != nil {
					logg.Fatal(err)
				}

				switch strings.TrimSpace(strings.ToLower(input)) {
				case "y", "yes":
					confirmed = true
				case "n", "no", "":
					confirmed = false
					input = "no"
				default:
					input = ""
				}
			}
		}

		if confirmed {
			if err := api.DeleteDevice(devID); err != nil {
				return err
			}
		} else {
			return errors.New("Aborted delete")
		}
		return nil
	}

	return _printProperties(api.RemoveProperties(devID, args))
}

func deviceSetProperties(devID string, args []string) error {
	var props = make(map[string]string)

	for _, arg := range args {
		s := strings.SplitN(arg, "=", 2)
		if _, ok := props[s[0]]; ok {
			return fmt.Errorf("Duplicate value for property '%s'", s[0])
		}
		props[s[0]] = s[1]
	}

	return _printProperties(api.SetProperties(devID, props))
}

func _printProperties(props map[string]interface{}, err error) error {
	if err != nil {
		return err
	}

	// get list of keys and sort them
	var keys = make([]string, 0, len(props))
	for k := range props {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	for _, k := range keys {
		var v = props[k]
		var repr string

		if s, ok := v.(string); ok {
			repr = s
		} else {
			var reprBytes, _ = json.Marshal(v)
			repr = string(reprBytes)
		}

		fmt.Printf("%s=%s\n", k, repr)
	}

	return nil
}

// DeviceCommand -- implemnts `ondevice device`
var DeviceCommand = BaseCommand{
	Arguments: "<devId> <props/set/rm> [key1=val1 ...]",
	ShortHelp: "List/manipulate device properties",
	RunFn:     deviceRun,
	LongHelp: `$ ondevice device <devId> props
$ ondevice device <devId> set [key1=val1 ...]
$ ondevice device <devId> rm [--yes/-y] [--delete] [key1 key2 ...]

This command allows you to change all your devices' properties.
It requires a client key with the 'manage' authorization.

Properties can be used to keep track of your devices, to manage their characteristics,
keep tracks of running maintenance scripts, etc.

- ondevice device $devId props
  lists that device's properties, one per line, as 'key=value' pairs
- ondevice device $devId set [key=val...]
  sets one or more device properties, again as 'key=value' pairs
- ondevice device $devId rm [key ...]
  removes one or more device properties by name

Some special cases are:
- ondevice device $devId set on:id=newId
  Rename (= change devId of) a device
- ondevice device $devId rm on:id
  Removing the special property 'on:id' will attempt to delete the device
  (will ask for confirmation unless you also specify --yes)
  Only devices that have been offline for at least an hour can be deleted.

Options:
--yes -y
  Don't ask before deleting a device

Each invocation will print the resulting property list.

Examples:
  $ ondevice device q5dkpm props
  $ ondevice device q5dkpm set test=1234 foo=bar
  test=1234
  foo=bar
  $ ondevice device q5dkpm rm foo
  test=1234

  # rename and then delete the device
  $ ondevice device q5dkpm set on:id=rpi
  $ ondevice device rpi rm on:id
  Do you really want to delete the device 'uyqsn4' (y/N):
`,
}
<|endoftext|>


<commit_before>package command

import (
	"encoding/hex"
	"fmt"
	"log"
	"net"
	"net/http"
	"net/url"
	"os"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/armon/go-metrics"
	"github.com/hashicorp/logutils"
	"github.com/hashicorp/vault/audit"
	"github.com/hashicorp/vault/command/server"
	"github.com/hashicorp/vault/helper/flag-slice"
	"github.com/hashicorp/vault/helper/gated-writer"
	"github.com/hashicorp/vault/helper/mlock"
	vaulthttp "github.com/hashicorp/vault/http"
	"github.com/hashicorp/vault/logical"
	"github.com/hashicorp/vault/physical"
	"github.com/hashicorp/vault/vault"
)

// ServerCommand is a Command that starts the Vault server.
type ServerCommand struct {
	AuditBackends      map[string]audit.Factory
	CredentialBackends map[string]logical.Factory
	LogicalBackends    map[string]logical.Factory

	Meta
}

func (c *ServerCommand) Run(args []string) int {
	var dev bool
	var configPath []string
	var logLevel string
	flags := c.Meta.FlagSet("server", FlagSetDefault)
	flags.BoolVar(&dev, "dev", false, "")
	flags.StringVar(&logLevel, "log-level", "info", "")
	flags.Usage = func() { c.Ui.Error(c.Help()) }
	flags.Var((*sliceflag.StringFlag)(&configPath), "config", "config")
	if err := flags.Parse(args); err != nil {
		return 1
	}

	// Validation
	if !dev && len(configPath) == 0 {
		c.Ui.Error("At least one config path must be specified with -config")
		flags.Usage()
		return 1
	}

	// Load the configuration
	var config *server.Config
	if dev {
		config = server.DevConfig()
	}
	for _, path := range configPath {
		current, err := server.LoadConfig(path)
		if err != nil {
			c.Ui.Error(fmt.Sprintf(
				"Error loading configuration from %s: %s", path, err))
			return 1
		}

		if config == nil {
			config = current
		} else {
			config = config.Merge(current)
		}
	}

	// If mlock isn't supported, show a warning. We disable this in
	// dev because it is quite scary to see when first using Vault.
	if !dev && !mlock.Supported() {
		c.Ui.Output("==> WARNING: mlock not supported on this system!\n")
		c.Ui.Output("  The `mlock` syscall to prevent memory from being swapped to")
		c.Ui.Output("  disk is not supported on this system. Enabling mlock or")
		c.Ui.Output("  running Vault on a system with mlock is much more secure.\n")
	}

	// Create a logger. We wrap it in a gated writer so that it doesn't
	// start logging too early.
	logGate := &gatedwriter.Writer{Writer: os.Stderr}
	logger := log.New(&logutils.LevelFilter{
		Levels: []logutils.LogLevel{
			"TRACE", "DEBUG", "INFO", "WARN", "ERR"},
		MinLevel: logutils.LogLevel(strings.ToUpper(logLevel)),
		Writer:   logGate,
	}, "", log.LstdFlags)

	// Initialize the backend
	backend, err := physical.NewBackend(
		config.Backend.Type, config.Backend.Config)
	if err != nil {
		c.Ui.Error(fmt.Sprintf(
			"Error initializing backend of type %s: %s",
			config.Backend.Type, err))
		return 1
	}

	// Attempt to detect the advertise address possible
	if detect, ok := backend.(physical.AdvertiseDetect); ok && config.Backend.AdvertiseAddr == "" {
		advertise, err := c.detectAdvertise(detect, config)
		if err != nil {
			c.Ui.Error(fmt.Sprintf("Error detecting advertise address: %s", err))
		} else if advertise == "" {
			c.Ui.Error("Failed to detect advertise address.")
		} else {
			config.Backend.AdvertiseAddr = advertise
		}
	}

	// Initialize the core
	core, err := vault.NewCore(&vault.CoreConfig{
		AdvertiseAddr:      config.Backend.AdvertiseAddr,
		Physical:           backend,
		AuditBackends:      c.AuditBackends,
		CredentialBackends: c.CredentialBackends,
		LogicalBackends:    c.LogicalBackends,
		Logger:             logger,
		DisableMlock:       config.DisableMlock,
	})
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error initializing core: %s", err))
		return 1
	}

	// If we're in dev mode, then initialize the core
	if dev {
		init, err := c.enableDev(core)
		if err != nil {
			c.Ui.Error(fmt.Sprintf(
				"Error initializing dev mode: %s", err))
			return 1
		}

		c.Ui.Output(fmt.Sprintf(
			"==> WARNING: Dev mode is enabled!\n\n"+
				"In this mode, Vault is completely in-memory and unsealed.\n"+
				"Vault is configured to only have a single unseal key. The root\n"+
				"token has already been authenticated with the CLI, so you can\n"+
				"immediately begin using the Vault CLI.\n\n"+
				"The only step you need to take is to set the following\n"+
				"environment variable since Vault will be talking without TLS:\n\n"+
				"    export VAULT_ADDR='http://127.0.0.1:8200'\n\n"+
				"The unseal key and root token are reproduced below in case you\n"+
				"want to seal/unseal the Vault or play with authentication.\n\n"+
				"Unseal Key: %s\nRoot Token: %s\n",
			hex.EncodeToString(init.SecretShares[0]),
			init.RootToken,
		))
	}

	// Compile server information for output later
	infoKeys := make([]string, 0, 10)
	info := make(map[string]string)
	info["backend"] = config.Backend.Type
	info["log level"] = logLevel
	info["mlock"] = fmt.Sprintf(
		"supported: %v, enabled: %v",
		mlock.Supported(), !config.DisableMlock)
	infoKeys = append(infoKeys, "log level", "mlock", "backend")

	// If the backend supports HA, then note it
	if _, ok := backend.(physical.HABackend); ok {
		info["backend"] += " (HA available)"
		info["advertise address"] = config.Backend.AdvertiseAddr
		infoKeys = append(infoKeys, "advertise address")
	}

	// Initialize the telemetry
	if err := c.setupTelementry(config); err != nil {
		c.Ui.Error(fmt.Sprintf("Error initializing telemetry: %s", err))
		return 1
	}

	// Initialize the listeners
	lns := make([]net.Listener, 0, len(config.Listeners))
	for i, lnConfig := range config.Listeners {
		ln, props, err := server.NewListener(lnConfig.Type, lnConfig.Config)
		if err != nil {
			c.Ui.Error(fmt.Sprintf(
				"Error initializing listener of type %s: %s",
				lnConfig.Type, err))
			return 1
		}

		// Store the listener props for output later
		key := fmt.Sprintf("listener %d", i+1)
		propsList := make([]string, 0, len(props))
		for k, v := range props {
			propsList = append(propsList, fmt.Sprintf(
				"%s: %q", k, v))
		}
		sort.Strings(propsList)
		infoKeys = append(infoKeys, key)
		info[key] = fmt.Sprintf(
			"%s (%s)", lnConfig.Type, strings.Join(propsList, ", "))

		lns = append(lns, ln)
	}

	// Initialize the HTTP server
	server := &http.Server{}
	server.Handler = vaulthttp.Handler(core)
	for _, ln := range lns {
		go server.Serve(ln)
	}

	// Server configuration output
	padding := 18
	c.Ui.Output("==> Vault server configuration:\n")
	for _, k := range infoKeys {
		c.Ui.Output(fmt.Sprintf(
			"%s%s: %s",
			strings.Repeat(" ", padding-len(k)),
			strings.Title(k),
			info[k]))
	}
	c.Ui.Output("")

	// Output the header that the server has started
	c.Ui.Output("==> Vault server started! Log data will stream in below:\n")

	// Release the log gate.
	logGate.Flush()

	<-make(chan struct{})
	return 0
}

func (c *ServerCommand) enableDev(core *vault.Core) (*vault.InitResult, error) {
	// Initialize it with a basic single key
	init, err := core.Initialize(&vault.SealConfig{
		SecretShares:    1,
		SecretThreshold: 1,
	})
	if err != nil {
		return nil, err
	}

	// Copy the key so that it can be zeroed
	key := make([]byte, len(init.SecretShares[0]))
	copy(key, init.SecretShares[0])

	// Unseal the core
	unsealed, err := core.Unseal(key)
	if err != nil {
		return nil, err
	}
	if !unsealed {
		return nil, fmt.Errorf("failed to unseal Vault for dev mode")
	}

	// Set the token
	tokenHelper, err := c.TokenHelper()
	if err != nil {
		return nil, err
	}
	if err := tokenHelper.Store(init.RootToken); err != nil {
		return nil, err
	}

	return init, nil
}

// detectAdvertise is used to attempt advertise address detection
func (c *ServerCommand) detectAdvertise(detect physical.AdvertiseDetect,
	config *server.Config) (string, error) {
	// Get the hostname
	host, err := detect.DetectHostAddr()
	if err != nil {
		return "", err
	}

	// Default the port and scheme
	scheme := "https"
	port := 8200

	// Attempt to detect overrides
	for _, list := range config.Listeners {
		// Only attempt TCP
		if list.Type != "tcp" {
			continue
		}

		// Check if TLS is disabled
		if _, ok := list.Config["tls_disable"]; ok {
			scheme = "http"
		}

		// Check for address override
		addr, ok := list.Config["address"]
		if !ok {
			addr = "127.0.0.1:8200"
		}

		// Check for localhost
		hostStr, portStr, err := net.SplitHostPort(addr)
		if err != nil {
			continue
		}
		if hostStr == "127.0.0.1" {
			host = hostStr
		}

		// Check for custom port
		listPort, err := strconv.Atoi(portStr)
		if err != nil {
			continue
		}
		port = listPort
	}

	// Build a URL
	url := &url.URL{
		Scheme: scheme,
		Host:   fmt.Sprintf("%s:%d", host, port),
	}

	// Return the URL string
	return url.String(), nil
}

// setupTelementry is used ot setup the telemetry sub-systems
func (c *ServerCommand) setupTelementry(config *server.Config) error {
	/* Setup telemetry
	Aggregate on 10 second intervals for 1 minute. Expose the
	metrics over stderr when there is a SIGUSR1 received.
	*/
	inm := metrics.NewInmemSink(10*time.Second, time.Minute)
	metrics.DefaultInmemSignal(inm)
	metricsConf := metrics.DefaultConfig("vault")

	// Configure the statsite sink
	var fanout metrics.FanoutSink
	if config.StatsiteAddr != "" {
		sink, err := metrics.NewStatsiteSink(config.StatsiteAddr)
		if err != nil {
			return err
		}
		fanout = append(fanout, sink)
	}

	// Configure the statsd sink
	if config.StatsdAddr != "" {
		sink, err := metrics.NewStatsdSink(config.StatsdAddr)
		if err != nil {
			return err
		}
		fanout = append(fanout, sink)
	}

	// Initialize the global sink
	if len(fanout) > 0 {
		fanout = append(fanout, inm)
		metrics.NewGlobal(metricsConf, fanout)
	} else {
		metricsConf.EnableHostname = false
		metrics.NewGlobal(metricsConf, inm)
	}
	return nil
}

func (c *ServerCommand) Synopsis() string {
	return "Start a Vault server"
}

func (c *ServerCommand) Help() string {
	helpText := `
Usage: vault server [options]

  Start a Vault server.

  This command starts a Vault server that responds to API requests.
  Vault will start in a "sealed" state. The Vault must be unsealed
  with "vault unseal" or the API before this server can respond to requests.
  This must be done for every server.

  If the server is being started against a storage backend that has
  brand new (no existing Vault data in it), it must be initialized with
  "vault init" or the API first.

General Options:

  -config=<path>      Path to the configuration file or directory. This can be
                      specified multiple times. If it is a directory, all
                      files with a ".hcl" or ".json" suffix will be loaded.

  -log-level=info     Log verbosity. Defaults to "info", will be outputted
                      to stderr.

`
	return strings.TrimSpace(helpText)
}
<commit_msg>Make the VAULT_TOKEN and VAULT_ADDR copy-pastable in dev mode<commit_after>package command

import (
	"encoding/hex"
	"fmt"
	"log"
	"net"
	"net/http"
	"net/url"
	"os"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/armon/go-metrics"
	"github.com/hashicorp/logutils"
	"github.com/hashicorp/vault/audit"
	"github.com/hashicorp/vault/command/server"
	"github.com/hashicorp/vault/helper/flag-slice"
	"github.com/hashicorp/vault/helper/gated-writer"
	"github.com/hashicorp/vault/helper/mlock"
	vaulthttp "github.com/hashicorp/vault/http"
	"github.com/hashicorp/vault/logical"
	"github.com/hashicorp/vault/physical"
	"github.com/hashicorp/vault/vault"
)

// ServerCommand is a Command that starts the Vault server.
type ServerCommand struct {
	AuditBackends      map[string]audit.Factory
	CredentialBackends map[string]logical.Factory
	LogicalBackends    map[string]logical.Factory

	Meta
}

func (c *ServerCommand) Run(args []string) int {
	var dev bool
	var configPath []string
	var logLevel string
	flags := c.Meta.FlagSet("server", FlagSetDefault)
	flags.BoolVar(&dev, "dev", false, "")
	flags.StringVar(&logLevel, "log-level", "info", "")
	flags.Usage = func() { c.Ui.Error(c.Help()) }
	flags.Var((*sliceflag.StringFlag)(&configPath), "config", "config")
	if err := flags.Parse(args); err != nil {
		return 1
	}

	// Validation
	if !dev && len(configPath) == 0 {
		c.Ui.Error("At least one config path must be specified with -config")
		flags.Usage()
		return 1
	}

	// Load the configuration
	var config *server.Config
	if dev {
		config = server.DevConfig()
	}
	for _, path := range configPath {
		current, err := server.LoadConfig(path)
		if err != nil {
			c.Ui.Error(fmt.Sprintf(
				"Error loading configuration from %s: %s", path, err))
			return 1
		}

		if config == nil {
			config = current
		} else {
			config = config.Merge(current)
		}
	}

	// If mlock isn't supported, show a warning. We disable this in
	// dev because it is quite scary to see when first using Vault.
	if !dev && !mlock.Supported() {
		c.Ui.Output("==> WARNING: mlock not supported on this system!\n")
		c.Ui.Output("  The `mlock` syscall to prevent memory from being swapped to")
		c.Ui.Output("  disk is not supported on this system. Enabling mlock or")
		c.Ui.Output("  running Vault on a system with mlock is much more secure.\n")
	}

	// Create a logger. We wrap it in a gated writer so that it doesn't
	// start logging too early.
	logGate := &gatedwriter.Writer{Writer: os.Stderr}
	logger := log.New(&logutils.LevelFilter{
		Levels: []logutils.LogLevel{
			"TRACE", "DEBUG", "INFO", "WARN", "ERR"},
		MinLevel: logutils.LogLevel(strings.ToUpper(logLevel)),
		Writer:   logGate,
	}, "", log.LstdFlags)

	// Initialize the backend
	backend, err := physical.NewBackend(
		config.Backend.Type, config.Backend.Config)
	if err != nil {
		c.Ui.Error(fmt.Sprintf(
			"Error initializing backend of type %s: %s",
			config.Backend.Type, err))
		return 1
	}

	// Attempt to detect the advertise address possible
	if detect, ok := backend.(physical.AdvertiseDetect); ok && config.Backend.AdvertiseAddr == "" {
		advertise, err := c.detectAdvertise(detect, config)
		if err != nil {
			c.Ui.Error(fmt.Sprintf("Error detecting advertise address: %s", err))
		} else if advertise == "" {
			c.Ui.Error("Failed to detect advertise address.")
		} else {
			config.Backend.AdvertiseAddr = advertise
		}
	}

	// Initialize the core
	core, err := vault.NewCore(&vault.CoreConfig{
		AdvertiseAddr:      config.Backend.AdvertiseAddr,
		Physical:           backend,
		AuditBackends:      c.AuditBackends,
		CredentialBackends: c.CredentialBackends,
		LogicalBackends:    c.LogicalBackends,
		Logger:             logger,
		DisableMlock:       config.DisableMlock,
	})
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error initializing core: %s", err))
		return 1
	}

	// If we're in dev mode, then initialize the core
	if dev {
		init, err := c.enableDev(core)
		if err != nil {
			c.Ui.Error(fmt.Sprintf(
				"Error initializing dev mode: %s", err))
			return 1
		}

		c.Ui.Output(fmt.Sprintf(
			"==> WARNING: Dev mode is enabled!\n\n"+
				"In this mode, Vault is completely in-memory and unsealed.\n"+
				"Vault is configured to only have a single unseal key. The root\n"+
				"token has already been authenticated with the CLI, so you can\n"+
				"immediately begin using the Vault CLI.\n\n"+
				"The only step you need to take is to set the following\n"+
				"environment variables:\n\n"+
				"    export VAULT_ADDR='http://127.0.0.1:8200'\n"+
				"    export VAULT_TOKEN='%s'\n\n"+
				"The unseal key and root token are reproduced below in case you\n"+
				"want to seal/unseal the Vault or play with authentication.\n\n"+
				"Unseal Key: %s\nRoot Token: %s\n",
			init.RootToken,
			hex.EncodeToString(init.SecretShares[0]),
			init.RootToken,
		))
	}

	// Compile server information for output later
	infoKeys := make([]string, 0, 10)
	info := make(map[string]string)
	info["backend"] = config.Backend.Type
	info["log level"] = logLevel
	info["mlock"] = fmt.Sprintf(
		"supported: %v, enabled: %v",
		mlock.Supported(), !config.DisableMlock)
	infoKeys = append(infoKeys, "log level", "mlock", "backend")

	// If the backend supports HA, then note it
	if _, ok := backend.(physical.HABackend); ok {
		info["backend"] += " (HA available)"
		info["advertise address"] = config.Backend.AdvertiseAddr
		infoKeys = append(infoKeys, "advertise address")
	}

	// Initialize the telemetry
	if err := c.setupTelementry(config); err != nil {
		c.Ui.Error(fmt.Sprintf("Error initializing telemetry: %s", err))
		return 1
	}

	// Initialize the listeners
	lns := make([]net.Listener, 0, len(config.Listeners))
	for i, lnConfig := range config.Listeners {
		ln, props, err := server.NewListener(lnConfig.Type, lnConfig.Config)
		if err != nil {
			c.Ui.Error(fmt.Sprintf(
				"Error initializing listener of type %s: %s",
				lnConfig.Type, err))
			return 1
		}

		// Store the listener props for output later
		key := fmt.Sprintf("listener %d", i+1)
		propsList := make([]string, 0, len(props))
		for k, v := range props {
			propsList = append(propsList, fmt.Sprintf(
				"%s: %q", k, v))
		}
		sort.Strings(propsList)
		infoKeys = append(infoKeys, key)
		info[key] = fmt.Sprintf(
			"%s (%s)", lnConfig.Type, strings.Join(propsList, ", "))

		lns = append(lns, ln)
	}

	// Initialize the HTTP server
	server := &http.Server{}
	server.Handler = vaulthttp.Handler(core)
	for _, ln := range lns {
		go server.Serve(ln)
	}

	// Server configuration output
	padding := 18
	c.Ui.Output("==> Vault server configuration:\n")
	for _, k := range infoKeys {
		c.Ui.Output(fmt.Sprintf(
			"%s%s: %s",
			strings.Repeat(" ", padding-len(k)),
			strings.Title(k),
			info[k]))
	}
	c.Ui.Output("")

	// Output the header that the server has started
	c.Ui.Output("==> Vault server started! Log data will stream in below:\n")

	// Release the log gate.
	logGate.Flush()

	<-make(chan struct{})
	return 0
}

func (c *ServerCommand) enableDev(core *vault.Core) (*vault.InitResult, error) {
	// Initialize it with a basic single key
	init, err := core.Initialize(&vault.SealConfig{
		SecretShares:    1,
		SecretThreshold: 1,
	})
	if err != nil {
		return nil, err
	}

	// Copy the key so that it can be zeroed
	key := make([]byte, len(init.SecretShares[0]))
	copy(key, init.SecretShares[0])

	// Unseal the core
	unsealed, err := core.Unseal(key)
	if err != nil {
		return nil, err
	}
	if !unsealed {
		return nil, fmt.Errorf("failed to unseal Vault for dev mode")
	}

	// Set the token
	tokenHelper, err := c.TokenHelper()
	if err != nil {
		return nil, err
	}
	if err := tokenHelper.Store(init.RootToken); err != nil {
		return nil, err
	}

	return init, nil
}

// detectAdvertise is used to attempt advertise address detection
func (c *ServerCommand) detectAdvertise(detect physical.AdvertiseDetect,
	config *server.Config) (string, error) {
	// Get the hostname
	host, err := detect.DetectHostAddr()
	if err != nil {
		return "", err
	}

	// Default the port and scheme
	scheme := "https"
	port := 8200

	// Attempt to detect overrides
	for _, list := range config.Listeners {
		// Only attempt TCP
		if list.Type != "tcp" {
			continue
		}

		// Check if TLS is disabled
		if _, ok := list.Config["tls_disable"]; ok {
			scheme = "http"
		}

		// Check for address override
		addr, ok := list.Config["address"]
		if !ok {
			addr = "127.0.0.1:8200"
		}

		// Check for localhost
		hostStr, portStr, err := net.SplitHostPort(addr)
		if err != nil {
			continue
		}
		if hostStr == "127.0.0.1" {
			host = hostStr
		}

		// Check for custom port
		listPort, err := strconv.Atoi(portStr)
		if err != nil {
			continue
		}
		port = listPort
	}

	// Build a URL
	url := &url.URL{
		Scheme: scheme,
		Host:   fmt.Sprintf("%s:%d", host, port),
	}

	// Return the URL string
	return url.String(), nil
}

// setupTelementry is used ot setup the telemetry sub-systems
func (c *ServerCommand) setupTelementry(config *server.Config) error {
	/* Setup telemetry
	Aggregate on 10 second intervals for 1 minute. Expose the
	metrics over stderr when there is a SIGUSR1 received.
	*/
	inm := metrics.NewInmemSink(10*time.Second, time.Minute)
	metrics.DefaultInmemSignal(inm)
	metricsConf := metrics.DefaultConfig("vault")

	// Configure the statsite sink
	var fanout metrics.FanoutSink
	if config.StatsiteAddr != "" {
		sink, err := metrics.NewStatsiteSink(config.StatsiteAddr)
		if err != nil {
			return err
		}
		fanout = append(fanout, sink)
	}

	// Configure the statsd sink
	if config.StatsdAddr != "" {
		sink, err := metrics.NewStatsdSink(config.StatsdAddr)
		if err != nil {
			return err
		}
		fanout = append(fanout, sink)
	}

	// Initialize the global sink
	if len(fanout) > 0 {
		fanout = append(fanout, inm)
		metrics.NewGlobal(metricsConf, fanout)
	} else {
		metricsConf.EnableHostname = false
		metrics.NewGlobal(metricsConf, inm)
	}
	return nil
}

func (c *ServerCommand) Synopsis() string {
	return "Start a Vault server"
}

func (c *ServerCommand) Help() string {
	helpText := `
Usage: vault server [options]

  Start a Vault server.

  This command starts a Vault server that responds to API requests.
  Vault will start in a "sealed" state. The Vault must be unsealed
  with "vault unseal" or the API before this server can respond to requests.
  This must be done for every server.

  If the server is being started against a storage backend that has
  brand new (no existing Vault data in it), it must be initialized with
  "vault init" or the API first.

General Options:

  -config=<path>      Path to the configuration file or directory. This can be
                      specified multiple times. If it is a directory, all
                      files with a ".hcl" or ".json" suffix will be loaded.

  -log-level=info     Log verbosity. Defaults to "info", will be outputted
                      to stderr.

`
	return strings.TrimSpace(helpText)
}
<|endoftext|>


<commit_before>package commands

import (
	"io"
	"io/ioutil"
	"regexp"

	"github.com/github/hub/github"
	"github.com/github/hub/utils"
)

var cmdApply = &Command{
	Run:          apply,
	GitExtension: true,
	Usage:        "apply GITHUB-URL",
	Short:        "Apply a patch to files and/or to the index",
	Long: `Downloads the patch file for the pull request or commit at the URL and
applies that patch from disk with git am or git apply. Similar to
cherry-pick, but doesn't add new remotes. git am creates commits while
preserving authorship info while apply only applies the
patch to the working copy.
`,
}

var cmdAm = &Command{
	Run:          apply,
	GitExtension: true,
	Usage:        "am GITHUB-URL",
	Short:        "Apply a patch to files and/or to the index",
	Long: `Downloads the patch file for the pull request or commit at the URL and
applies that patch from disk with git am or git apply. Similar to
cherry-pick, but doesn't add new remotes. git am creates commits while
preserving authorship info while apply only applies the
patch to the working copy.
`,
}

func init() {
	CmdRunner.Use(cmdApply)
	CmdRunner.Use(cmdAm)
}

/*
  $ gh apply https://github.com/jingweno/gh/pull/55
  > curl https://github.com/jingweno/gh/pull/55.patch -o /tmp/55.patch
  > git apply /tmp/55.patch

  $ git apply --ignore-whitespace https://github.com/jingweno/gh/commit/fdb9921
  > curl https://github.com/jingweno/gh/commit/fdb9921.patch -o /tmp/fdb9921.patch
  > git apply --ignore-whitespace /tmp/fdb9921.patch

  $ git apply https://gist.github.com/8da7fb575debd88c54cf
  > curl https://gist.github.com/8da7fb575debd88c54cf.txt -o /tmp/gist-8da7fb575debd88c54cf.txt
  > git apply /tmp/gist-8da7fb575debd88c54cf.txt
*/
func apply(command *Command, args *Args) {
	if !args.IsParamsEmpty() {
		transformApplyArgs(args)
	}
}

func transformApplyArgs(args *Args) {
	gistRegexp := regexp.MustCompile("^https?://gist\\.github\\.com/([\\w.-]+/)?([a-f0-9]+)")
	pullRegexp := regexp.MustCompile("^(pull|commit)/([0-9a-f]+)")
	for _, arg := range args.Params {
		var (
			patch    io.ReadCloser
			apiError error
		)
		projectURL, err := github.ParseURL(arg)
		if err == nil {
			gh := github.NewClient(projectURL.Project.Host)
			match := pullRegexp.FindStringSubmatch(projectURL.ProjectPath())
			if match != nil {
				if match[1] == "pull" {
					patch, apiError = gh.PullRequestPatch(projectURL.Project, match[2])
				} else {
					patch, apiError = gh.CommitPatch(projectURL.Project, match[2])
				}
			}
		} else {
			match := gistRegexp.FindStringSubmatch(arg)
			if match != nil {
				// TODO: support Enterprise gist
				gh := github.NewClient(github.GitHubHost)
				patch, apiError = gh.GistPatch(match[2])
			}
		}

		utils.Check(apiError)
		if patch == nil {
			continue
		}

		idx := args.IndexOfParam(arg)
		patchFile, err := ioutil.TempFile("", "hub")
		utils.Check(err)

		bytes, err := ioutil.ReadAll(patch)
		utils.Check(err)

		patchFile.Write(bytes)
		patchFile.Close()
		patch.Close()

		args.Params[idx] = patchFile.Name()
	}
}
<commit_msg>Use io.Copy to write an io into the tempfile<commit_after>package commands

import (
	"io"
	"io/ioutil"
	"regexp"

	"github.com/github/hub/github"
	"github.com/github/hub/utils"
)

var cmdApply = &Command{
	Run:          apply,
	GitExtension: true,
	Usage:        "apply GITHUB-URL",
	Short:        "Apply a patch to files and/or to the index",
	Long: `Downloads the patch file for the pull request or commit at the URL and
applies that patch from disk with git am or git apply. Similar to
cherry-pick, but doesn't add new remotes. git am creates commits while
preserving authorship info while apply only applies the
patch to the working copy.
`,
}

var cmdAm = &Command{
	Run:          apply,
	GitExtension: true,
	Usage:        "am GITHUB-URL",
	Short:        "Apply a patch to files and/or to the index",
	Long: `Downloads the patch file for the pull request or commit at the URL and
applies that patch from disk with git am or git apply. Similar to
cherry-pick, but doesn't add new remotes. git am creates commits while
preserving authorship info while apply only applies the
patch to the working copy.
`,
}

func init() {
	CmdRunner.Use(cmdApply)
	CmdRunner.Use(cmdAm)
}

/*
  $ gh apply https://github.com/jingweno/gh/pull/55
  > curl https://github.com/jingweno/gh/pull/55.patch -o /tmp/55.patch
  > git apply /tmp/55.patch

  $ git apply --ignore-whitespace https://github.com/jingweno/gh/commit/fdb9921
  > curl https://github.com/jingweno/gh/commit/fdb9921.patch -o /tmp/fdb9921.patch
  > git apply --ignore-whitespace /tmp/fdb9921.patch

  $ git apply https://gist.github.com/8da7fb575debd88c54cf
  > curl https://gist.github.com/8da7fb575debd88c54cf.txt -o /tmp/gist-8da7fb575debd88c54cf.txt
  > git apply /tmp/gist-8da7fb575debd88c54cf.txt
*/
func apply(command *Command, args *Args) {
	if !args.IsParamsEmpty() {
		transformApplyArgs(args)
	}
}

func transformApplyArgs(args *Args) {
	gistRegexp := regexp.MustCompile("^https?://gist\\.github\\.com/([\\w.-]+/)?([a-f0-9]+)")
	pullRegexp := regexp.MustCompile("^(pull|commit)/([0-9a-f]+)")
	for _, arg := range args.Params {
		var (
			patch    io.ReadCloser
			apiError error
		)
		projectURL, err := github.ParseURL(arg)
		if err == nil {
			gh := github.NewClient(projectURL.Project.Host)
			match := pullRegexp.FindStringSubmatch(projectURL.ProjectPath())
			if match != nil {
				if match[1] == "pull" {
					patch, apiError = gh.PullRequestPatch(projectURL.Project, match[2])
				} else {
					patch, apiError = gh.CommitPatch(projectURL.Project, match[2])
				}
			}
		} else {
			match := gistRegexp.FindStringSubmatch(arg)
			if match != nil {
				// TODO: support Enterprise gist
				gh := github.NewClient(github.GitHubHost)
				patch, apiError = gh.GistPatch(match[2])
			}
		}

		utils.Check(apiError)
		if patch == nil {
			continue
		}

		idx := args.IndexOfParam(arg)
		patchFile, err := ioutil.TempFile("", "hub")
		utils.Check(err)

		_, err = io.Copy(patchFile, patch)
		utils.Check(err)

		patchFile.Close()
		patch.Close()

		args.Params[idx] = patchFile.Name()
	}
}
<|endoftext|>


<commit_before>package darksky

import "net/url"

// Timestamp is an int64 timestamp
type Timestamp int64

// Measurement is a float64 measurement
type Measurement float64

// ForecastRequest contains all available options for requesting a forecast
type ForecastRequest struct {
	Latitude  Measurement
	Longitude Measurement
	Time      Timestamp
	Options   ForecastRequestOptions
}

// ForecastRequestOptions are optional and passed as query parameters
type ForecastRequestOptions struct {
	Exclude string
	Extend  string
	Lang    string
	Units   string
}

// Encode into URL encoded query string parameters (exclude=hourly&units=si)
func (o ForecastRequestOptions) Encode() string {
	q := url.Values{}

	if o.Exclude != "" {
		q.Add("exclude", o.Exclude)
	}
	if o.Extend != "" {
		q.Add("extend", o.Extend)
	}
	if o.Lang != "" {
		q.Add("lang", o.Lang)
	}
	if o.Units != "" {
		q.Add("units", o.Units)
	}

	return q.Encode()
}

// ForecastResponse is the response containing all requested properties
type ForecastResponse struct {
	Latitude  Measurement `json:"latitude,omitempty"`
	Longitude Measurement `json:"longitude,omitempty"`
	Timezone  string      `json:"timezone,omitempty"`
	Currently *DataPoint  `json:"currently,omitempty"`
	Minutely  *DataBlock  `json:"minutely,omitempty"`
	Hourly    *DataBlock  `json:"hourly,omitempty"`
	Daily     *DataBlock  `json:"daily,omitempty"`
	Alerts    []*Alert    `json:"alerts,omitempty"`
	Flags     *Flags      `json:"flags,omitempty"`
}

// DataPoint contains various properties, each representing the average (unless otherwise specified) of a particular weather phenomenon occurring during a period of time.
type DataPoint struct {
	ApparentTemperature         Measurement `json:"apparentTemperature,omitempty"`
	ApparentTemperatureHigh     Measurement `json:"apparentTemperatureHigh,omitempty"`
	ApparentTemperatureHighTime Timestamp   `json:"apparentTemperatureHighTime,omitempty"`
	ApparentTemperatureLow      Measurement `json:"apparentTemperatureLow,omitempty"`
	ApparentTemperatureLowTime  Timestamp   `json:"apparentTemperatureLowTime,omitempty"`
	ApparentTemperatureMax      Measurement `json:"apparentTemperatureMax,omitempty"`
	ApparentTemperatureMaxTime  Timestamp   `json:"apparentTemperatureMaxTime,omitempty"`
	ApparentTemperatureMin      Measurement `json:"apparentTemperatureMin,omitempty"`
	ApparentTemperatureMinTime  Timestamp   `json:"apparentTemperatureMinTime,omitempty"`
	CloudCover                  Measurement `json:"cloudCover,omitempty"`
	DewPoint                    Measurement `json:"dewPoint,omitempty"`
	Humidity                    Measurement `json:"humidity,omitempty"`
	Icon                        string      `json:"icon,omitempty"`
	MoonPhase                   Measurement `json:"moonPhase,omitempty"`
	NearestStormBearing         Measurement `json:"nearestStormBearing,omitempty"`
	NearestStormDistance        Measurement `json:"nearestStormDistance,omitempty"`
	Ozone                       Measurement `json:"ozone,omitempty"`
	PrecipAccumulation          Measurement `json:"precipAccumulation,omitempty"`
	PrecipIntensity             Measurement `json:"precipIntensity,omitempty"`
	PrecipIntensityError        Measurement `json:"precipIntensityError,omitempty"`
	PrecipIntensityMax          Measurement `json:"precipIntensityMax,omitempty"`
	PrecipIntensityMaxTime      Timestamp   `json:"precipIntensityMaxTime,omitempty"`
	PrecipProbability           Measurement `json:"precipProbability,omitempty"`
	PrecipType                  string      `json:"precipType,omitempty"`
	Pressure                    Measurement `json:"pressure,omitempty"`
	Summary                     string      `json:"summary,omitempty"`
	SunriseTime                 Timestamp   `json:"sunriseTime,omitempty"`
	SunsetTime                  Timestamp   `json:"sunsetTime,omitempty"`
	Temperature                 Measurement `json:"temperature,omitempty"`
	TemperatureHigh             Measurement `json:"temperatureHigh,omitempty"`
	TemperatureHighTime         Timestamp   `json:"temperatureHighTime,omitempty"`
	TemperatureLow              Measurement `json:"temperatureLow,omitempty"`
	TemperatureLowTime          Timestamp   `json:"temperatureLowTime,omitempty"`
	TemperatureMax              Measurement `json:"temperatureMax,omitempty"`
	TemperatureMaxTime          Timestamp   `json:"temperatureMaxTime,omitempty"`
	TemperatureMin              Measurement `json:"temperatureMin,omitempty"`
	TemperatureMinTime          Timestamp   `json:"temperatureMinTime,omitempty"`
	Time                        Timestamp   `json:"time,omitempty"`
	UvIndex                     int64       `json:"uvIndex,omitempty"`
	UvIndexTime                 Timestamp   `json:"uvIndexTime,omitempty"`
	Visibility                  Measurement `json:"visibility,omitempty"`
	WindBearing                 Measurement `json:"windBearing,omitempty"`
	WindGust                    Measurement `json:"windGust,omitempty"`
	WindGustTime                Timestamp   `json:"windGustTime,omitempty"`
	WindSpeed                   Measurement `json:"windSpeed,omitempty"`
}

// DataBlock represents the various weather phenomena occurring over a period of time
type DataBlock struct {
	Data    []DataPoint `json:"data,omitempty"`
	Summary string      `json:"summary,omitempty"`
	Icon    string      `json:"icon,omitempty"`
}

// Alert contains objects representing the severe weather warnings issued for the requested location by a governmental authority
type Alert struct {
	Description string    `json:"description,omitempty"`
	Expires     Timestamp `json:"expires,omitempty"`
	Regions     []string  `json:"regions,omitempty"`
	Severity    string    `json:"severity,omitempty"`
	Time        Timestamp `json:"time,omitempty"`
	Title       string    `json:"title,omitempty"`
	Uri         string    `json:"uri,omitempty"`
}

// Flags contains various metadata information related to the request
type Flags struct {
	DarkSkyUnavailable string      `json:"darksky-unavailable,omitempty"`
	NearestStation     Measurement `json:"nearest-station"`
	Sources            []string    `json:"sources,omitempty"`
	Units              string      `json:"units,omitempty"`
}
<commit_msg>add time offset<commit_after>package darksky

import "net/url"

// Timestamp is an int64 timestamp
type Timestamp int64

// Measurement is a float64 measurement
type Measurement float64

// ForecastRequest contains all available options for requesting a forecast
type ForecastRequest struct {
	Latitude  Measurement
	Longitude Measurement
	Time      Timestamp
	Options   ForecastRequestOptions
}

// ForecastRequestOptions are optional and passed as query parameters
type ForecastRequestOptions struct {
	Exclude string
	Extend  string
	Lang    string
	Units   string
}

// Encode into URL encoded query string parameters (exclude=hourly&units=si)
func (o ForecastRequestOptions) Encode() string {
	q := url.Values{}

	if o.Exclude != "" {
		q.Add("exclude", o.Exclude)
	}
	if o.Extend != "" {
		q.Add("extend", o.Extend)
	}
	if o.Lang != "" {
		q.Add("lang", o.Lang)
	}
	if o.Units != "" {
		q.Add("units", o.Units)
	}

	return q.Encode()
}

// ForecastResponse is the response containing all requested properties
type ForecastResponse struct {
	Latitude  Measurement `json:"latitude,omitempty"`
	Longitude Measurement `json:"longitude,omitempty"`
	Timezone  string      `json:"timezone,omitempty"`
	Offset    int         `json:"offset,omitempty"`
	Currently *DataPoint  `json:"currently,omitempty"`
	Minutely  *DataBlock  `json:"minutely,omitempty"`
	Hourly    *DataBlock  `json:"hourly,omitempty"`
	Daily     *DataBlock  `json:"daily,omitempty"`
	Alerts    []*Alert    `json:"alerts,omitempty"`
	Flags     *Flags      `json:"flags,omitempty"`
}

// DataPoint contains various properties, each representing the average (unless otherwise specified) of a particular weather phenomenon occurring during a period of time.
type DataPoint struct {
	ApparentTemperature         Measurement `json:"apparentTemperature,omitempty"`
	ApparentTemperatureHigh     Measurement `json:"apparentTemperatureHigh,omitempty"`
	ApparentTemperatureHighTime Timestamp   `json:"apparentTemperatureHighTime,omitempty"`
	ApparentTemperatureLow      Measurement `json:"apparentTemperatureLow,omitempty"`
	ApparentTemperatureLowTime  Timestamp   `json:"apparentTemperatureLowTime,omitempty"`
	ApparentTemperatureMax      Measurement `json:"apparentTemperatureMax,omitempty"`
	ApparentTemperatureMaxTime  Timestamp   `json:"apparentTemperatureMaxTime,omitempty"`
	ApparentTemperatureMin      Measurement `json:"apparentTemperatureMin,omitempty"`
	ApparentTemperatureMinTime  Timestamp   `json:"apparentTemperatureMinTime,omitempty"`
	CloudCover                  Measurement `json:"cloudCover,omitempty"`
	DewPoint                    Measurement `json:"dewPoint,omitempty"`
	Humidity                    Measurement `json:"humidity,omitempty"`
	Icon                        string      `json:"icon,omitempty"`
	MoonPhase                   Measurement `json:"moonPhase,omitempty"`
	NearestStormBearing         Measurement `json:"nearestStormBearing,omitempty"`
	NearestStormDistance        Measurement `json:"nearestStormDistance,omitempty"`
	Ozone                       Measurement `json:"ozone,omitempty"`
	PrecipAccumulation          Measurement `json:"precipAccumulation,omitempty"`
	PrecipIntensity             Measurement `json:"precipIntensity,omitempty"`
	PrecipIntensityError        Measurement `json:"precipIntensityError,omitempty"`
	PrecipIntensityMax          Measurement `json:"precipIntensityMax,omitempty"`
	PrecipIntensityMaxTime      Timestamp   `json:"precipIntensityMaxTime,omitempty"`
	PrecipProbability           Measurement `json:"precipProbability,omitempty"`
	PrecipType                  string      `json:"precipType,omitempty"`
	Pressure                    Measurement `json:"pressure,omitempty"`
	Summary                     string      `json:"summary,omitempty"`
	SunriseTime                 Timestamp   `json:"sunriseTime,omitempty"`
	SunsetTime                  Timestamp   `json:"sunsetTime,omitempty"`
	Temperature                 Measurement `json:"temperature,omitempty"`
	TemperatureHigh             Measurement `json:"temperatureHigh,omitempty"`
	TemperatureHighTime         Timestamp   `json:"temperatureHighTime,omitempty"`
	TemperatureLow              Measurement `json:"temperatureLow,omitempty"`
	TemperatureLowTime          Timestamp   `json:"temperatureLowTime,omitempty"`
	TemperatureMax              Measurement `json:"temperatureMax,omitempty"`
	TemperatureMaxTime          Timestamp   `json:"temperatureMaxTime,omitempty"`
	TemperatureMin              Measurement `json:"temperatureMin,omitempty"`
	TemperatureMinTime          Timestamp   `json:"temperatureMinTime,omitempty"`
	Time                        Timestamp   `json:"time,omitempty"`
	UvIndex                     int64       `json:"uvIndex,omitempty"`
	UvIndexTime                 Timestamp   `json:"uvIndexTime,omitempty"`
	Visibility                  Measurement `json:"visibility,omitempty"`
	WindBearing                 Measurement `json:"windBearing,omitempty"`
	WindGust                    Measurement `json:"windGust,omitempty"`
	WindGustTime                Timestamp   `json:"windGustTime,omitempty"`
	WindSpeed                   Measurement `json:"windSpeed,omitempty"`
}

// DataBlock represents the various weather phenomena occurring over a period of time
type DataBlock struct {
	Data    []DataPoint `json:"data,omitempty"`
	Summary string      `json:"summary,omitempty"`
	Icon    string      `json:"icon,omitempty"`
}

// Alert contains objects representing the severe weather warnings issued for the requested location by a governmental authority
type Alert struct {
	Description string    `json:"description,omitempty"`
	Expires     Timestamp `json:"expires,omitempty"`
	Regions     []string  `json:"regions,omitempty"`
	Severity    string    `json:"severity,omitempty"`
	Time        Timestamp `json:"time,omitempty"`
	Title       string    `json:"title,omitempty"`
	Uri         string    `json:"uri,omitempty"`
}

// Flags contains various metadata information related to the request
type Flags struct {
	DarkSkyUnavailable string      `json:"darksky-unavailable,omitempty"`
	NearestStation     Measurement `json:"nearest-station"`
	Sources            []string    `json:"sources,omitempty"`
	Units              string      `json:"units,omitempty"`
}
<|endoftext|>


<commit_before>package commands

import (
	"fmt"
	"time"

	"github.com/spf13/cobra"
)

func GetStringFlag(ctx *cobra.Command, name string) string {
	return ctx.Flag(name).Value.String()
}

func GetBoolFlag(ctx *cobra.Command, name string) bool {
	return ctx.Flag(name).Value.String() == "true"
}

func FormatDateTime(t time.Time) string {
	return fmt.Sprintf("%d-%02d-%02d %02d:%02d:%02d",
		t.Year(), t.Month(), t.Day(),
		t.Hour(), t.Minute(), t.Second())
}

func Truncate(s string, maxlen int) string {
	if len(s) <= maxlen {
		return s
	}
	return s[:maxlen]
}
<commit_msg>Fix a bug if the flag doesn't exist<commit_after>package commands

import (
	"fmt"
	"time"

	"github.com/spf13/cobra"
)

func GetStringFlag(ctx *cobra.Command, name string) string {
	flag := ctx.Flag(name)
	if flag == nil {
		return ""
	}
	return flag.Value.String()
}

func GetBoolFlag(ctx *cobra.Command, name string) bool {
	flag := ctx.Flag(name)
	if flag == nil {
		return false
	}
	return flag.Value.String() == "true"
}

func FormatDateTime(t time.Time) string {
	return fmt.Sprintf("%d-%02d-%02d %02d:%02d:%02d",
		t.Year(), t.Month(), t.Day(),
		t.Hour(), t.Minute(), t.Second())
}

func Truncate(s string, maxlen int) string {
	if len(s) <= maxlen {
		return s
	}
	return s[:maxlen]
}
<|endoftext|>


<commit_before>package log

import (
	"DNA/common/config"
	"bytes"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"time"
)

const (
	Blue   = "0;34"
	Red    = "0;31"
	Green  = "0;32"
	Yellow = "0;33"
	Cyan   = "0;36"
	Pink   = "1;35"
)

func Color(code, msg string) string {
	return fmt.Sprintf("\033[%sm%s\033[m", code, msg)
}

const (
	debugLog = iota
	infoLog
	warnLog
	errorLog
	fatalLog
	traceLog
	maxLevelLog
)

var (
	levels = map[int]string{
		debugLog: Color(Green, "[DEBUG]"),
		infoLog:  Color(Green, "[INFO ]"),
		warnLog:  Color(Yellow, "[WARN ]"),
		errorLog: Color(Red, "[ERROR]"),
		fatalLog: Color(Red, "[FATAL]"),
		traceLog: Color(Pink, "[TRACE]"),
	}
	Stdout = os.Stdout
)

const (
	namePrefix        = "LEVEL"
	callDepth         = 2
	defaultMaxLogSize = 20
	byteToMb          = 1024 * 1024
	byteToKb          = 1024
	Path              = "./Log/"
)

func GetGID() uint64 {
	var buf [64]byte
	b := buf[:runtime.Stack(buf[:], false)]
	b = bytes.TrimPrefix(b, []byte("goroutine "))
	b = b[:bytes.IndexByte(b, ' ')]
	n, _ := strconv.ParseUint(string(b), 10, 64)
	return n
}

var Log *Logger

func LevelName(level int) string {
	if name, ok := levels[level]; ok {
		return name
	}
	return namePrefix + strconv.Itoa(level)
}

func NameLevel(name string) int {
	for k, v := range levels {
		if v == name {
			return k
		}
	}
	var level int
	if strings.HasPrefix(name, namePrefix) {
		level, _ = strconv.Atoi(name[len(namePrefix):])
	}
	return level
}

type Logger struct {
	level   int
	logger  *log.Logger
	logFile *os.File
}

func New(out io.Writer, prefix string, flag, level int, file *os.File) *Logger {
	return &Logger{
		level:   level,
		logger:  log.New(out, prefix, flag),
		logFile: file,
	}
}

func (l *Logger) SetDebugLevel(level int) error {
	if level > maxLevelLog || level < 0 {
		return errors.New("Invalid Debug Level")
	}

	l.level = level
	return nil
}

func (l *Logger) Output(level int, a ...interface{}) error {
	if level >= l.level {
		gid := GetGID()
		gidStr := strconv.FormatUint(gid, 10)

		a = append([]interface{}{LevelName(level), "GID",
			gidStr + ","}, a...)

		return l.logger.Output(callDepth, fmt.Sprintln(a...))
	}
	return nil
}

func (l *Logger) Outputf(level int, format string, v ...interface{}) error {
	if level >= l.level {
		gid := GetGID()
		v = append([]interface{}{LevelName(level), "GID",
			gid}, v...)

		return l.logger.Output(callDepth, fmt.Sprintf("%s %s %d, "+format+"\n", v...))
	}
	return nil
}

func (l *Logger) Trace(a ...interface{}) {
	l.Output(traceLog, a...)
}

func (l *Logger) Tracef(format string, a ...interface{}) {
	l.Outputf(traceLog, format, a...)
}

func (l *Logger) Debug(a ...interface{}) {
	l.Output(debugLog, a...)
}

func (l *Logger) Debugf(format string, a ...interface{}) {
	l.Outputf(debugLog, format, a...)
}

func (l *Logger) Info(a ...interface{}) {
	l.Output(infoLog, a...)
}

func (l *Logger) Infof(format string, a ...interface{}) {
	l.Outputf(infoLog, format, a...)
}

func (l *Logger) Warn(a ...interface{}) {
	l.Output(warnLog, a...)
}

func (l *Logger) Warnf(format string, a ...interface{}) {
	l.Outputf(warnLog, format, a...)
}

func (l *Logger) Error(a ...interface{}) {
	l.Output(errorLog, a...)
}

func (l *Logger) Errorf(format string, a ...interface{}) {
	l.Outputf(errorLog, format, a...)
}

func (l *Logger) Fatal(a ...interface{}) {
	l.Output(fatalLog, a...)
}

func (l *Logger) Fatalf(format string, a ...interface{}) {
	l.Outputf(fatalLog, format, a...)
}

func Trace(a ...interface{}) {
	if traceLog < Log.level {
		return
	}

	pc := make([]uintptr, 10)
	runtime.Callers(2, pc)
	f := runtime.FuncForPC(pc[0])
	file, line := f.FileLine(pc[0])
	fileName := filepath.Base(file)

	nameFull := f.Name()
	nameEnd := filepath.Ext(nameFull)
	funcName := strings.TrimPrefix(nameEnd, ".")

	a = append([]interface{}{funcName + "()", fileName + ":" + strconv.Itoa(line)}, a...)

	Log.Trace(a...)
}

func Tracef(format string, a ...interface{}) {
	if traceLog < Log.level {
		return
	}

	pc := make([]uintptr, 10)
	runtime.Callers(2, pc)
	f := runtime.FuncForPC(pc[0])
	file, line := f.FileLine(pc[0])
	fileName := filepath.Base(file)

	nameFull := f.Name()
	nameEnd := filepath.Ext(nameFull)
	funcName := strings.TrimPrefix(nameEnd, ".")

	a = append([]interface{}{funcName, fileName, line}, a...)

	Log.Tracef("%s() %s:%d "+format, a...)
}

func Debug(a ...interface{}) {
	if debugLog < Log.level {
		return
	}

	pc := make([]uintptr, 10)
	runtime.Callers(2, pc)
	f := runtime.FuncForPC(pc[0])
	file, line := f.FileLine(pc[0])
	fileName := filepath.Base(file)

	a = append([]interface{}{f.Name(), fileName + ":" + strconv.Itoa(line)}, a...)

	Log.Debug(a...)
}

func Debugf(format string, a ...interface{}) {
	if debugLog < Log.level {
		return
	}

	pc := make([]uintptr, 10)
	runtime.Callers(2, pc)
	f := runtime.FuncForPC(pc[0])
	file, line := f.FileLine(pc[0])
	fileName := filepath.Base(file)

	a = append([]interface{}{f.Name(), fileName, line}, a...)

	Log.Debugf("%s %s:%d "+format, a...)
}

func Info(a ...interface{}) {
	Log.Info(a...)
}

func Warn(a ...interface{}) {
	Log.Warn(a...)
}

func Error(a ...interface{}) {
	Log.Error(a...)
}

func Fatal(a ...interface{}) {
	Log.Fatal(a...)
}

func Infof(format string, a ...interface{}) {
	Log.Infof(format, a...)
}

func Warnf(format string, a ...interface{}) {
	Log.Warnf(format, a...)
}

func Errorf(format string, a ...interface{}) {
	Log.Errorf(format, a...)
}

func Fatalf(format string, a ...interface{}) {
	Log.Fatalf(format, a...)
}

func FileOpen(path string) (*os.File, error) {
	if fi, err := os.Stat(path); err == nil {
		if !fi.IsDir() {
			return nil, fmt.Errorf("open %s: not a directory", path)
		}
	} else if os.IsNotExist(err) {
		if err := os.MkdirAll(path, 0766); err != nil {
			return nil, err
		}
	} else {
		return nil, err
	}

	var currenttime string = time.Now().Format("2006-01-02_15.04.05")

	logfile, err := os.OpenFile(path+currenttime+"_LOG.log", os.O_RDWR|os.O_CREATE, 0666)
	if err != nil {
		return nil, err
	}
	return logfile, nil
}

func Init(a ...interface{}) {
	writers := []io.Writer{}
	var logFile *os.File
	var err error
	if len(a) == 0 {
		writers = append(writers, ioutil.Discard)
	} else {
		for _, o := range a {
			switch o.(type) {
			case string:
				logFile, err = FileOpen(o.(string))
				if err != nil {
					fmt.Println("error: open log file failed")
					os.Exit(1)
				}
				writers = append(writers, logFile)
			case *os.File:
				writers = append(writers, o.(*os.File))
			default:
				fmt.Println("error: invalid log location")
				os.Exit(1)
			}
		}
	}
	fileAndStdoutWrite := io.MultiWriter(writers...)
	var printlevel int = config.Parameters.PrintLevel
	Log = New(fileAndStdoutWrite, "", log.Lmicroseconds, printlevel, logFile)
}

func GetLogFileSize() (int64, error) {
	f, e := Log.logFile.Stat()
	if e != nil {
		return 0, e
	}
	return f.Size(), nil
}

func GetMaxLogChangeInterval() int64 {
	if config.Parameters.MaxLogSize != 0 {
		return (config.Parameters.MaxLogSize * byteToMb)
	} else {
		return (defaultMaxLogSize * byteToMb)
	}
}

func CheckIfNeedNewFile() bool {
	logFileSize, err := GetLogFileSize()
	maxLogFileSize := GetMaxLogChangeInterval()
	if err != nil {
		return false
	}
	if logFileSize > maxLogFileSize {
		return true
	} else {
		return false
	}
}

func ClosePrintLog() error {
	var err error
	if Log.logFile != nil {
		err = Log.logFile.Close()
	}
	return err
}
<commit_msg>Add date in log<commit_after>package log

import (
	"DNA/common/config"
	"bytes"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"time"
)

const (
	Blue   = "0;34"
	Red    = "0;31"
	Green  = "0;32"
	Yellow = "0;33"
	Cyan   = "0;36"
	Pink   = "1;35"
)

func Color(code, msg string) string {
	return fmt.Sprintf("\033[%sm%s\033[m", code, msg)
}

const (
	debugLog = iota
	infoLog
	warnLog
	errorLog
	fatalLog
	traceLog
	maxLevelLog
)

var (
	levels = map[int]string{
		debugLog: Color(Green, "[DEBUG]"),
		infoLog:  Color(Green, "[INFO ]"),
		warnLog:  Color(Yellow, "[WARN ]"),
		errorLog: Color(Red, "[ERROR]"),
		fatalLog: Color(Red, "[FATAL]"),
		traceLog: Color(Pink, "[TRACE]"),
	}
	Stdout = os.Stdout
)

const (
	namePrefix        = "LEVEL"
	callDepth         = 2
	defaultMaxLogSize = 20
	byteToMb          = 1024 * 1024
	byteToKb          = 1024
	Path              = "./Log/"
)

func GetGID() uint64 {
	var buf [64]byte
	b := buf[:runtime.Stack(buf[:], false)]
	b = bytes.TrimPrefix(b, []byte("goroutine "))
	b = b[:bytes.IndexByte(b, ' ')]
	n, _ := strconv.ParseUint(string(b), 10, 64)
	return n
}

var Log *Logger

func LevelName(level int) string {
	if name, ok := levels[level]; ok {
		return name
	}
	return namePrefix + strconv.Itoa(level)
}

func NameLevel(name string) int {
	for k, v := range levels {
		if v == name {
			return k
		}
	}
	var level int
	if strings.HasPrefix(name, namePrefix) {
		level, _ = strconv.Atoi(name[len(namePrefix):])
	}
	return level
}

type Logger struct {
	level   int
	logger  *log.Logger
	logFile *os.File
}

func New(out io.Writer, prefix string, flag, level int, file *os.File) *Logger {
	return &Logger{
		level:   level,
		logger:  log.New(out, prefix, flag),
		logFile: file,
	}
}

func (l *Logger) SetDebugLevel(level int) error {
	if level > maxLevelLog || level < 0 {
		return errors.New("Invalid Debug Level")
	}

	l.level = level
	return nil
}

func (l *Logger) Output(level int, a ...interface{}) error {
	if level >= l.level {
		gid := GetGID()
		gidStr := strconv.FormatUint(gid, 10)

		a = append([]interface{}{LevelName(level), "GID",
			gidStr + ","}, a...)

		return l.logger.Output(callDepth, fmt.Sprintln(a...))
	}
	return nil
}

func (l *Logger) Outputf(level int, format string, v ...interface{}) error {
	if level >= l.level {
		gid := GetGID()
		v = append([]interface{}{LevelName(level), "GID",
			gid}, v...)

		return l.logger.Output(callDepth, fmt.Sprintf("%s %s %d, "+format+"\n", v...))
	}
	return nil
}

func (l *Logger) Trace(a ...interface{}) {
	l.Output(traceLog, a...)
}

func (l *Logger) Tracef(format string, a ...interface{}) {
	l.Outputf(traceLog, format, a...)
}

func (l *Logger) Debug(a ...interface{}) {
	l.Output(debugLog, a...)
}

func (l *Logger) Debugf(format string, a ...interface{}) {
	l.Outputf(debugLog, format, a...)
}

func (l *Logger) Info(a ...interface{}) {
	l.Output(infoLog, a...)
}

func (l *Logger) Infof(format string, a ...interface{}) {
	l.Outputf(infoLog, format, a...)
}

func (l *Logger) Warn(a ...interface{}) {
	l.Output(warnLog, a...)
}

func (l *Logger) Warnf(format string, a ...interface{}) {
	l.Outputf(warnLog, format, a...)
}

func (l *Logger) Error(a ...interface{}) {
	l.Output(errorLog, a...)
}

func (l *Logger) Errorf(format string, a ...interface{}) {
	l.Outputf(errorLog, format, a...)
}

func (l *Logger) Fatal(a ...interface{}) {
	l.Output(fatalLog, a...)
}

func (l *Logger) Fatalf(format string, a ...interface{}) {
	l.Outputf(fatalLog, format, a...)
}

func Trace(a ...interface{}) {
	if traceLog < Log.level {
		return
	}

	pc := make([]uintptr, 10)
	runtime.Callers(2, pc)
	f := runtime.FuncForPC(pc[0])
	file, line := f.FileLine(pc[0])
	fileName := filepath.Base(file)

	nameFull := f.Name()
	nameEnd := filepath.Ext(nameFull)
	funcName := strings.TrimPrefix(nameEnd, ".")

	a = append([]interface{}{funcName + "()", fileName + ":" + strconv.Itoa(line)}, a...)

	Log.Trace(a...)
}

func Tracef(format string, a ...interface{}) {
	if traceLog < Log.level {
		return
	}

	pc := make([]uintptr, 10)
	runtime.Callers(2, pc)
	f := runtime.FuncForPC(pc[0])
	file, line := f.FileLine(pc[0])
	fileName := filepath.Base(file)

	nameFull := f.Name()
	nameEnd := filepath.Ext(nameFull)
	funcName := strings.TrimPrefix(nameEnd, ".")

	a = append([]interface{}{funcName, fileName, line}, a...)

	Log.Tracef("%s() %s:%d "+format, a...)
}

func Debug(a ...interface{}) {
	if debugLog < Log.level {
		return
	}

	pc := make([]uintptr, 10)
	runtime.Callers(2, pc)
	f := runtime.FuncForPC(pc[0])
	file, line := f.FileLine(pc[0])
	fileName := filepath.Base(file)

	a = append([]interface{}{f.Name(), fileName + ":" + strconv.Itoa(line)}, a...)

	Log.Debug(a...)
}

func Debugf(format string, a ...interface{}) {
	if debugLog < Log.level {
		return
	}

	pc := make([]uintptr, 10)
	runtime.Callers(2, pc)
	f := runtime.FuncForPC(pc[0])
	file, line := f.FileLine(pc[0])
	fileName := filepath.Base(file)

	a = append([]interface{}{f.Name(), fileName, line}, a...)

	Log.Debugf("%s %s:%d "+format, a...)
}

func Info(a ...interface{}) {
	Log.Info(a...)
}

func Warn(a ...interface{}) {
	Log.Warn(a...)
}

func Error(a ...interface{}) {
	Log.Error(a...)
}

func Fatal(a ...interface{}) {
	Log.Fatal(a...)
}

func Infof(format string, a ...interface{}) {
	Log.Infof(format, a...)
}

func Warnf(format string, a ...interface{}) {
	Log.Warnf(format, a...)
}

func Errorf(format string, a ...interface{}) {
	Log.Errorf(format, a...)
}

func Fatalf(format string, a ...interface{}) {
	Log.Fatalf(format, a...)
}

func FileOpen(path string) (*os.File, error) {
	if fi, err := os.Stat(path); err == nil {
		if !fi.IsDir() {
			return nil, fmt.Errorf("open %s: not a directory", path)
		}
	} else if os.IsNotExist(err) {
		if err := os.MkdirAll(path, 0766); err != nil {
			return nil, err
		}
	} else {
		return nil, err
	}

	var currenttime string = time.Now().Format("2006-01-02_15.04.05")

	logfile, err := os.OpenFile(path+currenttime+"_LOG.log", os.O_RDWR|os.O_CREATE, 0666)
	if err != nil {
		return nil, err
	}
	return logfile, nil
}

func Init(a ...interface{}) {
	writers := []io.Writer{}
	var logFile *os.File
	var err error
	if len(a) == 0 {
		writers = append(writers, ioutil.Discard)
	} else {
		for _, o := range a {
			switch o.(type) {
			case string:
				logFile, err = FileOpen(o.(string))
				if err != nil {
					fmt.Println("error: open log file failed")
					os.Exit(1)
				}
				writers = append(writers, logFile)
			case *os.File:
				writers = append(writers, o.(*os.File))
			default:
				fmt.Println("error: invalid log location")
				os.Exit(1)
			}
		}
	}
	fileAndStdoutWrite := io.MultiWriter(writers...)
	var printlevel int = config.Parameters.PrintLevel
	Log = New(fileAndStdoutWrite, "", log.Ldate|log.Lmicroseconds, printlevel, logFile)
}

func GetLogFileSize() (int64, error) {
	f, e := Log.logFile.Stat()
	if e != nil {
		return 0, e
	}
	return f.Size(), nil
}

func GetMaxLogChangeInterval() int64 {
	if config.Parameters.MaxLogSize != 0 {
		return (config.Parameters.MaxLogSize * byteToMb)
	} else {
		return (defaultMaxLogSize * byteToMb)
	}
}

func CheckIfNeedNewFile() bool {
	logFileSize, err := GetLogFileSize()
	maxLogFileSize := GetMaxLogChangeInterval()
	if err != nil {
		return false
	}
	if logFileSize > maxLogFileSize {
		return true
	} else {
		return false
	}
}

func ClosePrintLog() error {
	var err error
	if Log.logFile != nil {
		err = Log.logFile.Close()
	}
	return err
}
<|endoftext|>


<commit_before>package common

import (
	"sort"
	"sync"
)

// ServiceState 表示服务的状态
type ServiceState uint32

const (
	// NEW 新建``
	NEW ServiceState = iota
	// INITED 初始化完毕
	INITED
	// STARTING 正在启动
	STARTING
	// RUNNING 正在运行
	RUNNING
	// STOPPING 正在停止
	STOPPING
	// TERMINATED 已经停止
	TERMINATED
	// FAILED 失败
	FAILED
)

var serviceStateStrings = map[ServiceState]string{
	NEW:        "NEW",
	INITED:     "INITED",
	STARTING:   "STARTING",
	RUNNING:    "RUNNING",
	STOPPING:   "STOPPING",
	TERMINATED: "TERMINATED",
	FAILED:     "FAILED"}

func (p ServiceState) String() string {
	return serviceStateStrings[p]
}

var validStateState = map[ServiceState][]ServiceState{
	NEW:        []ServiceState{INITED, FAILED, TERMINATED},
	INITED:     []ServiceState{STARTING, FAILED, TERMINATED},
	STARTING:   []ServiceState{RUNNING, FAILED, TERMINATED},
	RUNNING:    []ServiceState{STOPPING, FAILED, TERMINATED},
	STOPPING:   []ServiceState{TERMINATED, FAILED},
	TERMINATED: []ServiceState{},
	FAILED:     []ServiceState{},
}

// IsValidServiceState 检查ServiceState的状态转移是否有效
func IsValidServiceState(oldState ServiceState, newState ServiceState) bool {
	if targetStates, ok := validStateState[oldState]; ok {
		for _, targetState := range targetStates {
			if targetState == newState {
				return true
			}
		}
	}
	return false
}

// Initable 表示需要进行初始化
type Initable interface {
	// Init 执行初始化操作,如果初始化失败,返回错误的原因
	Init() error
}

// Service 统一的服务接口
type Service interface {
	Initable
	// Name 取得服务名称
	Name() string
	// Start 启动服务
	Start() bool
	// 启动的次序
	GetStartOrder() int
	// Stop 停止服务
	Stop() bool
	// 停止的次序
	GetStopOrder() int
	// State 服务的状态
	State() ServiceState
	// SetState 设置服务的状态
	setState(newState ServiceState) bool
}

// ServiceInit 初始化服务
func ServiceInit(service Service) bool {
	Debugf("Init %T#%s", service, service.Name())
	if service.State() == INITED {
		Infof("%T#%s has been inited,skip", service, service.Name())
		return true
	}
	Infof("Init %T#%s", service, service.Name())
	err := service.Init()
	if err == nil && service.setState(INITED) {
		Debugf("Init %T#%s succ", service, service.Name())
		return true
	}
	Infof("Init %T#%s fail,err:%s", service, service.Name(), err)
	service.setState(FAILED)
	return false
}

// ServiceStart 开始服务
func ServiceStart(service Service) bool {
	Infof("Start %T#%s,state:%s", service, service.Name(), service.State())
	service.setState(STARTING)
	if service.Start() && service.setState(RUNNING) {
		Debugf("Start %T#%s succ", service, service.Name())
		return true
	}
	Infof("Start %T#%s fail", service, service.Name())
	service.setState(FAILED)
	return false
}

// ServiceStop 停止服务
func ServiceStop(service Service) bool {
	Debugf("Stop %T#%s", service, service.Name())
	service.setState(STOPPING)
	if service.Stop() && service.setState(TERMINATED) {
		Debugf("Stop %T#%s succ", service, service.Name())
		return true
	}
	Infof("Stop %T#%s fail", service, service.Name())
	service.setState(FAILED)
	return false
}

// BaseService 提供基本的Service接口实现
type BaseService struct {
	SName     string //服务的名称
	Order     int
	state     ServiceState //服务的状态
	stateLock sync.RWMutex //读写锁

}

// Name 服务名称
func (p *BaseService) Name() string {
	return p.SName
}

// Init 初始化
func (p *BaseService) Init() error {
	return nil
}

// Start 启动服务
func (p *BaseService) Start() bool {
	return true
}

// GetStartOrder 启动服务
func (p *BaseService) GetStartOrder() int {
	return p.Order
}

// Stop 停止服务
func (p *BaseService) Stop() bool {
	return true
}

// GetStopOrder 停止服务
func (p *BaseService) GetStopOrder() int {
	return -p.GetStartOrder()
}

// State 取得服务的状态
func (p *BaseService) State() ServiceState {
	p.stateLock.RLock()
	defer p.stateLock.RUnlock()
	return p.state
}

func (p *BaseService) setState(newState ServiceState) bool {
	p.stateLock.Lock()
	defer p.stateLock.Unlock()
	if IsValidServiceState(p.state, newState) {
		p.state = newState
		return true
	}
	Criticalf("Invalid state transfer %s->%s,%s", p.state, newState, p.Name())
	return false
}

// Services 一组Service的集合
type Services struct {
	sorted []Service //排序后的服务集合
}

// NewServices 构建新的Service集合
func NewServices(services []Service, start bool) *Services {
	//排序
	var sorted = make([]Service, len(services))
	copy(sorted, services)
	sort.Slice(sorted, func(i, j int) bool {
		if start {
			return sorted[i].GetStartOrder() < sorted[j].GetStartOrder()
		} else {
			return sorted[i].GetStopOrder() < sorted[j].GetStopOrder()
		}
	})
	return &Services{sorted: sorted}
}

// Init 初始化服务集合
func (p *Services) Init() bool {
	for _, service := range p.sorted {
		if !ServiceInit(service) {
			Warnf("Init service %T#%s fail", service, service.Name())
			return false
		}
	}
	return true
}

// Start 启动服务
func (p *Services) Start() bool {
	for _, service := range p.sorted {
		Infof("Start service %T#%s", service, service.Name())
		if !ServiceStart(service) {
			Warnf("Start service %T#%s fail", service, service.Name())
			return false
		}
	}
	return true
}

// Stop 停止服务
func (p *Services) Stop() bool {
	for i := len(p.sorted) - 1; i >= 0; i-- {
		service := p.sorted[i]
		Infof("Stop service %T#%s", service, service.Name())
		if !ServiceStop(service) {
			Warnf("Stop service %T#%s fail", service, service.Name())
		}
	}
	return true
}
<commit_msg>add order<commit_after>package common

import (
	"sort"
	"sync"
)

// ServiceState 表示服务的状态
type ServiceState uint32

const (
	// NEW 新建``
	NEW ServiceState = iota
	// INITED 初始化完毕
	INITED
	// STARTING 正在启动
	STARTING
	// RUNNING 正在运行
	RUNNING
	// STOPPING 正在停止
	STOPPING
	// TERMINATED 已经停止
	TERMINATED
	// FAILED 失败
	FAILED
)

var serviceStateStrings = map[ServiceState]string{
	NEW:        "NEW",
	INITED:     "INITED",
	STARTING:   "STARTING",
	RUNNING:    "RUNNING",
	STOPPING:   "STOPPING",
	TERMINATED: "TERMINATED",
	FAILED:     "FAILED"}

func (p ServiceState) String() string {
	return serviceStateStrings[p]
}

var validStateState = map[ServiceState][]ServiceState{
	NEW:        []ServiceState{INITED, FAILED, TERMINATED},
	INITED:     []ServiceState{STARTING, FAILED, TERMINATED},
	STARTING:   []ServiceState{RUNNING, FAILED, TERMINATED},
	RUNNING:    []ServiceState{STOPPING, FAILED, TERMINATED},
	STOPPING:   []ServiceState{TERMINATED, FAILED},
	TERMINATED: []ServiceState{},
	FAILED:     []ServiceState{},
}

// IsValidServiceState 检查ServiceState的状态转移是否有效
func IsValidServiceState(oldState ServiceState, newState ServiceState) bool {
	if targetStates, ok := validStateState[oldState]; ok {
		for _, targetState := range targetStates {
			if targetState == newState {
				return true
			}
		}
	}
	return false
}

// Initable 表示需要进行初始化
type Initable interface {
	// Init 执行初始化操作,如果初始化失败,返回错误的原因
	Init() error
}

// Service 统一的服务接口
type Service interface {
	Initable
	// Name 取得服务名称
	Name() string
	// Start 启动服务
	Start() bool
	// 启动的次序
	GetStartOrder() int
	// Stop 停止服务
	Stop() bool
	// 停止的次序
	GetStopOrder() int
	// State 服务的状态
	State() ServiceState
	// SetState 设置服务的状态
	setState(newState ServiceState) bool
}

// ServiceInit 初始化服务
func ServiceInit(service Service) bool {
	Debugf("Init %T#%s", service, service.Name())
	if service.State() == INITED {
		Infof("%T#%s has been inited,skip", service, service.Name())
		return true
	}
	Infof("Init %T#%s", service, service.Name())
	err := service.Init()
	if err == nil && service.setState(INITED) {
		Debugf("Init %T#%s succ", service, service.Name())
		return true
	}
	Infof("Init %T#%s fail,err:%s", service, service.Name(), err)
	service.setState(FAILED)
	return false
}

// ServiceStart 开始服务
func ServiceStart(service Service) bool {
	Infof("Starting %T#%s,state:%s", service, service.Name(), service.State())
	service.setState(STARTING)
	if service.Start() && service.setState(RUNNING) {
		Infof("%T#%s,state:%s", service, service.Name(), service.State())
		return true
	}
	Infof("Start %T#%s fail", service, service.Name())
	service.setState(FAILED)
	return false
}

// ServiceStop 停止服务
func ServiceStop(service Service) bool {
	Infof("Stop %T#%s", service, service.Name())
	service.setState(STOPPING)
	if service.Stop() && service.setState(TERMINATED) {
		Infof("%T#%s,state:%s", service, service.Name(), service.State())
		return true
	}
	Infof("Stop %T#%s fail", service, service.Name())
	service.setState(FAILED)
	return false
}

// BaseService 提供基本的Service接口实现
type BaseService struct {
	SName     string //服务的名称
	Order     int
	state     ServiceState //服务的状态
	stateLock sync.RWMutex //读写锁

}

// Name 服务名称
func (p *BaseService) Name() string {
	return p.SName
}

// Init 初始化
func (p *BaseService) Init() error {
	return nil
}

// Start 启动服务
func (p *BaseService) Start() bool {
	return true
}

// GetStartOrder 启动服务
func (p *BaseService) GetStartOrder() int {
	return p.Order
}

// Stop 停止服务
func (p *BaseService) Stop() bool {
	return true
}

// GetStopOrder 停止服务
func (p *BaseService) GetStopOrder() int {
	return -p.GetStartOrder()
}

// State 取得服务的状态
func (p *BaseService) State() ServiceState {
	p.stateLock.RLock()
	defer p.stateLock.RUnlock()
	return p.state
}

func (p *BaseService) setState(newState ServiceState) bool {
	p.stateLock.Lock()
	defer p.stateLock.Unlock()
	if IsValidServiceState(p.state, newState) {
		p.state = newState
		return true
	}
	Criticalf("Invalid state transfer %s->%s,%s", p.state, newState, p.Name())
	return false
}

// Services 一组Service的集合
type Services struct {
	sorted []Service //排序后的服务集合
}

// NewServices 构建新的Service集合
func NewServices(services []Service, start bool) *Services {
	//排序
	var sorted = make([]Service, len(services))
	copy(sorted, services)
	sort.Slice(sorted, func(i, j int) bool {
		if start {
			return sorted[i].GetStartOrder() < sorted[j].GetStartOrder()
		}
		return sorted[i].GetStopOrder() < sorted[j].GetStopOrder()
	})
	return &Services{sorted: sorted}
}

// Init 初始化服务集合
func (p *Services) Init() bool {
	for _, service := range p.sorted {
		if !ServiceInit(service) {
			Warnf("Init service %T#%s fail", service, service.Name())
			return false
		}
	}
	return true
}

// Start 启动服务
func (p *Services) Start() bool {
	for _, service := range p.sorted {
		Infof("Start service %T#%s", service, service.Name())
		if !ServiceStart(service) {
			Warnf("Start service %T#%s fail", service, service.Name())
			return false
		}
	}
	return true
}

// Stop 停止服务
func (p *Services) Stop() bool {
	for i := len(p.sorted) - 1; i >= 0; i-- {
		service := p.sorted[i]
		Infof("Stop service %T#%s", service, service.Name())
		if !ServiceStop(service) {
			Warnf("Stop service %T#%s fail", service, service.Name())
		}
	}
	return true
}
<|endoftext|>