<commit_before>// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package pathos

import (
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
)

func SlashToFilepath(path string) string {
	if '/' == filepath.Separator {
		return path
	}
	return strings.Replace(path, "/", string(filepath.Separator), -1)
}

func SlashToImportPath(path string) string {
	return strings.Replace(path, `\`, "/", -1)
}

func FileHasPrefix(s, prefix string) bool {
	if len(prefix) > len(s) {
		return false
	}
	return caseInsensitiveEq(s[:len(prefix)], prefix)
}

func FileTrimPrefix(s, prefix string) string {
	if FileHasPrefix(s, prefix) {
		return s[len(prefix):]
	}
	return s
}

func FileHasSuffix(s, suffix string) bool {
	if len(suffix) > len(s) {
		return false
	}
	return caseInsensitiveEq(s[len(s)-len(suffix):], suffix)
}

func FileTrimSuffix(s, suffix string) string {
	if FileHasSuffix(s, suffix) {
		return s[:len(s)-len(suffix)]
	}
	return s
}

var slashSep = filepath.Separator

func TrimCommonSuffix(base, suffix string) (string, string) {
	a, b := base, suffix
	if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
		a = strings.ToLower(a)
		b = strings.ToLower(b)
	}
	a = strings.TrimSuffix(strings.TrimSuffix(a, "\\"), "/")
	b = strings.TrimSuffix(strings.TrimSuffix(b, "\\"), "/")
	base = strings.TrimSuffix(strings.TrimSuffix(base, "\\"), "/")

	ff := func(r rune) bool {
		return r == '/' || r == '\\'
	}
	aa := strings.FieldsFunc(a, ff)
	bb := strings.FieldsFunc(b, ff)

	min := len(aa)
	if min > len(bb) {
		min = len(bb)
	}
	i := 1
	for ; i <= min; i++ {
		// fmt.Printf("(%d) end aa: %q, end bb: %q\n", i, aa[len(aa)-i], bb[len(bb)-i])
		if aa[len(aa)-i] == bb[len(bb)-i] {
			continue
		}
		break
	}
	baseParts := strings.FieldsFunc(base, ff)
	// fmt.Printf("base parts: %q\n", baseParts)
	base1 := FileTrimSuffix(base, strings.Join(baseParts[len(baseParts)-i+1:], string(slashSep)))
	base1 = strings.TrimSuffix(strings.TrimSuffix(base1, "\\"), "/")
	base2 := strings.Trim(base[len(base1):], `\/`)
	return base1, base2
}

func FileStringEquals(s1, s2 string) bool {
	if len(s1) == 0 {
		return len(s2) == 0
	}
	if len(s2) == 0 {
		return len(s1) == 0
	}
	r1End := s1[len(s1)-1]
	r2End := s2[len(s2)-1]
	if r1End == '/' || r1End == '\\' {
		s1 = s1[:len(s1)-1]
	}
	if r2End == '/' || r2End == '\\' {
		s2 = s2[:len(s2)-1]
	}
	return caseInsensitiveEq(s1, s2)
}

func caseInsensitiveEq(s1, s2 string) bool {
	if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
		return strings.EqualFold(s1, s2)
	}
	return s1 == s2
}

// ParseGoEnvLine parses a "go env" line into a key value pair.
func ParseGoEnvLine(line string) (key, value string, ok bool) {
	// Remove any leading "set " found on windows.
	// Match the name to the env var + "=".
	// Remove any quotes.
	// Return result.
	line = strings.TrimPrefix(line, "set ")
	parts := strings.SplitN(line, "=", 2)
	if len(parts) < 2 {
		return "", "", false
	}

	un, err := strconv.Unquote(parts[1])
	if err != nil {
		return parts[0], parts[1], true
	}
	return parts[0], un, true
}

// GoEnv parses a "go env" line and checks for a specific
// variable name.
func GoEnv(name, line string) (value string, ok bool) {
	// Remove any leading "set " found on windows.
	// Match the name to the env var + "=".
	// Remove any quotes.
	// Return result.
	line = strings.TrimPrefix(line, "set ")
	if len(line) < len(name)+1 {
		return "", false
	}
	if name != line[:len(name)] || line[len(name)] != '=' {
		return "", false
	}
	line = line[len(name)+1:]
	if un, err := strconv.Unquote(line); err == nil {
		line = un
	}
	return line, true
}
<commit_msg>Fixes for empty RootImportPath<commit_after>// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package pathos

import (
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
)

func SlashToFilepath(path string) string {
	if '/' == filepath.Separator {
		return path
	}
	return strings.Replace(path, "/", string(filepath.Separator), -1)
}

func SlashToImportPath(path string) string {
	return strings.Replace(path, `\`, "/", -1)
}

func FileHasPrefix(s, prefix string) bool {
	if len(prefix) > len(s) {
		return false
	}
	return caseInsensitiveEq(s[:len(prefix)], prefix)
}

func FileTrimPrefix(s, prefix string) string {
	if FileHasPrefix(s, prefix) {
		return s[len(prefix):]
	} else if FileStringEquals(s, prefix) {
		return ""
	}
	return s
}

func FileHasSuffix(s, suffix string) bool {
	if len(suffix) > len(s) {
		return false
	}
	return caseInsensitiveEq(s[len(s)-len(suffix):], suffix)
}

func FileTrimSuffix(s, suffix string) string {
	if FileHasSuffix(s, suffix) {
		return s[:len(s)-len(suffix)]
	} else if FileStringEquals(s, suffix) {
		return ""
	}
	return s
}

var slashSep = filepath.Separator

func TrimCommonSuffix(base, suffix string) (string, string) {
	a, b := base, suffix
	if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
		a = strings.ToLower(a)
		b = strings.ToLower(b)
	}
	a = strings.TrimSuffix(strings.TrimSuffix(a, "\\"), "/")
	b = strings.TrimSuffix(strings.TrimSuffix(b, "\\"), "/")
	base = strings.TrimSuffix(strings.TrimSuffix(base, "\\"), "/")

	ff := func(r rune) bool {
		return r == '/' || r == '\\'
	}
	aa := strings.FieldsFunc(a, ff)
	bb := strings.FieldsFunc(b, ff)

	min := len(aa)
	if min > len(bb) {
		min = len(bb)
	}
	i := 1
	for ; i <= min; i++ {
		// fmt.Printf("(%d) end aa: %q, end bb: %q\n", i, aa[len(aa)-i], bb[len(bb)-i])
		if aa[len(aa)-i] == bb[len(bb)-i] {
			continue
		}
		break
	}
	baseParts := strings.FieldsFunc(base, ff)
	// fmt.Printf("base parts: %q\n", baseParts)
	base1 := FileTrimSuffix(base, strings.Join(baseParts[len(baseParts)-i+1:], string(slashSep)))
	base1 = strings.TrimSuffix(strings.TrimSuffix(base1, "\\"), "/")
	base2 := strings.Trim(base[len(base1):], `\/`)
	return base1, base2
}

func FileStringEquals(s1, s2 string) bool {
	if len(s1) == 0 {
		return len(s2) == 0
	}
	if len(s2) == 0 {
		return len(s1) == 0
	}
	r1End := s1[len(s1)-1]
	r2End := s2[len(s2)-1]
	if r1End == '/' || r1End == '\\' {
		s1 = s1[:len(s1)-1]
	}
	if r2End == '/' || r2End == '\\' {
		s2 = s2[:len(s2)-1]
	}
	return caseInsensitiveEq(s1, s2)
}

func caseInsensitiveEq(s1, s2 string) bool {
	if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
		return strings.EqualFold(s1, s2)
	}
	return s1 == s2
}

// ParseGoEnvLine parses a "go env" line into a key value pair.
func ParseGoEnvLine(line string) (key, value string, ok bool) {
	// Remove any leading "set " found on windows.
	// Match the name to the env var + "=".
	// Remove any quotes.
	// Return result.
	line = strings.TrimPrefix(line, "set ")
	parts := strings.SplitN(line, "=", 2)
	if len(parts) < 2 {
		return "", "", false
	}

	un, err := strconv.Unquote(parts[1])
	if err != nil {
		return parts[0], parts[1], true
	}
	return parts[0], un, true
}

// GoEnv parses a "go env" line and checks for a specific
// variable name.
func GoEnv(name, line string) (value string, ok bool) {
	// Remove any leading "set " found on windows.
	// Match the name to the env var + "=".
	// Remove any quotes.
	// Return result.
	line = strings.TrimPrefix(line, "set ")
	if len(line) < len(name)+1 {
		return "", false
	}
	if name != line[:len(name)] || line[len(name)] != '=' {
		return "", false
	}
	line = line[len(name)+1:]
	if un, err := strconv.Unquote(line); err == nil {
		line = un
	}
	return line, true
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Ceph-CSI Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package util

import (
	"context"
	"encoding/base64"
	"errors"
	"fmt"
	"path"
	"strings"

	"crypto/rand"
)

const (
	mapperFilePrefix     = "luks-rbd-"
	mapperFilePathPrefix = "/dev/mapper"

	// kmsConfigPath is the location of the vault config file
	kmsConfigPath = "/etc/ceph-csi-encryption-kms-config/config.json"

	// Passphrase size - 20 bytes is 160 bits to satisfy:
	// https://tools.ietf.org/html/rfc6749#section-10.10
	encryptionPassphraseSize = 20
)

var (
	// ErrDEKStoreNotFound is an error that is returned when the DEKStore
	// has not been configured for the volumeID in the KMS instance.
	ErrDEKStoreNotFound = errors.New("DEKStore not found")

	// ErrDEKStoreNeeded is an indication that gets returned with
	// NewVolumeEncryption when the KMS does not include support for the
	// DEKStore interface.
	ErrDEKStoreNeeded = errors.New("DEKStore required, use " +
		"VolumeEncryption.SetDEKStore()")
)

type VolumeEncryption struct {
	KMS EncryptionKMS

	// dekStore that will be used, this can be the EncryptionKMS or a
	// different object implementing the DEKStore interface.
	dekStore DEKStore

	id string
}

// NewVolumeEncryption creates a new instance of VolumeEncryption and
// configures the DEKStore. If the KMS does not provide a DEKStore interface,
// the VolumeEncryption will be created *and* a ErrDEKStoreNeeded is returned.
// Callers that receive a ErrDEKStoreNeeded error, should use
// VolumeEncryption.SetDEKStore() to configure an alternative storage for the
// DEKs.
func NewVolumeEncryption(id string, kms EncryptionKMS) (*VolumeEncryption, error) {
	kmsID := id
	if kmsID == "" {
		// if kmsID is not set, encryption is enabled, and the type is
		// SecretsKMS
		kmsID = defaultKMSType
	}

	ve := &VolumeEncryption{
		id:  kmsID,
		KMS: kms,
	}

	if kms.requiresDEKStore() == DEKStoreIntegrated {
		dekStore, ok := kms.(DEKStore)
		if !ok {
			return nil, fmt.Errorf("KMS %T does not implement the "+
				"DEKStore interface", kms)
		}

		ve.dekStore = dekStore
		return ve, nil
	}

	return ve, ErrDEKStoreNeeded
}

// SetDEKStore sets the DEKStore for this VolumeEncryption instance. It will be
// used when StoreNewCryptoPassphrase() or RemoveDEK() is called.
func (ve *VolumeEncryption) SetDEKStore(dekStore DEKStore) {
	ve.dekStore = dekStore
}

// Destroy frees any resources that the VolumeEncryption instance allocated.
func (ve *VolumeEncryption) Destroy() {
	ve.KMS.Destroy()
}

// RemoveDEK deletes the DEK for a particular volumeID from the DEKStore linked
// with this VolumeEncryption instance.
func (ve *VolumeEncryption) RemoveDEK(volumeID string) error {
	if ve.dekStore == nil {
		return ErrDEKStoreNotFound
	}

	return ve.dekStore.RemoveDEK(volumeID)
}

func (ve *VolumeEncryption) GetID() string {
	return ve.id
}

// EncryptionKMS provides external Key Management System for encryption
// passphrases storage.
type EncryptionKMS interface {
	Destroy()

	// requiresDEKStore returns the DEKStoreType that is needed to be
	// configure for the KMS. Nothing needs to be done when this function
	// returns DEKStoreIntegrated, otherwise you will need to configure an
	// alternative storage for the DEKs.
	requiresDEKStore() DEKStoreType

	// EncryptDEK provides a way for a KMS to encrypt a DEK. In case the
	// encryption is done transparently inside the KMS service, the
	// function can return an unencrypted value.
	EncryptDEK(volumeID, plainDEK string) (string, error)

	// DecryptDEK provides a way for a KMS to decrypt a DEK. In case the
	// encryption is done transparently inside the KMS service, the
	// function does not need to do anything except return the encyptedDEK
	// as it was received.
	DecryptDEK(volumeID, encyptedDEK string) (string, error)
}

// DEKStoreType describes what DEKStore needs to be configured when using a
// particular KMS. A KMS might support different DEKStores depending on its
// configuration.
type DEKStoreType string

const (
	// DEKStoreIntegrated indicates that the KMS itself supports storing
	// DEKs.
	DEKStoreIntegrated = DEKStoreType("")
	// DEKStoreMetadata indicates that the KMS should be configured to
	// store the DEK in the metadata of the volume.
	DEKStoreMetadata = DEKStoreType("metadata")
)

// DEKStore allows KMS instances to implement a modular backend for DEK
// storage. This can be used to store the DEK in a different location, in case
// the KMS can not store passphrases for volumes.
type DEKStore interface {
	// StoreDEK saves the DEK in the configured store.
	StoreDEK(volumeID string, dek string) error
	// FetchDEK reads the DEK from the configured store and returns it.
	FetchDEK(volumeID string) (string, error)
	// RemoveDEK deletes the DEK from the configured store.
	RemoveDEK(volumeID string) error
}

// integratedDEK is a DEKStore that can not be configured. Either the KMS does
// not use a DEK, or the DEK is stored in the KMS without additional
// configuration options.
type integratedDEK struct{}

func (i integratedDEK) requiresDEKStore() DEKStoreType {
	return DEKStoreIntegrated
}

func (i integratedDEK) EncryptDEK(volumeID, plainDEK string) (string, error) {
	return plainDEK, nil
}

func (i integratedDEK) DecryptDEK(volumeID, encyptedDEK string) (string, error) {
	return encyptedDEK, nil
}

// StoreNewCryptoPassphrase generates a new passphrase and saves it in the KMS.
func (ve *VolumeEncryption) StoreNewCryptoPassphrase(volumeID string) error {
	passphrase, err := generateNewEncryptionPassphrase()
	if err != nil {
		return fmt.Errorf("failed to generate passphrase for %s: %w", volumeID, err)
	}

	encryptedPassphrase, err := ve.KMS.EncryptDEK(volumeID, passphrase)
	if err != nil {
		return fmt.Errorf("failed encrypt the passphrase for %s: %w", volumeID, err)
	}

	err = ve.dekStore.StoreDEK(volumeID, encryptedPassphrase)
	if err != nil {
		return fmt.Errorf("failed to save the passphrase for %s: %w", volumeID, err)
	}
	return nil
}

// GetCryptoPassphrase Retrieves passphrase to encrypt volume.
func (ve *VolumeEncryption) GetCryptoPassphrase(volumeID string) (string, error) {
	passphrase, err := ve.dekStore.FetchDEK(volumeID)
	if err != nil {
		return "", err
	}

	return ve.KMS.DecryptDEK(volumeID, passphrase)
}

// generateNewEncryptionPassphrase generates a random passphrase for encryption.
func generateNewEncryptionPassphrase() (string, error) {
	bytesPassphrase := make([]byte, encryptionPassphraseSize)
	_, err := rand.Read(bytesPassphrase)
	if err != nil {
		return "", err
	}
	return base64.URLEncoding.EncodeToString(bytesPassphrase), nil
}

// VolumeMapper returns file name and it's path to where encrypted device should be open.
func VolumeMapper(volumeID string) (mapperFile, mapperFilePath string) {
	mapperFile = mapperFilePrefix + volumeID
	mapperFilePath = path.Join(mapperFilePathPrefix, mapperFile)
	return mapperFile, mapperFilePath
}

// EncryptVolume encrypts provided device with LUKS.
func EncryptVolume(ctx context.Context, devicePath, passphrase string) error {
	DebugLog(ctx, "Encrypting device %s with LUKS", devicePath)
	if _, _, err := LuksFormat(devicePath, passphrase); err != nil {
		return fmt.Errorf("failed to encrypt device %s with LUKS: %w", devicePath, err)
	}
	return nil
}

// OpenEncryptedVolume opens volume so that it can be used by the client.
func OpenEncryptedVolume(ctx context.Context, devicePath, mapperFile, passphrase string) error {
	DebugLog(ctx, "Opening device %s with LUKS on %s", devicePath, mapperFile)
	_, _, err := LuksOpen(devicePath, mapperFile, passphrase)
	return err
}

// CloseEncryptedVolume closes encrypted volume so it can be detached.
func CloseEncryptedVolume(ctx context.Context, mapperFile string) error {
	DebugLog(ctx, "Closing LUKS device %s", mapperFile)
	_, _, err := LuksClose(mapperFile)
	return err
}

// IsDeviceOpen determines if encrypted device is already open.
func IsDeviceOpen(ctx context.Context, device string) (bool, error) {
	_, mappedFile, err := DeviceEncryptionStatus(ctx, device)
	return (mappedFile != ""), err
}

// DeviceEncryptionStatus looks to identify if the passed device is a LUKS mapping
// and if so what the device is and the mapper name as used by LUKS.
// If not, just returns the original device and an empty string.
func DeviceEncryptionStatus(ctx context.Context, devicePath string) (mappedDevice, mapper string, err error) {
	if !strings.HasPrefix(devicePath, mapperFilePathPrefix) {
		return devicePath, "", nil
	}
	mapPath := strings.TrimPrefix(devicePath, mapperFilePathPrefix+"/")
	stdout, _, err := LuksStatus(mapPath)
	if err != nil {
		DebugLog(ctx, "device %s is not an active LUKS device: %v", devicePath, err)
		return devicePath, "", nil
	}
	lines := strings.Split(string(stdout), "\n")
	if len(lines) < 1 {
		return "", "", fmt.Errorf("device encryption status returned no stdout for %s", devicePath)
	}
	if !strings.HasSuffix(lines[0], " is active.") {
		// Implies this is not a LUKS device
		return devicePath, "", nil
	}
	for i := 1; i < len(lines); i++ {
		kv := strings.SplitN(strings.TrimSpace(lines[i]), ":", 2)
		if len(kv) < 1 {
			return "", "", fmt.Errorf("device encryption status output for %s is badly formatted: %s",
				devicePath, lines[i])
		}
		if strings.Compare(kv[0], "device") == 0 {
			return strings.TrimSpace(kv[1]), mapPath, nil
		}
	}
	// Identified as LUKS, but failed to identify a mapped device
	return "", "", fmt.Errorf("mapped device not found in path %s", devicePath)
}
<commit_msg>util: add logging when OpenEncryptedVolume() encounters an error<commit_after>/*
Copyright 2019 The Ceph-CSI Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package util

import (
	"context"
	"encoding/base64"
	"errors"
	"fmt"
	"path"
	"strings"

	"crypto/rand"
)

const (
	mapperFilePrefix     = "luks-rbd-"
	mapperFilePathPrefix = "/dev/mapper"

	// kmsConfigPath is the location of the vault config file
	kmsConfigPath = "/etc/ceph-csi-encryption-kms-config/config.json"

	// Passphrase size - 20 bytes is 160 bits to satisfy:
	// https://tools.ietf.org/html/rfc6749#section-10.10
	encryptionPassphraseSize = 20
)

var (
	// ErrDEKStoreNotFound is an error that is returned when the DEKStore
	// has not been configured for the volumeID in the KMS instance.
	ErrDEKStoreNotFound = errors.New("DEKStore not found")

	// ErrDEKStoreNeeded is an indication that gets returned with
	// NewVolumeEncryption when the KMS does not include support for the
	// DEKStore interface.
	ErrDEKStoreNeeded = errors.New("DEKStore required, use " +
		"VolumeEncryption.SetDEKStore()")
)

type VolumeEncryption struct {
	KMS EncryptionKMS

	// dekStore that will be used, this can be the EncryptionKMS or a
	// different object implementing the DEKStore interface.
	dekStore DEKStore

	id string
}

// NewVolumeEncryption creates a new instance of VolumeEncryption and
// configures the DEKStore. If the KMS does not provide a DEKStore interface,
// the VolumeEncryption will be created *and* a ErrDEKStoreNeeded is returned.
// Callers that receive a ErrDEKStoreNeeded error, should use
// VolumeEncryption.SetDEKStore() to configure an alternative storage for the
// DEKs.
func NewVolumeEncryption(id string, kms EncryptionKMS) (*VolumeEncryption, error) {
	kmsID := id
	if kmsID == "" {
		// if kmsID is not set, encryption is enabled, and the type is
		// SecretsKMS
		kmsID = defaultKMSType
	}

	ve := &VolumeEncryption{
		id:  kmsID,
		KMS: kms,
	}

	if kms.requiresDEKStore() == DEKStoreIntegrated {
		dekStore, ok := kms.(DEKStore)
		if !ok {
			return nil, fmt.Errorf("KMS %T does not implement the "+
				"DEKStore interface", kms)
		}

		ve.dekStore = dekStore
		return ve, nil
	}

	return ve, ErrDEKStoreNeeded
}

// SetDEKStore sets the DEKStore for this VolumeEncryption instance. It will be
// used when StoreNewCryptoPassphrase() or RemoveDEK() is called.
func (ve *VolumeEncryption) SetDEKStore(dekStore DEKStore) {
	ve.dekStore = dekStore
}

// Destroy frees any resources that the VolumeEncryption instance allocated.
func (ve *VolumeEncryption) Destroy() {
	ve.KMS.Destroy()
}

// RemoveDEK deletes the DEK for a particular volumeID from the DEKStore linked
// with this VolumeEncryption instance.
func (ve *VolumeEncryption) RemoveDEK(volumeID string) error {
	if ve.dekStore == nil {
		return ErrDEKStoreNotFound
	}

	return ve.dekStore.RemoveDEK(volumeID)
}

func (ve *VolumeEncryption) GetID() string {
	return ve.id
}

// EncryptionKMS provides external Key Management System for encryption
// passphrases storage.
type EncryptionKMS interface {
	Destroy()

	// requiresDEKStore returns the DEKStoreType that is needed to be
	// configure for the KMS. Nothing needs to be done when this function
	// returns DEKStoreIntegrated, otherwise you will need to configure an
	// alternative storage for the DEKs.
	requiresDEKStore() DEKStoreType

	// EncryptDEK provides a way for a KMS to encrypt a DEK. In case the
	// encryption is done transparently inside the KMS service, the
	// function can return an unencrypted value.
	EncryptDEK(volumeID, plainDEK string) (string, error)

	// DecryptDEK provides a way for a KMS to decrypt a DEK. In case the
	// encryption is done transparently inside the KMS service, the
	// function does not need to do anything except return the encyptedDEK
	// as it was received.
	DecryptDEK(volumeID, encyptedDEK string) (string, error)
}

// DEKStoreType describes what DEKStore needs to be configured when using a
// particular KMS. A KMS might support different DEKStores depending on its
// configuration.
type DEKStoreType string

const (
	// DEKStoreIntegrated indicates that the KMS itself supports storing
	// DEKs.
	DEKStoreIntegrated = DEKStoreType("")
	// DEKStoreMetadata indicates that the KMS should be configured to
	// store the DEK in the metadata of the volume.
	DEKStoreMetadata = DEKStoreType("metadata")
)

// DEKStore allows KMS instances to implement a modular backend for DEK
// storage. This can be used to store the DEK in a different location, in case
// the KMS can not store passphrases for volumes.
type DEKStore interface {
	// StoreDEK saves the DEK in the configured store.
	StoreDEK(volumeID string, dek string) error
	// FetchDEK reads the DEK from the configured store and returns it.
	FetchDEK(volumeID string) (string, error)
	// RemoveDEK deletes the DEK from the configured store.
	RemoveDEK(volumeID string) error
}

// integratedDEK is a DEKStore that can not be configured. Either the KMS does
// not use a DEK, or the DEK is stored in the KMS without additional
// configuration options.
type integratedDEK struct{}

func (i integratedDEK) requiresDEKStore() DEKStoreType {
	return DEKStoreIntegrated
}

func (i integratedDEK) EncryptDEK(volumeID, plainDEK string) (string, error) {
	return plainDEK, nil
}

func (i integratedDEK) DecryptDEK(volumeID, encyptedDEK string) (string, error) {
	return encyptedDEK, nil
}

// StoreNewCryptoPassphrase generates a new passphrase and saves it in the KMS.
func (ve *VolumeEncryption) StoreNewCryptoPassphrase(volumeID string) error {
	passphrase, err := generateNewEncryptionPassphrase()
	if err != nil {
		return fmt.Errorf("failed to generate passphrase for %s: %w", volumeID, err)
	}

	encryptedPassphrase, err := ve.KMS.EncryptDEK(volumeID, passphrase)
	if err != nil {
		return fmt.Errorf("failed encrypt the passphrase for %s: %w", volumeID, err)
	}

	err = ve.dekStore.StoreDEK(volumeID, encryptedPassphrase)
	if err != nil {
		return fmt.Errorf("failed to save the passphrase for %s: %w", volumeID, err)
	}
	return nil
}

// GetCryptoPassphrase Retrieves passphrase to encrypt volume.
func (ve *VolumeEncryption) GetCryptoPassphrase(volumeID string) (string, error) {
	passphrase, err := ve.dekStore.FetchDEK(volumeID)
	if err != nil {
		return "", err
	}

	return ve.KMS.DecryptDEK(volumeID, passphrase)
}

// generateNewEncryptionPassphrase generates a random passphrase for encryption.
func generateNewEncryptionPassphrase() (string, error) {
	bytesPassphrase := make([]byte, encryptionPassphraseSize)
	_, err := rand.Read(bytesPassphrase)
	if err != nil {
		return "", err
	}
	return base64.URLEncoding.EncodeToString(bytesPassphrase), nil
}

// VolumeMapper returns file name and it's path to where encrypted device should be open.
func VolumeMapper(volumeID string) (mapperFile, mapperFilePath string) {
	mapperFile = mapperFilePrefix + volumeID
	mapperFilePath = path.Join(mapperFilePathPrefix, mapperFile)
	return mapperFile, mapperFilePath
}

// EncryptVolume encrypts provided device with LUKS.
func EncryptVolume(ctx context.Context, devicePath, passphrase string) error {
	DebugLog(ctx, "Encrypting device %s with LUKS", devicePath)
	if _, _, err := LuksFormat(devicePath, passphrase); err != nil {
		return fmt.Errorf("failed to encrypt device %s with LUKS: %w", devicePath, err)
	}
	return nil
}

// OpenEncryptedVolume opens volume so that it can be used by the client.
func OpenEncryptedVolume(ctx context.Context, devicePath, mapperFile, passphrase string) error {
	DebugLog(ctx, "Opening device %s with LUKS on %s", devicePath, mapperFile)
	_, stderr, err := LuksOpen(devicePath, mapperFile, passphrase)
	if err != nil {
		WarningLog(ctx, "failed to open LUKS device %q: %s", devicePath, stderr)
	}
	return err
}

// CloseEncryptedVolume closes encrypted volume so it can be detached.
func CloseEncryptedVolume(ctx context.Context, mapperFile string) error {
	DebugLog(ctx, "Closing LUKS device %s", mapperFile)
	_, _, err := LuksClose(mapperFile)
	return err
}

// IsDeviceOpen determines if encrypted device is already open.
func IsDeviceOpen(ctx context.Context, device string) (bool, error) {
	_, mappedFile, err := DeviceEncryptionStatus(ctx, device)
	return (mappedFile != ""), err
}

// DeviceEncryptionStatus looks to identify if the passed device is a LUKS mapping
// and if so what the device is and the mapper name as used by LUKS.
// If not, just returns the original device and an empty string.
func DeviceEncryptionStatus(ctx context.Context, devicePath string) (mappedDevice, mapper string, err error) {
	if !strings.HasPrefix(devicePath, mapperFilePathPrefix) {
		return devicePath, "", nil
	}
	mapPath := strings.TrimPrefix(devicePath, mapperFilePathPrefix+"/")
	stdout, _, err := LuksStatus(mapPath)
	if err != nil {
		DebugLog(ctx, "device %s is not an active LUKS device: %v", devicePath, err)
		return devicePath, "", nil
	}
	lines := strings.Split(string(stdout), "\n")
	if len(lines) < 1 {
		return "", "", fmt.Errorf("device encryption status returned no stdout for %s", devicePath)
	}
	if !strings.HasSuffix(lines[0], " is active.") {
		// Implies this is not a LUKS device
		return devicePath, "", nil
	}
	for i := 1; i < len(lines); i++ {
		kv := strings.SplitN(strings.TrimSpace(lines[i]), ":", 2)
		if len(kv) < 1 {
			return "", "", fmt.Errorf("device encryption status output for %s is badly formatted: %s",
				devicePath, lines[i])
		}
		if strings.Compare(kv[0], "device") == 0 {
			return strings.TrimSpace(kv[1]), mapPath, nil
		}
	}
	// Identified as LUKS, but failed to identify a mapped device
	return "", "", fmt.Errorf("mapped device not found in path %s", devicePath)
}
<|endoftext|>


<commit_before>package uvm

import (
	"fmt"
	"path"

	"github.com/Microsoft/hcsshim/hcn"
	"github.com/Microsoft/hcsshim/internal/guestrequest"
	"github.com/Microsoft/hcsshim/internal/guid"
	"github.com/Microsoft/hcsshim/internal/hns"
	"github.com/Microsoft/hcsshim/internal/requesttype"
	"github.com/Microsoft/hcsshim/internal/schema2"
	"github.com/Microsoft/hcsshim/osversion"
	"github.com/sirupsen/logrus"
)

// AddNetNS adds network namespace inside the guest & adds endpoints to the guest on that namepace
func (uvm *UtilityVM) AddNetNS(id string, endpoints []*hns.HNSEndpoint) (err error) {
	uvm.m.Lock()
	defer uvm.m.Unlock()
	ns := uvm.namespaces[id]
	if ns == nil {
		ns = &namespaceInfo{}
		// Add a Guest Network namespace
		if uvm.operatingSystem == "windows" {
			hcnNamespace, err := hcn.GetNamespaceByID(id)
			if err != nil {
				return err
			}
			guestNamespace := hcsschema.ModifySettingRequest{
				GuestRequest: guestrequest.GuestRequest{
					ResourceType: guestrequest.ResourceTypeNetworkNamespace,
					RequestType:  requesttype.Add,
					Settings:     hcnNamespace,
				},
			}
			if err := uvm.Modify(&guestNamespace); err != nil {
				return err
			}
		}

		defer func() {
			if err != nil {
				if e := uvm.removeNamespaceNICs(ns); e != nil {
					logrus.Warnf("failed to undo NIC add: %v", e)
				}
			}
		}()
		for _, endpoint := range endpoints {
			nicID := guid.New()
			err = uvm.addNIC(nicID, endpoint)
			if err != nil {
				return err
			}
			ns.nics = append(ns.nics, nicInfo{nicID, endpoint})
		}
		if uvm.namespaces == nil {
			uvm.namespaces = make(map[string]*namespaceInfo)
		}
		uvm.namespaces[id] = ns
	}
	ns.refCount++
	return nil
}

//RemoveNetNS removes the namespace information
func (uvm *UtilityVM) RemoveNetNS(id string) error {
	uvm.m.Lock()
	defer uvm.m.Unlock()
	ns := uvm.namespaces[id]
	if ns == nil || ns.refCount <= 0 {
		panic(fmt.Errorf("removed a namespace that was not added: %s", id))
	}

	// Remove the Guest Network namespace
	if uvm.operatingSystem == "windows" {
		hcnNamespace, err := hcn.GetNamespaceByID(id)
		if err != nil {
			return err
		}
		guestNamespace := hcsschema.ModifySettingRequest{
			GuestRequest: guestrequest.GuestRequest{
				ResourceType: guestrequest.ResourceTypeNetworkNamespace,
				RequestType:  requesttype.Remove,
				Settings:     hcnNamespace,
			},
		}
		if err := uvm.Modify(&guestNamespace); err != nil {
			return err
		}
	}

	ns.refCount--
	var err error
	if ns.refCount == 0 {
		err = uvm.removeNamespaceNICs(ns)
		delete(uvm.namespaces, id)
	}

	return err
}

func (uvm *UtilityVM) removeNamespaceNICs(ns *namespaceInfo) error {
	for len(ns.nics) != 0 {
		nic := ns.nics[len(ns.nics)-1]
		err := uvm.removeNIC(nic.ID, nic.Endpoint)
		if err != nil {
			return err
		}
		ns.nics = ns.nics[:len(ns.nics)-1]
	}
	return nil
}

func getNetworkModifyRequest(adapterID string, requestType string, settings interface{}) interface{} {
	if osversion.Get().Build >= osversion.RS5 {
		return guestrequest.NetworkModifyRequest{
			AdapterId:   adapterID,
			RequestType: requestType,
			Settings:    settings,
		}
	}
	return guestrequest.RS4NetworkModifyRequest{
		AdapterInstanceId: adapterID,
		RequestType:       requestType,
		Settings:          settings,
	}
}

func (uvm *UtilityVM) addNIC(id guid.GUID, endpoint *hns.HNSEndpoint) error {

	// First a pre-add. This is a guest-only request and is only done on Windows.
	if uvm.operatingSystem == "windows" {
		preAddRequest := hcsschema.ModifySettingRequest{
			GuestRequest: guestrequest.GuestRequest{
				ResourceType: guestrequest.ResourceTypeNetwork,
				RequestType:  requesttype.Add,
				Settings: getNetworkModifyRequest(
					id.String(),
					requesttype.PreAdd,
					endpoint),
			},
		}
		if err := uvm.Modify(&preAddRequest); err != nil {
			return err
		}
	}

	// Then the Add itself
	request := hcsschema.ModifySettingRequest{
		RequestType:  requesttype.Add,
		ResourcePath: path.Join("VirtualMachine/Devices/NetworkAdapters", id.String()),
		Settings: hcsschema.NetworkAdapter{
			EndpointId: endpoint.Id,
			MacAddress: endpoint.MacAddress,
		},
	}

	if uvm.operatingSystem == "windows" {
		request.GuestRequest = guestrequest.GuestRequest{
			ResourceType: guestrequest.ResourceTypeNetwork,
			RequestType:  requesttype.Add,
			Settings: getNetworkModifyRequest(
				id.String(),
				requesttype.Add,
				nil),
		}
		// Uncomment this once we have GuestRequest support for Linux
		//} else {
		//	request.GuestRequest = guestrequest.GuestRequest{
		//		ResourceType: guestrequest.ResourceTypeNetwork,
		//		RequestType:  requesttype.Add,
		//		Settings:     endpoint,
		//	}
	}

	if err := uvm.Modify(&request); err != nil {
		return err
	}

	return nil
}

func (uvm *UtilityVM) removeNIC(id guid.GUID, endpoint *hns.HNSEndpoint) error {
	request := hcsschema.ModifySettingRequest{
		RequestType:  requesttype.Remove,
		ResourcePath: path.Join("VirtualMachine/Devices/NetworkAdapters", id.String()),
		Settings: hcsschema.NetworkAdapter{
			EndpointId: endpoint.Id,
			MacAddress: endpoint.MacAddress,
		},
	}

	if uvm.operatingSystem == "windows" {
		request.GuestRequest = hcsschema.ModifySettingRequest{
			RequestType: requesttype.Remove,
			Settings: getNetworkModifyRequest(
				id.String(),
				requesttype.Remove,
				nil),
		}
	} else {
		request.GuestRequest = guestrequest.GuestRequest{
			ResourceType: guestrequest.ResourceTypeNetwork,
			RequestType:  requesttype.Remove,
			Settings:     endpoint,
		}
	}

	if err := uvm.Modify(&request); err != nil {
		return err
	}
	return nil
}
<commit_msg>Fix runhcs to setup networking for RS4 WCOW images<commit_after>package uvm

import (
	"fmt"
	"path"

	"github.com/Microsoft/hcsshim/hcn"
	"github.com/Microsoft/hcsshim/internal/guestrequest"
	"github.com/Microsoft/hcsshim/internal/guid"
	"github.com/Microsoft/hcsshim/internal/hns"
	"github.com/Microsoft/hcsshim/internal/requesttype"
	"github.com/Microsoft/hcsshim/internal/schema1"
	"github.com/Microsoft/hcsshim/internal/schema2"
	"github.com/Microsoft/hcsshim/osversion"
	"github.com/sirupsen/logrus"
)

// AddNetNS adds network namespace inside the guest & adds endpoints to the guest on that namepace
func (uvm *UtilityVM) AddNetNS(id string, endpoints []*hns.HNSEndpoint) (err error) {
	uvm.m.Lock()
	defer uvm.m.Unlock()
	ns := uvm.namespaces[id]
	if ns == nil {
		ns = &namespaceInfo{}

		if uvm.isNetworkNamespaceSupported() {
			// Add a Guest Network namespace. Remove windows check when LCOW supports it
			if uvm.operatingSystem == "windows" {
				hcnNamespace, err := hcn.GetNamespaceByID(id)
				if err != nil {
					return err
				}
				guestNamespace := hcsschema.ModifySettingRequest{
					GuestRequest: guestrequest.GuestRequest{
						ResourceType: guestrequest.ResourceTypeNetworkNamespace,
						RequestType:  requesttype.Add,
						Settings:     hcnNamespace,
					},
				}
				if err := uvm.Modify(&guestNamespace); err != nil {
					return err
				}
			}
		}

		defer func() {
			if err != nil {
				if e := uvm.removeNamespaceNICs(ns); e != nil {
					logrus.Warnf("failed to undo NIC add: %v", e)
				}
			}
		}()
		for _, endpoint := range endpoints {
			nicID := guid.New()
			err = uvm.addNIC(nicID, endpoint)
			if err != nil {
				return err
			}
			ns.nics = append(ns.nics, nicInfo{nicID, endpoint})
		}
		if uvm.namespaces == nil {
			uvm.namespaces = make(map[string]*namespaceInfo)
		}
		uvm.namespaces[id] = ns
	}
	ns.refCount++
	return nil
}

//RemoveNetNS removes the namespace information
func (uvm *UtilityVM) RemoveNetNS(id string) error {
	uvm.m.Lock()
	defer uvm.m.Unlock()
	ns := uvm.namespaces[id]
	if ns == nil || ns.refCount <= 0 {
		panic(fmt.Errorf("removed a namespace that was not added: %s", id))
	}

	ns.refCount--

	// Remove the Guest Network namespace
	if uvm.isNetworkNamespaceSupported() {
		if uvm.operatingSystem == "windows" {
			hcnNamespace, err := hcn.GetNamespaceByID(id)
			if err != nil {
				return err
			}
			guestNamespace := hcsschema.ModifySettingRequest{
				GuestRequest: guestrequest.GuestRequest{
					ResourceType: guestrequest.ResourceTypeNetworkNamespace,
					RequestType:  requesttype.Remove,
					Settings:     hcnNamespace,
				},
			}
			if err := uvm.Modify(&guestNamespace); err != nil {
				return err
			}
		}
	}

	var err error
	if ns.refCount == 0 {
		err = uvm.removeNamespaceNICs(ns)
		delete(uvm.namespaces, id)
	}

	return err
}

// IsNetworkNamespaceSupported returns bool value specifying if network namespace is supported inside the guest
func (uvm *UtilityVM) isNetworkNamespaceSupported() bool {
	p, err := uvm.ComputeSystem().Properties(schema1.PropertyTypeGuestConnection)
	if err == nil {
		return p.GuestConnectionInfo.GuestDefinedCapabilities.NamespaceAddRequestSupported
	}

	return false
}

func (uvm *UtilityVM) removeNamespaceNICs(ns *namespaceInfo) error {
	for len(ns.nics) != 0 {
		nic := ns.nics[len(ns.nics)-1]
		err := uvm.removeNIC(nic.ID, nic.Endpoint)
		if err != nil {
			return err
		}
		ns.nics = ns.nics[:len(ns.nics)-1]
	}
	return nil
}

func getNetworkModifyRequest(adapterID string, requestType string, settings interface{}) interface{} {
	if osversion.Get().Build >= osversion.RS5 {
		return guestrequest.NetworkModifyRequest{
			AdapterId:   adapterID,
			RequestType: requestType,
			Settings:    settings,
		}
	}
	return guestrequest.RS4NetworkModifyRequest{
		AdapterInstanceId: adapterID,
		RequestType:       requestType,
		Settings:          settings,
	}
}

func (uvm *UtilityVM) addNIC(id guid.GUID, endpoint *hns.HNSEndpoint) error {

	// First a pre-add. This is a guest-only request and is only done on Windows.
	if uvm.operatingSystem == "windows" {
		preAddRequest := hcsschema.ModifySettingRequest{
			GuestRequest: guestrequest.GuestRequest{
				ResourceType: guestrequest.ResourceTypeNetwork,
				RequestType:  requesttype.Add,
				Settings: getNetworkModifyRequest(
					id.String(),
					requesttype.PreAdd,
					endpoint),
			},
		}
		if err := uvm.Modify(&preAddRequest); err != nil {
			return err
		}
	}

	// Then the Add itself
	request := hcsschema.ModifySettingRequest{
		RequestType:  requesttype.Add,
		ResourcePath: path.Join("VirtualMachine/Devices/NetworkAdapters", id.String()),
		Settings: hcsschema.NetworkAdapter{
			EndpointId: endpoint.Id,
			MacAddress: endpoint.MacAddress,
		},
	}

	if uvm.operatingSystem == "windows" {
		request.GuestRequest = guestrequest.GuestRequest{
			ResourceType: guestrequest.ResourceTypeNetwork,
			RequestType:  requesttype.Add,
			Settings: getNetworkModifyRequest(
				id.String(),
				requesttype.Add,
				nil),
		}
		// Uncomment this once we have GuestRequest support for Linux
		//} else {
		//	request.GuestRequest = guestrequest.GuestRequest{
		//		ResourceType: guestrequest.ResourceTypeNetwork,
		//		RequestType:  requesttype.Add,
		//		Settings:     endpoint,
		//	}
	}

	if err := uvm.Modify(&request); err != nil {
		return err
	}

	return nil
}

func (uvm *UtilityVM) removeNIC(id guid.GUID, endpoint *hns.HNSEndpoint) error {
	request := hcsschema.ModifySettingRequest{
		RequestType:  requesttype.Remove,
		ResourcePath: path.Join("VirtualMachine/Devices/NetworkAdapters", id.String()),
		Settings: hcsschema.NetworkAdapter{
			EndpointId: endpoint.Id,
			MacAddress: endpoint.MacAddress,
		},
	}

	if uvm.operatingSystem == "windows" {
		request.GuestRequest = hcsschema.ModifySettingRequest{
			RequestType: requesttype.Remove,
			Settings: getNetworkModifyRequest(
				id.String(),
				requesttype.Remove,
				nil),
		}
	} else {
		request.GuestRequest = guestrequest.GuestRequest{
			ResourceType: guestrequest.ResourceTypeNetwork,
			RequestType:  requesttype.Remove,
			Settings:     endpoint,
		}
	}

	if err := uvm.Modify(&request); err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package bot

import "fmt"
import "math"
import "strconv"
import "strings"
import "time"

const (
	ONE_SECOND = 1
	ONE_MINUTE = 60 * ONE_SECOND
	ONE_HOUR   = 60 * ONE_MINUTE
	ONE_DAY    = 24 * ONE_HOUR
	ONE_WEEK   = 7 * ONE_DAY
)

func twodigit(n int) string {
	if n < 10 {
		return "0" + strconv.Itoa(n)
	}

	return strconv.Itoa(n)
}

func plural(n int, word string) string {
	res := fmt.Sprintf("%d %s", n, word)

	if n != 1 {
		res = res + "s"
	}

	return res
}

func FormatDateAsSQL(t time.Time) string {
	return t.Format("2006-01-02")
}

func SecondsToTime(seconds int) string {
	return secondsToTime(seconds, false)
}

func SecondsToTimeCompact(seconds int) string {
	return secondsToTime(seconds, true)
}

func secondsToTime(seconds int, compact bool) string {
	weeks := seconds / ONE_WEEK
	seconds -= (weeks * ONE_WEEK)

	days := seconds / ONE_DAY
	seconds -= (days * ONE_DAY)

	hours := seconds / ONE_HOUR
	seconds -= (hours * ONE_HOUR)

	minutes := seconds / ONE_MINUTE
	seconds -= (minutes * ONE_MINUTE)

	list := make([]string, 0)

	if compact {
		if weeks > 0 {
			list = append(list, twodigit(weeks)+"w")
		}
		if len(list) > 0 || days > 0 {
			list = append(list, twodigit(days)+"d")
		}
		if len(list) > 0 || hours > 0 {
			list = append(list, twodigit(hours)+"h")
		}
		if len(list) > 0 || minutes > 0 {
			list = append(list, twodigit(minutes)+"m")
		}
		if len(list) > 0 || seconds > 0 {
			list = append(list, twodigit(seconds)+"s")
		}

		return strings.Join(list, ":")
	}

	if weeks > 0 {
		list = append(list, plural(weeks, "week"))
	}
	if days > 0 {
		list = append(list, plural(days, "day"))
	}
	if hours > 0 {
		list = append(list, plural(hours, "hour"))
	}
	if minutes > 0 {
		list = append(list, plural(minutes, "minute"))
	}
	if seconds > 0 {
		list = append(list, plural(seconds, "second"))
	}

	return HumanJoin(list, ", ")
}

func SecondsToRunTime(seconds float32) string {
	hours := int(seconds / ONE_HOUR)
	seconds -= float32(hours * ONE_HOUR)

	minutes := int(seconds / ONE_MINUTE)
	seconds -= float32(minutes * ONE_MINUTE)

	list := make([]string, 0)

	if hours > 0 {
		list = append(list, twodigit(hours))
	}
	if len(list) > 0 || minutes > 0 {
		list = append(list, twodigit(minutes))
	}
	if len(list) > 0 || seconds > 0 {
		list = append(list, twodigit(int(seconds)))
	}

	runtime := strings.TrimPrefix(strings.Join(list, ":"), "0")

	seconds -= float32(int(seconds) * ONE_SECOND)

	if seconds > 0.0001 {
		seconds *= 1000 // 0.12345 => 123.45
		seconds = float32(math.Floor(float64(seconds + 0.5)))
		runtime += fmt.Sprintf(".%d", int(seconds))
	}

	return runtime
}

func HumanJoin(list []string, glue string) string {
	if glue == "" {
		glue = ", "
	}

	l := len(list)

	switch l {
	case 0:
		return ""
	case 1:
		return list[0]
	default:
		return strings.Join(list[:(l-1)], glue) + " and " + list[l-1]
	}
}
<commit_msg>srapi does this one for us already<commit_after>package bot

import "fmt"

import "strconv"
import "strings"
import "time"

const (
	ONE_SECOND = 1
	ONE_MINUTE = 60 * ONE_SECOND
	ONE_HOUR   = 60 * ONE_MINUTE
	ONE_DAY    = 24 * ONE_HOUR
	ONE_WEEK   = 7 * ONE_DAY
)

func twodigit(n int) string {
	if n < 10 {
		return "0" + strconv.Itoa(n)
	}

	return strconv.Itoa(n)
}

func plural(n int, word string) string {
	res := fmt.Sprintf("%d %s", n, word)

	if n != 1 {
		res = res + "s"
	}

	return res
}

func FormatDateAsSQL(t time.Time) string {
	return t.Format("2006-01-02")
}

func SecondsToTime(seconds int) string {
	return secondsToTime(seconds, false)
}

func SecondsToTimeCompact(seconds int) string {
	return secondsToTime(seconds, true)
}

func secondsToTime(seconds int, compact bool) string {
	weeks := seconds / ONE_WEEK
	seconds -= (weeks * ONE_WEEK)

	days := seconds / ONE_DAY
	seconds -= (days * ONE_DAY)

	hours := seconds / ONE_HOUR
	seconds -= (hours * ONE_HOUR)

	minutes := seconds / ONE_MINUTE
	seconds -= (minutes * ONE_MINUTE)

	list := make([]string, 0)

	if compact {
		if weeks > 0 {
			list = append(list, twodigit(weeks)+"w")
		}
		if len(list) > 0 || days > 0 {
			list = append(list, twodigit(days)+"d")
		}
		if len(list) > 0 || hours > 0 {
			list = append(list, twodigit(hours)+"h")
		}
		if len(list) > 0 || minutes > 0 {
			list = append(list, twodigit(minutes)+"m")
		}
		if len(list) > 0 || seconds > 0 {
			list = append(list, twodigit(seconds)+"s")
		}

		return strings.Join(list, ":")
	}

	if weeks > 0 {
		list = append(list, plural(weeks, "week"))
	}
	if days > 0 {
		list = append(list, plural(days, "day"))
	}
	if hours > 0 {
		list = append(list, plural(hours, "hour"))
	}
	if minutes > 0 {
		list = append(list, plural(minutes, "minute"))
	}
	if seconds > 0 {
		list = append(list, plural(seconds, "second"))
	}

	return HumanJoin(list, ", ")
}

func HumanJoin(list []string, glue string) string {
	if glue == "" {
		glue = ", "
	}

	l := len(list)

	switch l {
	case 0:
		return ""
	case 1:
		return list[0]
	default:
		return strings.Join(list[:(l-1)], glue) + " and " + list[l-1]
	}
}
<|endoftext|>


<commit_before>package ole

import (
	"fmt"
	"testing"
)

func TestComSetupAndShutDown(t *testing.T) {
	defer func() {
		if r := recover(); r != nil {
			t.Log(r)
			t.Fail()
		}
	}()

	coInitialize()
	CoUninitialize()
}

func TestComPublicSetupAndShutDown(t *testing.T) {
	defer func() {
		if r := recover(); r != nil {
			t.Log(r)
			t.Fail()
		}
	}()

	CoInitialize(0)
	CoUninitialize()
}

func TestComPublicSetupAndShutDown_WithValue(t *testing.T) {
	defer func() {
		if r := recover(); r != nil {
			t.Log(r)
			t.Fail()
		}
	}()

	CoInitialize(5)
	CoUninitialize()
}

func TestComExSetupAndShutDown(t *testing.T) {
	defer func() {
		if r := recover(); r != nil {
			t.Log(r)
			t.Fail()
		}
	}()

	coInitializeEx(COINIT_MULTITHREADED)
	CoUninitialize()
}

func TestComPublicExSetupAndShutDown(t *testing.T) {
	defer func() {
		if r := recover(); r != nil {
			t.Log(r)
			t.Fail()
		}
	}()

	CoInitializeEx(0, COINIT_MULTITHREADED)
	CoUninitialize()
}

func TestComPublicExSetupAndShutDown_WithValue(t *testing.T) {
	defer func() {
		if r := recover(); r != nil {
			t.Log(r)
			t.Fail()
		}
	}()

	CoInitializeEx(5, COINIT_MULTITHREADED)
	CoUninitialize()
}

func TestClsidFromProgID_WindowsMediaNSSManager(t *testing.T) {
	defer func() {
		if r := recover(); r != nil {
			t.Log(r)
			t.Fail()
		}
	}()

	expected := &GUID{0x92498132, 0x4D1A, 0x4297, [8]byte{0x9B, 0x78, 0x9E, 0x2E, 0x4B, 0xA9, 0x9C, 0x07}}

	coInitialize()
	defer CoUninitialize()
	actual, err := CLSIDFromProgID("WMPNSSCI.NSSManager")
	if err == nil {
		if !IsEqualGUID(expected, actual) {
			t.Log(err)
			t.Log(fmt.Sprintf("Actual GUID: %+v\n", actual))
			t.Fail()
		}
	}
}

func TestClsidFromString_WindowsMediaNSSManager(t *testing.T) {
	defer func() {
		if r := recover(); r != nil {
			t.Log(r)
			t.Fail()
		}
	}()

	expected := &GUID{0x92498132, 0x4D1A, 0x4297, [8]byte{0x9B, 0x78, 0x9E, 0x2E, 0x4B, 0xA9, 0x9C, 0x07}}

	coInitialize()
	defer CoUninitialize()
	actual, err := CLSIDFromString("{92498132-4D1A-4297-9B78-9E2E4BA99C07}")

	if !IsEqualGUID(expected, actual) {
		t.Log(err)
		t.Log(fmt.Sprintf("Actual GUID: %+v\n", actual))
		t.Fail()
	}
}

func TestCreateInstance_WindowsMediaNSSManager(t *testing.T) {
	defer func() {
		if r := recover(); r != nil {
			t.Log(r)
			t.Fail()
		}
	}()

	expected := &GUID{0x92498132, 0x4D1A, 0x4297, [8]byte{0x9B, 0x78, 0x9E, 0x2E, 0x4B, 0xA9, 0x9C, 0x07}}

	coInitialize()
	defer CoUninitialize()
	actual, err := CLSIDFromProgID("WMPNSSCI.NSSManager")

	if err == nil {
		if !IsEqualGUID(expected, actual) {
			t.Log(err)
			t.Log(fmt.Sprintf("Actual GUID: %+v\n", actual))
			t.Fail()
		}

		unknown, err := CreateInstance(actual, IID_IUnknown)
		if err != nil {
			t.Log(err)
			t.Fail()
		}
		unknown.Release()
	}
}
<commit_msg>Add test for err<commit_after>package ole

import (
	"fmt"
	"testing"
)

func TestComSetupAndShutDown(t *testing.T) {
	defer func() {
		if r := recover(); r != nil {
			t.Log(r)
			t.Fail()
		}
	}()

	coInitialize()
	CoUninitialize()
}

func TestComPublicSetupAndShutDown(t *testing.T) {
	defer func() {
		if r := recover(); r != nil {
			t.Log(r)
			t.Fail()
		}
	}()

	CoInitialize(0)
	CoUninitialize()
}

func TestComPublicSetupAndShutDown_WithValue(t *testing.T) {
	defer func() {
		if r := recover(); r != nil {
			t.Log(r)
			t.Fail()
		}
	}()

	CoInitialize(5)
	CoUninitialize()
}

func TestComExSetupAndShutDown(t *testing.T) {
	defer func() {
		if r := recover(); r != nil {
			t.Log(r)
			t.Fail()
		}
	}()

	coInitializeEx(COINIT_MULTITHREADED)
	CoUninitialize()
}

func TestComPublicExSetupAndShutDown(t *testing.T) {
	defer func() {
		if r := recover(); r != nil {
			t.Log(r)
			t.Fail()
		}
	}()

	CoInitializeEx(0, COINIT_MULTITHREADED)
	CoUninitialize()
}

func TestComPublicExSetupAndShutDown_WithValue(t *testing.T) {
	defer func() {
		if r := recover(); r != nil {
			t.Log(r)
			t.Fail()
		}
	}()

	CoInitializeEx(5, COINIT_MULTITHREADED)
	CoUninitialize()
}

func TestClsidFromProgID_WindowsMediaNSSManager(t *testing.T) {
	defer func() {
		if r := recover(); r != nil {
			t.Log(r)
			t.Fail()
		}
	}()

	expected := &GUID{0x92498132, 0x4D1A, 0x4297, [8]byte{0x9B, 0x78, 0x9E, 0x2E, 0x4B, 0xA9, 0x9C, 0x07}}

	coInitialize()
	defer CoUninitialize()
	actual, err := CLSIDFromProgID("WMPNSSCI.NSSManager")
	if err == nil {
		if !IsEqualGUID(expected, actual) {
			t.Log(err)
			t.Log(fmt.Sprintf("Actual GUID: %+v\n", actual))
			t.Fail()
		}
	}
}

func TestClsidFromString_WindowsMediaNSSManager(t *testing.T) {
	defer func() {
		if r := recover(); r != nil {
			t.Log(r)
			t.Fail()
		}
	}()

	expected := &GUID{0x92498132, 0x4D1A, 0x4297, [8]byte{0x9B, 0x78, 0x9E, 0x2E, 0x4B, 0xA9, 0x9C, 0x07}}

	coInitialize()
	defer CoUninitialize()
	actual, err := CLSIDFromString("{92498132-4D1A-4297-9B78-9E2E4BA99C07}")

	if !IsEqualGUID(expected, actual) {
		t.Log(err)
		t.Log(fmt.Sprintf("Actual GUID: %+v\n", actual))
		t.Fail()
	}
}

func TestCreateInstance_WindowsMediaNSSManager(t *testing.T) {
	defer func() {
		if r := recover(); r != nil {
			t.Log(r)
			t.Fail()
		}
	}()

	expected := &GUID{0x92498132, 0x4D1A, 0x4297, [8]byte{0x9B, 0x78, 0x9E, 0x2E, 0x4B, 0xA9, 0x9C, 0x07}}

	coInitialize()
	defer CoUninitialize()
	actual, err := CLSIDFromProgID("WMPNSSCI.NSSManager")

	if err == nil {
		if !IsEqualGUID(expected, actual) {
			t.Log(err)
			t.Log(fmt.Sprintf("Actual GUID: %+v\n", actual))
			t.Fail()
		}

		unknown, err := CreateInstance(actual, IID_IUnknown)
		if err != nil {
			t.Log(err)
			t.Fail()
		}
		unknown.Release()
	}
}

func TestError(t *testing.T) {
	defer func() {
		if r := recover(); r != nil {
			t.Log(r)
			t.Fail()
		}
	}()

	coInitialize()
	defer CoUninitialize()
	_, err := CLSIDFromProgID("INTERFACE-NOT-FOUND")
	if err == nil {
		t.Fatalf("should be fail", err)
	}

	switch vt := err.(type) {
	case *OleError:
	default:
		t.Fatalf("should be *ole.OleError %t", vt)
	}
}
<|endoftext|>


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

package user

import (
	"bufio"
	"code.google.com/p/go.crypto/ssh"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/globocom/config"
	"github.com/globocom/gandalf/db"
	"github.com/globocom/gandalf/fs"
	"io"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
	"os"
	"os/user"
	"path"
	"strings"
	"syscall"
	"time"
)

var (
	ErrDuplicateKey = errors.New("Duplicate key")
	ErrInvalidKey   = errors.New("Invalid key")
	ErrKeyNotFound  = errors.New("Key not found")
)

type Key struct {
	Name      string
	Body      string
	Comment   string
	UserName  string
	CreatedAt time.Time
}

func newKey(name, user, raw string) (*Key, error) {
	key, comment, _, _, ok := ssh.ParseAuthorizedKey([]byte(raw))
	if !ok {
		return nil, ErrInvalidKey
	}
	body := ssh.MarshalAuthorizedKey(key)
	k := Key{
		Name:      name,
		Body:      string(body),
		Comment:   comment,
		UserName:  user,
		CreatedAt: time.Now(),
	}
	return &k, nil
}

func (k *Key) String() string {
	parts := make([]string, 1, 2)
	parts[0] = strings.TrimSpace(k.Body)
	if k.Comment != "" {
		parts = append(parts, k.Comment)
	}
	return strings.Join(parts, " ")
}

func (k *Key) format() string {
	binPath, err := config.GetString("bin-path")
	if err != nil {
		panic(err)
	}
	keyFmt := `no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command="%s %s" %s` + "\n"
	return fmt.Sprintf(keyFmt, binPath, k.UserName, k)
}

func (k *Key) dump(w io.Writer) error {
	formatted := k.format()
	n, err := fmt.Fprint(w, formatted)
	if err != nil {
		return err
	}
	if n != len(formatted) {
		return io.ErrShortWrite
	}
	return nil
}

// authKey returns the file to write user's keys.
func authKey() string {
	var home string
	if current, err := user.Current(); err == nil {
		home = current.HomeDir
	} else {
		home = os.ExpandEnv("$HOME")
	}
	return path.Join(home, ".ssh", "authorized_keys")
}

// writeKeys serializes the given key in the authorized_keys file (of the
// current user).
func writeKey(k *Key) error {
	file, err := fs.Filesystem().OpenFile(authKey(), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
	if err != nil {
		return err
	}
	defer file.Close()
	syscall.Flock(int(file.Fd()), syscall.LOCK_EX)
	defer syscall.Flock(int(file.Fd()), syscall.LOCK_UN)
	return k.dump(file)
}

// Writes `key` in authorized_keys file (from current user)
// It does not writes in the database, there is no need for that since the key
// object is embedded on the user's document
func addKey(name, body, username string) error {
	key, err := newKey(name, username, body)
	if err != nil {
		return err
	}
	err = db.Session.Key().Insert(key)
	if err != nil {
		if e, ok := err.(*mgo.LastError); ok && e.Code == 11000 {
			return ErrDuplicateKey
		}
		return err
	}
	return writeKey(key)
}

func addKeys(keys map[string]string, username string) error {
	for name, k := range keys {
		err := addKey(name, k, username)
		if err != nil {
			return err
		}
	}
	return nil
}

func remove(k *Key) error {
	formatted := k.format()
	file, err := fs.Filesystem().OpenFile(authKey(), os.O_RDWR|os.O_EXCL, 0644)
	if err != nil {
		return err
	}
	defer file.Close()
	lines := make([]string, 0, 10)
	reader := bufio.NewReader(file)
	line, _ := reader.ReadString('\n')
	for line != "" {
		if line != formatted {
			lines = append(lines, line)
		}
		line, _ = reader.ReadString('\n')
	}
	file.Truncate(0)
	file.Seek(0, 0)
	content := strings.Join(lines, "")
	n, err := file.WriteString(content)
	if err != nil {
		return err
	}
	if n != len(content) {
		return io.ErrShortWrite
	}
	return nil
}

func removeUserKeys(username string) error {
	var keys []Key
	q := bson.M{"username": username}
	err := db.Session.Key().Find(q).All(&keys)
	if err != nil {
		return err
	}
	db.Session.Key().RemoveAll(q)
	for _, k := range keys {
		remove(&k)
	}
	return nil
}

// removes a key from the database and the authorized_keys file.
func removeKey(name, username string) error {
	var k Key
	err := db.Session.Key().Find(bson.M{"name": name, "username": username}).One(&k)
	if err != nil {
		return ErrKeyNotFound
	}
	db.Session.Key().Remove(k)
	return remove(&k)
}

type KeyList []Key

func (keys KeyList) MarshalJSON() ([]byte, error) {
	m := make(map[string]string, len(keys))
	for _, key := range keys {
		m[key.Name] = key.String()
	}
	return json.Marshal(m)
}

// ListKeys lists all user's keys.
//
// If the user is not found, returns an error
func ListKeys(uName string) (KeyList, error) {
	if n, err := db.Session.User().FindId(uName).Count(); err != nil || n != 1 {
		return nil, ErrUserNotFound
	}
	var keys []Key
	err := db.Session.Key().Find(bson.M{"username": uName}).All(&keys)
	return KeyList(keys), err
}
<commit_msg>user: convert ParseAuthorizedKey result to use in MarshalAuthorizedKey<commit_after>// Copyright 2013 gandalf authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package user

import (
	"bufio"
	"code.google.com/p/go.crypto/ssh"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/globocom/config"
	"github.com/globocom/gandalf/db"
	"github.com/globocom/gandalf/fs"
	"io"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
	"os"
	"os/user"
	"path"
	"strings"
	"syscall"
	"time"
)

var (
	ErrDuplicateKey = errors.New("Duplicate key")
	ErrInvalidKey   = errors.New("Invalid key")
	ErrKeyNotFound  = errors.New("Key not found")
)

type Key struct {
	Name      string
	Body      string
	Comment   string
	UserName  string
	CreatedAt time.Time
}

func newKey(name, user, raw string) (*Key, error) {
	key, comment, _, _, ok := ssh.ParseAuthorizedKey([]byte(raw))
	if !ok {
		return nil, ErrInvalidKey
	}
	body := ssh.MarshalAuthorizedKey(key.(ssh.PublicKey))
	k := Key{
		Name:      name,
		Body:      string(body),
		Comment:   comment,
		UserName:  user,
		CreatedAt: time.Now(),
	}
	return &k, nil
}

func (k *Key) String() string {
	parts := make([]string, 1, 2)
	parts[0] = strings.TrimSpace(k.Body)
	if k.Comment != "" {
		parts = append(parts, k.Comment)
	}
	return strings.Join(parts, " ")
}

func (k *Key) format() string {
	binPath, err := config.GetString("bin-path")
	if err != nil {
		panic(err)
	}
	keyFmt := `no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command="%s %s" %s` + "\n"
	return fmt.Sprintf(keyFmt, binPath, k.UserName, k)
}

func (k *Key) dump(w io.Writer) error {
	formatted := k.format()
	n, err := fmt.Fprint(w, formatted)
	if err != nil {
		return err
	}
	if n != len(formatted) {
		return io.ErrShortWrite
	}
	return nil
}

// authKey returns the file to write user's keys.
func authKey() string {
	var home string
	if current, err := user.Current(); err == nil {
		home = current.HomeDir
	} else {
		home = os.ExpandEnv("$HOME")
	}
	return path.Join(home, ".ssh", "authorized_keys")
}

// writeKeys serializes the given key in the authorized_keys file (of the
// current user).
func writeKey(k *Key) error {
	file, err := fs.Filesystem().OpenFile(authKey(), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
	if err != nil {
		return err
	}
	defer file.Close()
	syscall.Flock(int(file.Fd()), syscall.LOCK_EX)
	defer syscall.Flock(int(file.Fd()), syscall.LOCK_UN)
	return k.dump(file)
}

// Writes `key` in authorized_keys file (from current user)
// It does not writes in the database, there is no need for that since the key
// object is embedded on the user's document
func addKey(name, body, username string) error {
	key, err := newKey(name, username, body)
	if err != nil {
		return err
	}
	err = db.Session.Key().Insert(key)
	if err != nil {
		if e, ok := err.(*mgo.LastError); ok && e.Code == 11000 {
			return ErrDuplicateKey
		}
		return err
	}
	return writeKey(key)
}

func addKeys(keys map[string]string, username string) error {
	for name, k := range keys {
		err := addKey(name, k, username)
		if err != nil {
			return err
		}
	}
	return nil
}

func remove(k *Key) error {
	formatted := k.format()
	file, err := fs.Filesystem().OpenFile(authKey(), os.O_RDWR|os.O_EXCL, 0644)
	if err != nil {
		return err
	}
	defer file.Close()
	lines := make([]string, 0, 10)
	reader := bufio.NewReader(file)
	line, _ := reader.ReadString('\n')
	for line != "" {
		if line != formatted {
			lines = append(lines, line)
		}
		line, _ = reader.ReadString('\n')
	}
	file.Truncate(0)
	file.Seek(0, 0)
	content := strings.Join(lines, "")
	n, err := file.WriteString(content)
	if err != nil {
		return err
	}
	if n != len(content) {
		return io.ErrShortWrite
	}
	return nil
}

func removeUserKeys(username string) error {
	var keys []Key
	q := bson.M{"username": username}
	err := db.Session.Key().Find(q).All(&keys)
	if err != nil {
		return err
	}
	db.Session.Key().RemoveAll(q)
	for _, k := range keys {
		remove(&k)
	}
	return nil
}

// removes a key from the database and the authorized_keys file.
func removeKey(name, username string) error {
	var k Key
	err := db.Session.Key().Find(bson.M{"name": name, "username": username}).One(&k)
	if err != nil {
		return ErrKeyNotFound
	}
	db.Session.Key().Remove(k)
	return remove(&k)
}

type KeyList []Key

func (keys KeyList) MarshalJSON() ([]byte, error) {
	m := make(map[string]string, len(keys))
	for _, key := range keys {
		m[key.Name] = key.String()
	}
	return json.Marshal(m)
}

// ListKeys lists all user's keys.
//
// If the user is not found, returns an error
func ListKeys(uName string) (KeyList, error) {
	if n, err := db.Session.User().FindId(uName).Count(); err != nil || n != 1 {
		return nil, ErrUserNotFound
	}
	var keys []Key
	err := db.Session.Key().Find(bson.M{"username": uName}).All(&keys)
	return KeyList(keys), err
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"os"

	"github.com/awslabs/aws-sdk-go/aws"
	"github.com/awslabs/aws-sdk-go/aws/awsutil"
	"github.com/awslabs/aws-sdk-go/service/ec2"
	"github.com/codegangsta/cli"

	myaws "github.com/y-uuki/grabeni/aws"
)

var Commands = []cli.Command{
	commandStatus,
	commandGrab,
	commandAttach,
	commandDetach,
}

var commandStatus = cli.Command{
	Name:  "status",
	Usage: "Show ENI status",
	Description: `
`,
	Action: doStatus,
	Flags: []cli.Flag{
		cli.BoolFlag{Name: "n, nametag", Usage: "ENI Tag Name"},
	},
}

var commandGrab = cli.Command{
	Name:  "grab",
	Usage: "",
	Description: `
`,
	Action: doGrab,
}

var commandAttach = cli.Command{
	Name:  "attach",
	Usage: "",
	Description: `
`,
	Action: doAttach,
}

var commandDetach = cli.Command{
	Name:  "detach",
	Usage: "",
	Description: `
`,
	Action: doDetach,
}

func debug(v ...interface{}) {
	if os.Getenv("DEBUG") != "" {
		log.Println(v...)
	}
}

func assert(err error) {
	if err != nil {
		log.Fatal(err)
	}
}

func doStatus(c *cli.Context) {
	if len(c.Args()) < 1 {
		cli.ShowCommandHelp(c, "status")
		os.Exit(1)
	}

	eniID := c.Args()[0]
	if eniID == "" {
		cli.ShowCommandHelp(c, "status")
		os.Exit(1)
	}

	region, err := myaws.GetRegion()
	if err != nil {
		assert(err)
		os.Exit(1)
	}

	svc := ec2.New(&aws.Config{Region: region})

	params := &ec2.DescribeNetworkInterfacesInput{
		NetworkInterfaceIDs: []*string{
			aws.String(eniID),
		},
	}
	resp, err := svc.DescribeNetworkInterfaces(params)
	if awserr := aws.Error(err); awserr != nil {
		// A service error occurred.
		fmt.Println("Error:", awserr.Code, awserr.Message)
		os.Exit(1)
	} else if err != nil {
		// A non-service error occurred.
		assert(err)
		os.Exit(1)
	}

	fmt.Println(awsutil.StringValue(resp))
}

func doGrab(c *cli.Context) {
}

func doAttach(c *cli.Context) {
}

func doDetach(c *cli.Context) {
}
<commit_msg>Pretty output about status<commit_after>package main

import (
	"fmt"
	"log"
	"os"

	"github.com/awslabs/aws-sdk-go/aws"
	"github.com/awslabs/aws-sdk-go/service/ec2"
	"github.com/codegangsta/cli"

	myaws "github.com/y-uuki/grabeni/aws"
)

var Commands = []cli.Command{
	commandStatus,
	commandGrab,
	commandAttach,
	commandDetach,
}

var commandStatus = cli.Command{
	Name:  "status",
	Usage: "Show ENI status",
	Description: `
`,
	Action: doStatus,
	Flags: []cli.Flag{
		cli.BoolFlag{Name: "n, nametag", Usage: "ENI Tag Name"},
	},
}

var commandGrab = cli.Command{
	Name:  "grab",
	Usage: "",
	Description: `
`,
	Action: doGrab,
}

var commandAttach = cli.Command{
	Name:  "attach",
	Usage: "",
	Description: `
`,
	Action: doAttach,
}

var commandDetach = cli.Command{
	Name:  "detach",
	Usage: "",
	Description: `
`,
	Action: doDetach,
}

func debug(v ...interface{}) {
	if os.Getenv("DEBUG") != "" {
		log.Println(v...)
	}
}

func assert(err error) {
	if err != nil {
		log.Fatal(err)
	}
}

func doStatus(c *cli.Context) {
	if len(c.Args()) < 1 {
		cli.ShowCommandHelp(c, "status")
		os.Exit(1)
	}

	eniID := c.Args()[0]
	if eniID == "" {
		cli.ShowCommandHelp(c, "status")
		os.Exit(1)
	}

	region, err := myaws.GetRegion()
	if err != nil {
		assert(err)
		os.Exit(1)
	}

	svc := ec2.New(&aws.Config{Region: region})

	params := &ec2.DescribeNetworkInterfacesInput{
		NetworkInterfaceIDs: []*string{
			aws.String(eniID),
		},
	}
	resp, err := svc.DescribeNetworkInterfaces(params)
	if awserr := aws.Error(err); awserr != nil {
		// A service error occurred.
		fmt.Println("Error:", awserr.Code, awserr.Message)
		os.Exit(1)
	} else if err != nil {
		// A non-service error occurred.
		assert(err)
		os.Exit(1)
	}

	eni := resp.NetworkInterfaces[0]
	name := ""
	if len(eni.TagSet) > 0 {
		for _, tag := range eni.TagSet {
			if *tag.Key == "Name" {
				name = *tag.Value
				break
			}
		}
	}

	fmt.Println("Name\tNetworkInterfaceID\tPrivateDNSName\tPrivateIPAddress\tInstanceID\tDeviceIndex\tStatus")
	fmt.Printf("%s\t%s\t%s\t%s\t%s\t%d\t%s\t\n",
		name,
		*eni.NetworkInterfaceID,
		*eni.PrivateDNSName,
		*eni.PrivateIPAddress,
		*eni.Attachment.InstanceID,
		*eni.Attachment.DeviceIndex,
		*eni.Status,
	)
}

func doGrab(c *cli.Context) {
}

func doAttach(c *cli.Context) {
}

func doDetach(c *cli.Context) {
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"fmt"
	"os"
	"os/exec"
	"runtime"
	"strings"
	"syscall"

	"github.com/codegangsta/cli"
	"github.com/motemen/ghq/utils"
)

var Commands = []cli.Command{
	commandGet,
	commandList,
	commandLook,
	commandImport,
	commandRoot,
}

var cloneFlags = []cli.Flag{
	cli.BoolFlag{Name: "update, u", Usage: "Update local repository if cloned already"},
	cli.BoolFlag{Name: "p", Usage: "Clone with SSH"},
	cli.BoolFlag{Name: "shallow", Usage: "Do a shallow clone"},
}

var commandGet = cli.Command{
	Name:  "get",
	Usage: "Clone/sync with a remote repository",
	Description: `
    Clone a GitHub repository under ghq root direcotry. If the repository is
    already cloned to local, nothing will happen unless '-u' ('--update')
    flag is supplied, in which case 'git remote update' is executed.
    When you use '-p' option, the repository is cloned via SSH.
`,
	Action: doGet,
	Flags:  cloneFlags,
}

var commandList = cli.Command{
	Name:  "list",
	Usage: "List local repositories",
	Description: `
    List locally cloned repositories. If a query argument is given, only
    repositories whose names contain that query text are listed. '-e'
    ('--exact') forces the match to be an exact one (i.e. the query equals to
    _project_ or _user_/_project_) If '-p' ('--full-path') is given, the full paths
    to the repository root are printed instead of relative ones.
`,
	Action: doList,
	Flags: []cli.Flag{
		cli.BoolFlag{Name: "exact, e", Usage: "Perform an exact match"},
		cli.BoolFlag{Name: "full-path, p", Usage: "Print full paths"},
		cli.BoolFlag{Name: "unique", Usage: "Print unique subpaths"},
	},
}

var commandLook = cli.Command{
	Name:  "look",
	Usage: "Look into a local repository",
	Description: `
    Look into a locally cloned repository with the shell.
`,
	Action: doLook,
}

var commandImport = cli.Command{
	Name:   "import",
	Usage:  "Bulk get repositories from a file or stdin",
	Action: doImport,
	Flags:  cloneFlags,
}

var commandRoot = cli.Command{
	Name:   "root",
	Usage:  "Returns repositories' root",
	Action: doRoot,
}

type commandDoc struct {
	Parent    string
	Arguments string
}

var commandDocs = map[string]commandDoc{
	"get":    {"", "[-u] <repository URL> | [-u] [-p] <user>/<project>"},
	"list":   {"", "[-p] [-e] [<query>]"},
	"look":   {"", "<project> | <user>/<project> | <host>/<user>/<project>"},
	"import": {"", "< file"},
	"root":   {"", ""},
}

// Makes template conditionals to generate per-command documents.
func mkCommandsTemplate(genTemplate func(commandDoc) string) string {
	template := "{{if false}}"
	for _, command := range append(Commands) {
		template = template + fmt.Sprintf("{{else if (eq .Name %q)}}%s", command.Name, genTemplate(commandDocs[command.Name]))
	}
	return template + "{{end}}"
}

func init() {
	argsTemplate := mkCommandsTemplate(func(doc commandDoc) string { return doc.Arguments })
	parentTemplate := mkCommandsTemplate(func(doc commandDoc) string { return string(strings.TrimLeft(doc.Parent+" ", " ")) })

	cli.CommandHelpTemplate = `NAME:
    {{.Name}} - {{.Usage}}

USAGE:
    ghq ` + parentTemplate + `{{.Name}} ` + argsTemplate + `
{{if (len .Description)}}
DESCRIPTION: {{.Description}}
{{end}}{{if (len .Flags)}}
OPTIONS:
    {{range .Flags}}{{.}}
    {{end}}
{{end}}`
}

func doGet(c *cli.Context) {
	argURL := c.Args().Get(0)
	doUpdate := c.Bool("update")
	isShallow := c.Bool("shallow")

	if argURL == "" {
		cli.ShowCommandHelp(c, "get")
		os.Exit(1)
	}

	url, err := NewURL(argURL)
	utils.DieIf(err)

	isSSH := c.Bool("p")
	if isSSH {
		// Assume Git repository if `-p` is given.
		url, err = ConvertGitURLHTTPToSSH(url)
		utils.DieIf(err)
	}

	remote, err := NewRemoteRepository(url)
	utils.DieIf(err)

	if remote.IsValid() == false {
		utils.Log("error", fmt.Sprintf("Not a valid repository: %s", url))
		os.Exit(1)
	}

	getRemoteRepository(remote, doUpdate, isShallow)
}

// getRemoteRepository clones or updates a remote repository remote.
// If doUpdate is true, updates the locally cloned repository. Otherwise does nothing.
// If isShallow is true, does shallow cloning. (no effect if already cloned or the VCS is Mercurial)
func getRemoteRepository(remote RemoteRepository, doUpdate bool, isShallow bool) {
	remoteURL := remote.URL()
	local := LocalRepositoryFromURL(remoteURL)

	path := local.FullPath
	newPath := false

	_, err := os.Stat(path)
	if err != nil {
		if os.IsNotExist(err) {
			newPath = true
			err = nil
		}
		utils.PanicIf(err)
	}

	if newPath {
		utils.Log("clone", fmt.Sprintf("%s -> %s", remoteURL, path))

		vcs := remote.VCS()
		if vcs == nil {
			utils.Log("error", fmt.Sprintf("Could not find version control system: %s", remoteURL))
			os.Exit(1)
		}

		vcs.Clone(remoteURL, path, isShallow)
	} else {
		if doUpdate {
			utils.Log("update", path)
			local.VCS().Update(path)
		} else {
			utils.Log("exists", path)
		}
	}
}

func doList(c *cli.Context) {
	query := c.Args().First()
	exact := c.Bool("exact")
	printFullPaths := c.Bool("full-path")
	printUniquePaths := c.Bool("unique")

	var filterFn func(*LocalRepository) bool
	if query == "" {
		filterFn = func(_ *LocalRepository) bool {
			return true
		}
	} else if exact {
		filterFn = func(repo *LocalRepository) bool {
			return repo.Matches(query)
		}
	} else {
		filterFn = func(repo *LocalRepository) bool {
			return strings.Contains(repo.NonHostPath(), query)
		}
	}

	repos := []*LocalRepository{}

	walkLocalRepositories(func(repo *LocalRepository) {
		if filterFn(repo) == false {
			return
		}

		repos = append(repos, repo)
	})

	if printUniquePaths {
		subpathCount := map[string]int{} // Count duplicated subpaths (ex. foo/dotfiles and bar/dotfiles)
		reposCount := map[string]int{}   // Check duplicated repositories among roots

		// Primary first
		for _, repo := range repos {
			if reposCount[repo.RelPath] == 0 {
				for _, p := range repo.Subpaths() {
					subpathCount[p] = subpathCount[p] + 1
				}
			}

			reposCount[repo.RelPath] = reposCount[repo.RelPath] + 1
		}

		for _, repo := range repos {
			if reposCount[repo.RelPath] > 1 && repo.IsUnderPrimaryRoot() == false {
				continue
			}

			for _, p := range repo.Subpaths() {
				if subpathCount[p] == 1 {
					fmt.Println(p)
					break
				}
			}
		}
	} else {
		for _, repo := range repos {
			if printFullPaths {
				fmt.Println(repo.FullPath)
			} else {
				fmt.Println(repo.RelPath)
			}
		}
	}
}

func doLook(c *cli.Context) {
	name := c.Args().First()

	if name == "" {
		cli.ShowCommandHelp(c, "look")
		os.Exit(1)
	}

	reposFound := []*LocalRepository{}
	walkLocalRepositories(func(repo *LocalRepository) {
		if repo.Matches(name) {
			reposFound = append(reposFound, repo)
		}
	})

	switch len(reposFound) {
	case 0:
		utils.Log("error", "No repository found")
		os.Exit(1)

	case 1:
		if runtime.GOOS == "windows" {
			cmd := exec.Command(os.Getenv("COMSPEC"))
			cmd.Stdin = os.Stdin
			cmd.Stdout = os.Stdout
			cmd.Stderr = os.Stderr
			cmd.Dir = reposFound[0].FullPath
			err := cmd.Start()
			if err == nil {
				cmd.Wait()
				os.Exit(0)
			}
		} else {
			shell := os.Getenv("SHELL")
			if shell == "" {
				shell = "/bin/sh"
			}

			utils.Log("cd", reposFound[0].FullPath)
			err := os.Chdir(reposFound[0].FullPath)
			utils.PanicIf(err)

			syscall.Exec(shell, []string{shell}, syscall.Environ())
		}

	default:
		utils.Log("error", "More than one repositories are found; Try more precise name")
		for _, repo := range reposFound {
			utils.Log("error", "- "+strings.Join(repo.PathParts, "/"))
		}
	}
}

func doImport(c *cli.Context) {
	var (
		doUpdate  = c.Bool("update")
		isSSH     = c.Bool("p")
		isShallow = c.Bool("shallow")
	)

	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		line := scanner.Text()
		url, err := NewURL(line)
		if err != nil {
			utils.Log("error", fmt.Sprintf("Could not parse URL <%s>: %s", line, err))
			continue
		}
		if isSSH {
			url, err = ConvertGitURLHTTPToSSH(url)
			if err != nil {
				utils.Log("error", fmt.Sprintf("Could not convert URL <%s>: %s", url, err))
				continue
			}
		}

		remote, err := NewRemoteRepository(url)
		if utils.ErrorIf(err) {
			continue
		}
		if remote.IsValid() == false {
			utils.Log("error", fmt.Sprintf("Not a valid repository: %s", url))
			continue
		}

		getRemoteRepository(remote, doUpdate, isShallow)
	}
	if err := scanner.Err(); err != nil {
		utils.Log("error", fmt.Sprintf("While reading input: %s", err))
		os.Exit(1)
	}
}

func doRoot(c *cli.Context) {
	fmt.Println(primaryLocalRepositoryRoot())
}
<commit_msg>Add --all option to the root command<commit_after>package main

import (
	"bufio"
	"fmt"
	"os"
	"os/exec"
	"runtime"
	"strings"
	"syscall"

	"github.com/codegangsta/cli"
	"github.com/motemen/ghq/utils"
)

var Commands = []cli.Command{
	commandGet,
	commandList,
	commandLook,
	commandImport,
	commandRoot,
}

var cloneFlags = []cli.Flag{
	cli.BoolFlag{Name: "update, u", Usage: "Update local repository if cloned already"},
	cli.BoolFlag{Name: "p", Usage: "Clone with SSH"},
	cli.BoolFlag{Name: "shallow", Usage: "Do a shallow clone"},
}

var commandGet = cli.Command{
	Name:  "get",
	Usage: "Clone/sync with a remote repository",
	Description: `
    Clone a GitHub repository under ghq root direcotry. If the repository is
    already cloned to local, nothing will happen unless '-u' ('--update')
    flag is supplied, in which case 'git remote update' is executed.
    When you use '-p' option, the repository is cloned via SSH.
`,
	Action: doGet,
	Flags:  cloneFlags,
}

var commandList = cli.Command{
	Name:  "list",
	Usage: "List local repositories",
	Description: `
    List locally cloned repositories. If a query argument is given, only
    repositories whose names contain that query text are listed. '-e'
    ('--exact') forces the match to be an exact one (i.e. the query equals to
    _project_ or _user_/_project_) If '-p' ('--full-path') is given, the full paths
    to the repository root are printed instead of relative ones.
`,
	Action: doList,
	Flags: []cli.Flag{
		cli.BoolFlag{Name: "exact, e", Usage: "Perform an exact match"},
		cli.BoolFlag{Name: "full-path, p", Usage: "Print full paths"},
		cli.BoolFlag{Name: "unique", Usage: "Print unique subpaths"},
	},
}

var commandLook = cli.Command{
	Name:  "look",
	Usage: "Look into a local repository",
	Description: `
    Look into a locally cloned repository with the shell.
`,
	Action: doLook,
}

var commandImport = cli.Command{
	Name:   "import",
	Usage:  "Bulk get repositories from a file or stdin",
	Action: doImport,
	Flags:  cloneFlags,
}

var commandRoot = cli.Command{
	Name:   "root",
	Usage:  "Returns repositories' root",
	Action: doRoot,
	Flags: []cli.Flag{
		cli.BoolFlag{Name: "all", Usage: "Return all roots"},
	},
}

type commandDoc struct {
	Parent    string
	Arguments string
}

var commandDocs = map[string]commandDoc{
	"get":    {"", "[-u] <repository URL> | [-u] [-p] <user>/<project>"},
	"list":   {"", "[-p] [-e] [<query>]"},
	"look":   {"", "<project> | <user>/<project> | <host>/<user>/<project>"},
	"import": {"", "< file"},
	"root":   {"", ""},
}

// Makes template conditionals to generate per-command documents.
func mkCommandsTemplate(genTemplate func(commandDoc) string) string {
	template := "{{if false}}"
	for _, command := range append(Commands) {
		template = template + fmt.Sprintf("{{else if (eq .Name %q)}}%s", command.Name, genTemplate(commandDocs[command.Name]))
	}
	return template + "{{end}}"
}

func init() {
	argsTemplate := mkCommandsTemplate(func(doc commandDoc) string { return doc.Arguments })
	parentTemplate := mkCommandsTemplate(func(doc commandDoc) string { return string(strings.TrimLeft(doc.Parent+" ", " ")) })

	cli.CommandHelpTemplate = `NAME:
    {{.Name}} - {{.Usage}}

USAGE:
    ghq ` + parentTemplate + `{{.Name}} ` + argsTemplate + `
{{if (len .Description)}}
DESCRIPTION: {{.Description}}
{{end}}{{if (len .Flags)}}
OPTIONS:
    {{range .Flags}}{{.}}
    {{end}}
{{end}}`
}

func doGet(c *cli.Context) {
	argURL := c.Args().Get(0)
	doUpdate := c.Bool("update")
	isShallow := c.Bool("shallow")

	if argURL == "" {
		cli.ShowCommandHelp(c, "get")
		os.Exit(1)
	}

	url, err := NewURL(argURL)
	utils.DieIf(err)

	isSSH := c.Bool("p")
	if isSSH {
		// Assume Git repository if `-p` is given.
		url, err = ConvertGitURLHTTPToSSH(url)
		utils.DieIf(err)
	}

	remote, err := NewRemoteRepository(url)
	utils.DieIf(err)

	if remote.IsValid() == false {
		utils.Log("error", fmt.Sprintf("Not a valid repository: %s", url))
		os.Exit(1)
	}

	getRemoteRepository(remote, doUpdate, isShallow)
}

// getRemoteRepository clones or updates a remote repository remote.
// If doUpdate is true, updates the locally cloned repository. Otherwise does nothing.
// If isShallow is true, does shallow cloning. (no effect if already cloned or the VCS is Mercurial)
func getRemoteRepository(remote RemoteRepository, doUpdate bool, isShallow bool) {
	remoteURL := remote.URL()
	local := LocalRepositoryFromURL(remoteURL)

	path := local.FullPath
	newPath := false

	_, err := os.Stat(path)
	if err != nil {
		if os.IsNotExist(err) {
			newPath = true
			err = nil
		}
		utils.PanicIf(err)
	}

	if newPath {
		utils.Log("clone", fmt.Sprintf("%s -> %s", remoteURL, path))

		vcs := remote.VCS()
		if vcs == nil {
			utils.Log("error", fmt.Sprintf("Could not find version control system: %s", remoteURL))
			os.Exit(1)
		}

		vcs.Clone(remoteURL, path, isShallow)
	} else {
		if doUpdate {
			utils.Log("update", path)
			local.VCS().Update(path)
		} else {
			utils.Log("exists", path)
		}
	}
}

func doList(c *cli.Context) {
	query := c.Args().First()
	exact := c.Bool("exact")
	printFullPaths := c.Bool("full-path")
	printUniquePaths := c.Bool("unique")

	var filterFn func(*LocalRepository) bool
	if query == "" {
		filterFn = func(_ *LocalRepository) bool {
			return true
		}
	} else if exact {
		filterFn = func(repo *LocalRepository) bool {
			return repo.Matches(query)
		}
	} else {
		filterFn = func(repo *LocalRepository) bool {
			return strings.Contains(repo.NonHostPath(), query)
		}
	}

	repos := []*LocalRepository{}

	walkLocalRepositories(func(repo *LocalRepository) {
		if filterFn(repo) == false {
			return
		}

		repos = append(repos, repo)
	})

	if printUniquePaths {
		subpathCount := map[string]int{} // Count duplicated subpaths (ex. foo/dotfiles and bar/dotfiles)
		reposCount := map[string]int{}   // Check duplicated repositories among roots

		// Primary first
		for _, repo := range repos {
			if reposCount[repo.RelPath] == 0 {
				for _, p := range repo.Subpaths() {
					subpathCount[p] = subpathCount[p] + 1
				}
			}

			reposCount[repo.RelPath] = reposCount[repo.RelPath] + 1
		}

		for _, repo := range repos {
			if reposCount[repo.RelPath] > 1 && repo.IsUnderPrimaryRoot() == false {
				continue
			}

			for _, p := range repo.Subpaths() {
				if subpathCount[p] == 1 {
					fmt.Println(p)
					break
				}
			}
		}
	} else {
		for _, repo := range repos {
			if printFullPaths {
				fmt.Println(repo.FullPath)
			} else {
				fmt.Println(repo.RelPath)
			}
		}
	}
}

func doLook(c *cli.Context) {
	name := c.Args().First()

	if name == "" {
		cli.ShowCommandHelp(c, "look")
		os.Exit(1)
	}

	reposFound := []*LocalRepository{}
	walkLocalRepositories(func(repo *LocalRepository) {
		if repo.Matches(name) {
			reposFound = append(reposFound, repo)
		}
	})

	switch len(reposFound) {
	case 0:
		utils.Log("error", "No repository found")
		os.Exit(1)

	case 1:
		if runtime.GOOS == "windows" {
			cmd := exec.Command(os.Getenv("COMSPEC"))
			cmd.Stdin = os.Stdin
			cmd.Stdout = os.Stdout
			cmd.Stderr = os.Stderr
			cmd.Dir = reposFound[0].FullPath
			err := cmd.Start()
			if err == nil {
				cmd.Wait()
				os.Exit(0)
			}
		} else {
			shell := os.Getenv("SHELL")
			if shell == "" {
				shell = "/bin/sh"
			}

			utils.Log("cd", reposFound[0].FullPath)
			err := os.Chdir(reposFound[0].FullPath)
			utils.PanicIf(err)

			syscall.Exec(shell, []string{shell}, syscall.Environ())
		}

	default:
		utils.Log("error", "More than one repositories are found; Try more precise name")
		for _, repo := range reposFound {
			utils.Log("error", "- "+strings.Join(repo.PathParts, "/"))
		}
	}
}

func doImport(c *cli.Context) {
	var (
		doUpdate  = c.Bool("update")
		isSSH     = c.Bool("p")
		isShallow = c.Bool("shallow")
	)

	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		line := scanner.Text()
		url, err := NewURL(line)
		if err != nil {
			utils.Log("error", fmt.Sprintf("Could not parse URL <%s>: %s", line, err))
			continue
		}
		if isSSH {
			url, err = ConvertGitURLHTTPToSSH(url)
			if err != nil {
				utils.Log("error", fmt.Sprintf("Could not convert URL <%s>: %s", url, err))
				continue
			}
		}

		remote, err := NewRemoteRepository(url)
		if utils.ErrorIf(err) {
			continue
		}
		if remote.IsValid() == false {
			utils.Log("error", fmt.Sprintf("Not a valid repository: %s", url))
			continue
		}

		getRemoteRepository(remote, doUpdate, isShallow)
	}
	if err := scanner.Err(); err != nil {
		utils.Log("error", fmt.Sprintf("While reading input: %s", err))
		os.Exit(1)
	}
}

func doRoot(c *cli.Context) {
	all := c.Bool("all")
	if all {
		for _, root := range localRepositoryRoots() {
			fmt.Println(root)
		}
	} else {
		fmt.Println(primaryLocalRepositoryRoot())
	}
}
<|endoftext|>