<commit_before>// Copyright (c) 2017 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package zanzibar

import (
	"bytes"
	"context"
	"encoding/json"
	"net/http"
	"time"

	"github.com/pkg/errors"
	"go.uber.org/zap"
)

// ClientHTTPRequest is the struct for making client
// requests using an outbound http client.
type ClientHTTPRequest struct {
	ClientID   string
	MethodName string
	client     *HTTPClient
	httpReq    *http.Request
	res        *ClientHTTPResponse
	started    bool
	startTime  time.Time
	Logger     *zap.Logger
	metrics    *OutboundHTTPMetrics
	rawBody    []byte
}

// NewClientHTTPRequest allocates a ClientHTTPRequest
func NewClientHTTPRequest(
	clientID, methodName string,
	client *HTTPClient,
) *ClientHTTPRequest {
	req := &ClientHTTPRequest{
		ClientID:   clientID,
		MethodName: methodName,
		client:     client,
		Logger:     client.loggers[methodName],
		metrics:    client.metrics[methodName],
	}
	req.res = NewClientHTTPResponse(req)
	req.start()
	return req
}

// Start the request, do some metrics book keeping
func (req *ClientHTTPRequest) start() {
	if req.started {
		/* coverage ignore next line */
		req.Logger.Error("Cannot start ClientHTTPRequest twice")
		/* coverage ignore next line */
		return
	}
	req.started = true
	req.startTime = time.Now()

	// emit metrics
	req.metrics.Sent.Inc(1)
}

// WriteJSON will send a json http request out.
func (req *ClientHTTPRequest) WriteJSON(
	method, url string,
	headers map[string]string,
	body json.Marshaler,
) error {
	var httpReq *http.Request
	var httpErr error
	if body != nil {
		rawBody, err := body.MarshalJSON()
		if err != nil {
			req.Logger.Error("Could not serialize request json", zap.Error(err))
			return errors.Wrapf(
				err, "Could not serialize %s.%s request json",
				req.ClientID, req.MethodName,
			)
		}
		req.rawBody = rawBody
		httpReq, httpErr = http.NewRequest(method, url, bytes.NewReader(rawBody))
	} else {
		httpReq, httpErr = http.NewRequest(method, url, nil)
	}

	if httpErr != nil {
		req.Logger.Error("Could not create outbound request", zap.Error(httpErr))
		return errors.Wrapf(
			httpErr, "Could not create outbound %s.%s request",
			req.ClientID, req.MethodName,
		)
	}

	for k := range headers {
		httpReq.Header.Add(k, headers[k])
	}
	httpReq.Header.Set("Content-Type", "application/json")

	req.httpReq = httpReq
	return nil
}

// Do will send the request out.
func (req *ClientHTTPRequest) Do(
	ctx context.Context,
) (*ClientHTTPResponse, error) {
	res, err := req.client.Client.Do(req.httpReq.WithContext(ctx))
	if err != nil {
		req.Logger.Error("Could not make outbound request", zap.Error(err))
		return nil, err
	}

	req.res.setRawHTTPResponse(res)
	return req.res, nil
}
<commit_msg>runtime: add CheckHeaders to client http request<commit_after>// Copyright (c) 2017 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package zanzibar

import (
	"bytes"
	"context"
	"encoding/json"
	"net/http"
	"time"

	"github.com/pkg/errors"
	"go.uber.org/zap"
)

// ClientHTTPRequest is the struct for making client
// requests using an outbound http client.
type ClientHTTPRequest struct {
	ClientID   string
	MethodName string
	client     *HTTPClient
	httpReq    *http.Request
	res        *ClientHTTPResponse
	started    bool
	startTime  time.Time
	Logger     *zap.Logger
	metrics    *OutboundHTTPMetrics
	rawBody    []byte
}

// NewClientHTTPRequest allocates a ClientHTTPRequest
func NewClientHTTPRequest(
	clientID, methodName string,
	client *HTTPClient,
) *ClientHTTPRequest {
	req := &ClientHTTPRequest{
		ClientID:   clientID,
		MethodName: methodName,
		client:     client,
		Logger:     client.loggers[methodName],
		metrics:    client.metrics[methodName],
	}
	req.res = NewClientHTTPResponse(req)
	req.start()
	return req
}

// Start the request, do some metrics book keeping
func (req *ClientHTTPRequest) start() {
	if req.started {
		/* coverage ignore next line */
		req.Logger.Error("Cannot start ClientHTTPRequest twice")
		/* coverage ignore next line */
		return
	}
	req.started = true
	req.startTime = time.Now()

	// emit metrics
	req.metrics.Sent.Inc(1)
}

// CheckHeaders verifies that the outbound request contaisn required headers
func (req *ClientHTTPRequest) CheckHeaders(expected []string) error {
	if req.httpReq == nil {
		panic("must call `req.WriteJSON()` before `req.CheckHeaders()`")
	}

	actualHeaders := req.httpReq.Header

	for _, headerName := range expected {
		// TODO: case sensitivity ?
		headerValue := actualHeaders.Get(headerName)
		if headerValue == "" {
			req.Logger.Warn("Got outbound request without mandatory header",
				zap.String("headerName", headerName),
				zap.String("clientId", req.ClientID),
				zap.String("methodName", req.MethodName),
			)

			return errors.New("Missing mandatory header: " + headerName)
		}
	}

	return nil
}

// WriteJSON will send a json http request out.
func (req *ClientHTTPRequest) WriteJSON(
	method, url string,
	headers map[string]string,
	body json.Marshaler,
) error {
	var httpReq *http.Request
	var httpErr error
	if body != nil {
		rawBody, err := body.MarshalJSON()
		if err != nil {
			req.Logger.Error("Could not serialize request json", zap.Error(err))
			return errors.Wrapf(
				err, "Could not serialize %s.%s request json",
				req.ClientID, req.MethodName,
			)
		}
		req.rawBody = rawBody
		httpReq, httpErr = http.NewRequest(method, url, bytes.NewReader(rawBody))
	} else {
		httpReq, httpErr = http.NewRequest(method, url, nil)
	}

	if httpErr != nil {
		req.Logger.Error("Could not create outbound request", zap.Error(httpErr))
		return errors.Wrapf(
			httpErr, "Could not create outbound %s.%s request",
			req.ClientID, req.MethodName,
		)
	}

	for k := range headers {
		httpReq.Header.Add(k, headers[k])
	}
	httpReq.Header.Set("Content-Type", "application/json")

	req.httpReq = httpReq
	return nil
}

// Do will send the request out.
func (req *ClientHTTPRequest) Do(
	ctx context.Context,
) (*ClientHTTPResponse, error) {
	res, err := req.client.Client.Do(req.httpReq.WithContext(ctx))
	if err != nil {
		req.Logger.Error("Could not make outbound request", zap.Error(err))
		return nil, err
	}

	req.res.setRawHTTPResponse(res)
	return req.res, nil
}
<|endoftext|>


<commit_before>// Copyright 2015 Hajime Hoshi
//
// 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.

// +build example

package main

import (
	"fmt"
	"log"
	"math"

	"github.com/hajimehoshi/ebiten"
	"github.com/hajimehoshi/ebiten/audio"
	"github.com/hajimehoshi/ebiten/ebitenutil"
)

const (
	screenWidth  = 320
	screenHeight = 240
	sampleRate   = 44100
)

var audioContext *audio.Context

func init() {
	var err error
	audioContext, err = audio.NewContext(sampleRate)
	if err != nil {
		log.Fatal(err)
	}
}

var frames = 0

const (
	freqA  = 440.0
	freqAS = 466.2
	freqB  = 493.9
	freqC  = 523.3
	freqCS = 554.4
	freqD  = 587.3
	freqDS = 622.3
	freqE  = 659.3
	freqF  = 698.5
	freqFS = 740.0
	freqG  = 784.0
	freqGS = 830.6
)

// Twinkle, Twinkle, Little Star
const score = `CCGGAAGR FFEEDDCR GGFFEEDR GGFFEEDR CCGGAAGR FFEEDDCR`

var scoreIndex = 0

func square(out []int16, volume float64, freq float64, sequence float64) {
	if freq == 0 {
		for i := 0; i < len(out); i++ {
			out[i] = 0
		}
		return
	}
	length := int(float64(sampleRate) / freq)
	if length == 0 {
		panic("invalid freq")
	}
	for i := 0; i < len(out); i++ {
		a := int16(volume * math.MaxInt16)
		if i%length < int(float64(length)*sequence) {
			a = -a
		}
		out[i] = a
	}
}

func toBytes(l, r []int16) []byte {
	if len(l) != len(r) {
		panic("len(l) must equal to len(r)")
	}
	b := make([]byte, len(l)*4)
	for i := range l {
		b[4*i] = byte(l[i])
		b[4*i+1] = byte(l[i] >> 8)
		b[4*i+2] = byte(r[i])
		b[4*i+3] = byte(r[i] >> 8)
	}
	return b
}

func addNote() error {
	size := sampleRate / ebiten.FPS
	notes := []float64{freqC, freqD, freqE, freqF, freqG, freqA * 2, freqB * 2}

	defer func() {
		scoreIndex++
		scoreIndex %= len(score)
	}()
	l := make([]int16, size*30)
	r := make([]int16, size*30)
	note := score[scoreIndex]
	for note == ' ' {
		scoreIndex++
		scoreIndex %= len(score)
		note = score[scoreIndex]
	}
	freq := 0.0
	switch {
	case note == 'R':
		freq = 0
	case note <= 'B':
		freq = notes[int(note)+len(notes)-int('C')]
	default:
		freq = notes[note-'C']
	}
	vol := 1.0 / 16.0
	square(l, vol, freq, 0.25)
	square(r, vol, freq, 0.25)
	b := toBytes(l, r)
	p, err := audio.NewPlayerFromBytes(audioContext, b)
	if err != nil {
		return err
	}
	if err := p.Play(); err != nil {
		return err
	}
	return nil
}

func update(screen *ebiten.Image) error {
	defer func() {
		frames++
	}()
	if frames%30 == 0 {
		if err := addNote(); err != nil {
			return err
		}
	}
	ebitenutil.DebugPrint(screen, fmt.Sprintf("FPS: %0.2f", ebiten.CurrentFPS()))
	if err := audioContext.Update(); err != nil {
		return err
	}
	return nil
}

func main() {
	if err := ebiten.Run(update, screenWidth, screenHeight, 2, "PCM (Ebiten Demo)"); err != nil {
		log.Fatal(err)
	}
}
<commit_msg>examples/pcm: Improve debug messages<commit_after>// Copyright 2015 Hajime Hoshi
//
// 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.

// +build example

package main

import (
	"log"
	"math"

	"github.com/hajimehoshi/ebiten"
	"github.com/hajimehoshi/ebiten/audio"
	"github.com/hajimehoshi/ebiten/ebitenutil"
)

const (
	screenWidth  = 320
	screenHeight = 240
	sampleRate   = 44100
)

var audioContext *audio.Context

func init() {
	var err error
	audioContext, err = audio.NewContext(sampleRate)
	if err != nil {
		log.Fatal(err)
	}
}

var frames = 0

const (
	freqA  = 440.0
	freqAS = 466.2
	freqB  = 493.9
	freqC  = 523.3
	freqCS = 554.4
	freqD  = 587.3
	freqDS = 622.3
	freqE  = 659.3
	freqF  = 698.5
	freqFS = 740.0
	freqG  = 784.0
	freqGS = 830.6
)

// Twinkle, Twinkle, Little Star
const score = `CCGGAAGR FFEEDDCR GGFFEEDR GGFFEEDR CCGGAAGR FFEEDDCR`

var scoreIndex = 0

func square(out []int16, volume float64, freq float64, sequence float64) {
	if freq == 0 {
		for i := 0; i < len(out); i++ {
			out[i] = 0
		}
		return
	}
	length := int(float64(sampleRate) / freq)
	if length == 0 {
		panic("invalid freq")
	}
	for i := 0; i < len(out); i++ {
		a := int16(volume * math.MaxInt16)
		if i%length < int(float64(length)*sequence) {
			a = -a
		}
		out[i] = a
	}
}

func toBytes(l, r []int16) []byte {
	if len(l) != len(r) {
		panic("len(l) must equal to len(r)")
	}
	b := make([]byte, len(l)*4)
	for i := range l {
		b[4*i] = byte(l[i])
		b[4*i+1] = byte(l[i] >> 8)
		b[4*i+2] = byte(r[i])
		b[4*i+3] = byte(r[i] >> 8)
	}
	return b
}

func addNote() (rune, error) {
	size := sampleRate / ebiten.FPS
	notes := []float64{freqC, freqD, freqE, freqF, freqG, freqA * 2, freqB * 2}

	defer func() {
		scoreIndex++
		scoreIndex %= len(score)
	}()
	l := make([]int16, size*30)
	r := make([]int16, size*30)
	note := score[scoreIndex]
	for note == ' ' {
		scoreIndex++
		scoreIndex %= len(score)
		note = score[scoreIndex]
	}
	freq := 0.0
	switch {
	case note == 'R':
		freq = 0
	case note <= 'B':
		freq = notes[int(note)+len(notes)-int('C')]
	default:
		freq = notes[note-'C']
	}
	vol := 1.0 / 16.0
	square(l, vol, freq, 0.25)
	square(r, vol, freq, 0.25)
	b := toBytes(l, r)
	p, err := audio.NewPlayerFromBytes(audioContext, b)
	if err != nil {
		return 0, err
	}
	if err := p.Play(); err != nil {
		return 0, err
	}
	return rune(note), nil
}

var currentNote rune

func update(screen *ebiten.Image) error {
	if frames%30 == 0 {
		n, err := addNote()
		if err != nil {
			return err
		}
		currentNote = n
	}
	frames++
	if err := audioContext.Update(); err != nil {
		return err
	}
	if ebiten.IsRunningSlowly() {
		return nil
	}
	msg := "Note: "
	if currentNote == 'R' {
		msg += "-"
	} else {
		msg += string(currentNote)
	}
	ebitenutil.DebugPrint(screen, msg)
	return nil
}

func main() {
	if err := ebiten.Run(update, screenWidth, screenHeight, 2, "PCM (Ebiten Demo)"); err != nil {
		log.Fatal(err)
	}
}
<|endoftext|>


<commit_before>//go:generate struct-markdown

package common

import (
	"fmt"
	"log"
	"regexp"

	"github.com/hashicorp/packer/helper/config"
	"github.com/hashicorp/packer/template/interpolate"
)

// AMIConfig is for common configuration related to creating AMIs.
type AMIConfig struct {
	// The name of the resulting AMI that will appear when managing AMIs in the
	// AWS console or via APIs. This must be unique. To help make this unique,
	// use a function like timestamp (see [template
	// engine](../templates/engine.html) for more info).
	AMIName string `mapstructure:"ami_name" required:"true"`
	// The description to set for the resulting
	// AMI(s). By default this description is empty. This is a template
	// engine, see Build template
	// data for more information.
	AMIDescription string `mapstructure:"ami_description" required:"false"`
	// The description to set for the resulting AMI(s). By default this
	// description is empty. This is a [template
	// engine](../templates/engine.html), see [Build template
	// data](#build-template-data) for more information.
	AMIVirtType string `mapstructure:"ami_virtualization_type" required:"false"`
	// A list of account IDs that have access to
	// launch the resulting AMI(s). By default no additional users other than the
	// user creating the AMI has permissions to launch it.
	AMIUsers []string `mapstructure:"ami_users" required:"false"`
	// A list of groups that have access to
	// launch the resulting AMI(s). By default no groups have permission to launch
	// the AMI. all will make the AMI publicly accessible.
	AMIGroups []string `mapstructure:"ami_groups" required:"false"`
	// A list of product codes to
	// associate with the AMI. By default no product codes are associated with the
	// AMI.
	AMIProductCodes []string `mapstructure:"ami_product_codes" required:"false"`
	// A list of regions to copy the AMI to.
	// Tags and attributes are copied along with the AMI. AMI copying takes time
	// depending on the size of the AMI, but will generally take many minutes.
	AMIRegions []string `mapstructure:"ami_regions" required:"false"`
	// Set to true if you want to skip
	// validation of the ami_regions configuration option. Default false.
	AMISkipRegionValidation bool `mapstructure:"skip_region_validation" required:"false"`
	// Tags applied to the AMI. This is a
	// [template engine](/docs/templates/engine.html), see [Build template
	// data](#build-template-data) for more information.
	AMITags TagMap `mapstructure:"tags" required:"false"`
	// Enable enhanced networking (ENA but not SriovNetSupport) on
	// HVM-compatible AMIs. If set, add `ec2:ModifyInstanceAttribute` to your
	// AWS IAM policy.
	//
	// Note: you must make sure enhanced networking is enabled on your
	// instance. See [Amazon's documentation on enabling enhanced
	// networking](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/enhanced-networking.html#enabling_enhanced_networking).
	AMIENASupport config.Trilean `mapstructure:"ena_support" required:"false"`
	// Enable enhanced networking (SriovNetSupport but not ENA) on
	// HVM-compatible AMIs. If true, add `ec2:ModifyInstanceAttribute` to your
	// AWS IAM policy. Note: you must make sure enhanced networking is enabled
	// on your instance. See [Amazon's documentation on enabling enhanced
	// networking](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/enhanced-networking.html#enabling_enhanced_networking).
	// Default `false`.
	AMISriovNetSupport bool `mapstructure:"sriov_support" required:"false"`
	// Force Packer to first deregister an existing
	// AMI if one with the same name already exists. Default false.
	AMIForceDeregister bool `mapstructure:"force_deregister" required:"false"`
	// Force Packer to delete snapshots
	// associated with AMIs, which have been deregistered by force_deregister.
	// Default false.
	AMIForceDeleteSnapshot bool `mapstructure:"force_delete_snapshot" required:"false"`
	// Whether or not to encrypt the resulting AMI when
	// copying a provisioned instance to an AMI. By default, Packer will keep the
	// encryption setting to what it was in the source image. Setting false will
	// result in an unencrypted image, and true will result in an encrypted one.
	AMIEncryptBootVolume config.Trilean `mapstructure:"encrypt_boot" required:"false"`
	// ID, alias or ARN of the KMS key to use for boot volume encryption. This
	// only applies to the main `region`, other regions where the AMI will be
	// copied will be encrypted by the default EBS KMS key. For valid formats
	// see *KmsKeyId* in the [AWS API docs -
	// CopyImage](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CopyImage.html).
	// This field is validated by Packer, when using an alias, you will have to
	// prefix `kms_key_id` with `alias/`.
	AMIKmsKeyId string `mapstructure:"kms_key_id" required:"false"`
	// regions to copy the ami to, along with the custom kms key id (alias or
	// arn) to use for encryption for that region. Keys must match the regions
	// provided in `ami_regions`. If you just want to encrypt using a default
	// ID, you can stick with `kms_key_id` and `ami_regions`. If you want a
	// region to be encrypted with that region's default key ID, you can use an
	// empty string `""` instead of a key id in this map. (e.g. `"us-east-1":
	// ""`) However, you cannot use default key IDs if you are using this in
	// conjunction with `snapshot_users` -- in that situation you must use
	// custom keys. For valid formats see *KmsKeyId* in the [AWS API docs -
	// CopyImage](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CopyImage.html).
	//
	// This option supercedes the `kms_key_id` option -- if you set both, and
	// they are different, Packer will respect the value in
	// `region_kms_key_ids` for your build region and silently disregard the
	// value provided in `kms_key_id`.
	AMIRegionKMSKeyIDs map[string]string `mapstructure:"region_kms_key_ids" required:"false"`
	// If true, Packer will not check whether an AMI with the `ami_name` exists
	// in the region it is building in. It will use an intermediary AMI name,
	// which it will not convert to an AMI in the build region. It will copy
	// the intermediary AMI into any regions provided in `ami_regions`, then
	// delete the intermediary AMI. Default `false`.
	AMISkipBuildRegion bool `mapstructure:"skip_save_build_region"`
	// Tags to apply to snapshot.
	// They will override AMI tags if already applied to snapshot. This is a
	// [template engine](../templates/engine.html), see [Build template
	// data](#build-template-data) for more information.
	SnapshotTags TagMap `mapstructure:"snapshot_tags" required:"false"`
	// A list of account IDs that have
	// access to create volumes from the snapshot(s). By default no additional
	// users other than the user creating the AMI has permissions to create
	// volumes from the backing snapshot(s).
	SnapshotUsers []string `mapstructure:"snapshot_users" required:"false"`
	// A list of groups that have access to
	// create volumes from the snapshot(s). By default no groups have permission
	// to create volumes from the snapshot(s). all will make the snapshot
	// publicly accessible.
	SnapshotGroups []string `mapstructure:"snapshot_groups" required:"false"`
}

func stringInSlice(s []string, searchstr string) bool {
	for _, item := range s {
		if item == searchstr {
			return true
		}
	}
	return false
}

func (c *AMIConfig) Prepare(accessConfig *AccessConfig, ctx *interpolate.Context) []error {
	var errs []error

	if c.AMIName == "" {
		errs = append(errs, fmt.Errorf("ami_name must be specified"))
	}

	// Make sure that if we have region_kms_key_ids defined,
	//  the regions in region_kms_key_ids are also in ami_regions
	if len(c.AMIRegionKMSKeyIDs) > 0 {
		for kmsKeyRegion := range c.AMIRegionKMSKeyIDs {
			if !stringInSlice(c.AMIRegions, kmsKeyRegion) {
				errs = append(errs, fmt.Errorf("Region %s is in region_kms_key_ids but not in ami_regions", kmsKeyRegion))
			}
		}
	}

	errs = append(errs, c.prepareRegions(accessConfig)...)

	// Prevent sharing of default KMS key encrypted volumes with other aws users
	if len(c.AMIUsers) > 0 {
		if len(c.AMIKmsKeyId) == 0 && c.AMIEncryptBootVolume.True() {
			errs = append(errs, fmt.Errorf("Cannot share AMI encrypted with default KMS key"))
		}
		if len(c.AMIRegionKMSKeyIDs) > 0 {
			for _, kmsKey := range c.AMIRegionKMSKeyIDs {
				if len(kmsKey) == 0 {
					errs = append(errs, fmt.Errorf("Cannot share AMI encrypted with default KMS key for other regions"))
				}
			}
		}
	}

	var kmsKeys []string
	if len(c.AMIKmsKeyId) > 0 {
		kmsKeys = append(kmsKeys, c.AMIKmsKeyId)
	}
	if len(c.AMIRegionKMSKeyIDs) > 0 {
		for _, kmsKey := range c.AMIRegionKMSKeyIDs {
			if len(kmsKey) == 0 {
				kmsKeys = append(kmsKeys, c.AMIKmsKeyId)
			}
		}
	}
	for _, kmsKey := range kmsKeys {
		if !validateKmsKey(kmsKey) {
			errs = append(errs, fmt.Errorf("%s is not a valid KMS Key Id.", kmsKey))
		}
	}

	if len(c.SnapshotUsers) > 0 {
		if len(c.AMIKmsKeyId) == 0 && c.AMIEncryptBootVolume.True() {
			errs = append(errs, fmt.Errorf("Cannot share snapshot encrypted with default KMS key"))
		}
		if len(c.AMIRegionKMSKeyIDs) > 0 {
			for _, kmsKey := range c.AMIRegionKMSKeyIDs {
				if len(kmsKey) == 0 {
					errs = append(errs, fmt.Errorf("Cannot share snapshot encrypted with default KMS key"))
				}
			}
		}
	}

	if len(c.AMIName) < 3 || len(c.AMIName) > 128 {
		errs = append(errs, fmt.Errorf("ami_name must be between 3 and 128 characters long"))
	}

	if c.AMIName != templateCleanAMIName(c.AMIName) {
		errs = append(errs, fmt.Errorf("AMIName should only contain "+
			"alphanumeric characters, parentheses (()), square brackets ([]), spaces "+
			"( ), periods (.), slashes (/), dashes (-), single quotes ('), at-signs "+
			"(@), or underscores(_). You can use the `clean_resource_name` template "+
			"filter to automatically clean your ami name."))
	}

	if len(errs) > 0 {
		return errs
	}

	return nil
}

func (c *AMIConfig) prepareRegions(accessConfig *AccessConfig) (errs []error) {
	if len(c.AMIRegions) > 0 {
		regionSet := make(map[string]struct{})
		regions := make([]string, 0, len(c.AMIRegions))

		for _, region := range c.AMIRegions {
			// If we already saw the region, then don't look again
			if _, ok := regionSet[region]; ok {
				continue
			}

			// Mark that we saw the region
			regionSet[region] = struct{}{}

			// Make sure that if we have region_kms_key_ids defined,
			// the regions in ami_regions are also in region_kms_key_ids
			if len(c.AMIRegionKMSKeyIDs) > 0 {
				if _, ok := c.AMIRegionKMSKeyIDs[region]; !ok {
					errs = append(errs, fmt.Errorf("Region %s is in ami_regions but not in region_kms_key_ids", region))
				}
			}
			if (accessConfig != nil) && (region == accessConfig.RawRegion) {
				// make sure we don't try to copy to the region we originally
				// create the AMI in.
				log.Printf("Cannot copy AMI to AWS session region '%s', deleting it from `ami_regions`.", region)
				continue
			}
			regions = append(regions, region)
		}

		c.AMIRegions = regions
	}
	return errs
}

// See https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CopyImage.html
func validateKmsKey(kmsKey string) (valid bool) {
	kmsKeyIdPattern := `[a-f0-9-]+$`
	aliasPattern := `alias/[a-zA-Z0-9:/_-]+$`
	kmsArnStartPattern := `^arn:aws(-us-gov)?:kms:([a-z]{2}-(gov-)?[a-z]+-\d{1})?:(\d{12}):`
	if regexp.MustCompile(fmt.Sprintf("^%s", kmsKeyIdPattern)).MatchString(kmsKey) {
		return true
	}
	if regexp.MustCompile(fmt.Sprintf("^%s", aliasPattern)).MatchString(kmsKey) {
		return true
	}
	if regexp.MustCompile(fmt.Sprintf("%skey/%s", kmsArnStartPattern, kmsKeyIdPattern)).MatchString(kmsKey) {
		return true
	}
	if regexp.MustCompile(fmt.Sprintf("%s%s", kmsArnStartPattern, aliasPattern)).MatchString(kmsKey) {
		return true
	}
	return false
}
<commit_msg>update docs<commit_after>//go:generate struct-markdown

package common

import (
	"fmt"
	"log"
	"regexp"

	"github.com/hashicorp/packer/helper/config"
	"github.com/hashicorp/packer/template/interpolate"
)

// AMIConfig is for common configuration related to creating AMIs.
type AMIConfig struct {
	// The name of the resulting AMI that will appear when managing AMIs in the
	// AWS console or via APIs. This must be unique. To help make this unique,
	// use a function like timestamp (see [template
	// engine](../templates/engine.html) for more info).
	AMIName string `mapstructure:"ami_name" required:"true"`
	// The description to set for the resulting
	// AMI(s). By default this description is empty. This is a template
	// engine, see Build template
	// data for more information.
	AMIDescription string `mapstructure:"ami_description" required:"false"`
	// The description to set for the resulting AMI(s). By default this
	// description is empty. This is a [template
	// engine](../templates/engine.html), see [Build template
	// data](#build-template-data) for more information.
	AMIVirtType string `mapstructure:"ami_virtualization_type" required:"false"`
	// A list of account IDs that have access to
	// launch the resulting AMI(s). By default no additional users other than the
	// user creating the AMI has permissions to launch it.
	AMIUsers []string `mapstructure:"ami_users" required:"false"`
	// A list of groups that have access to
	// launch the resulting AMI(s). By default no groups have permission to launch
	// the AMI. all will make the AMI publicly accessible.
	AMIGroups []string `mapstructure:"ami_groups" required:"false"`
	// A list of product codes to
	// associate with the AMI. By default no product codes are associated with the
	// AMI.
	AMIProductCodes []string `mapstructure:"ami_product_codes" required:"false"`
	// A list of regions to copy the AMI to.
	// Tags and attributes are copied along with the AMI. AMI copying takes time
	// depending on the size of the AMI, but will generally take many minutes.
	AMIRegions []string `mapstructure:"ami_regions" required:"false"`
	// Set to true if you want to skip
	// validation of the ami_regions configuration option. Default false.
	AMISkipRegionValidation bool `mapstructure:"skip_region_validation" required:"false"`
	// Tags applied to the AMI. This is a
	// [template engine](/docs/templates/engine.html), see [Build template
	// data](#build-template-data) for more information.
	AMITags TagMap `mapstructure:"tags" required:"false"`
	// Enable enhanced networking (ENA but not SriovNetSupport) on
	// HVM-compatible AMIs. If set, add `ec2:ModifyInstanceAttribute` to your
	// AWS IAM policy.
	//
	// Note: you must make sure enhanced networking is enabled on your
	// instance. See [Amazon's documentation on enabling enhanced
	// networking](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/enhanced-networking.html#enabling_enhanced_networking).
	AMIENASupport config.Trilean `mapstructure:"ena_support" required:"false"`
	// Enable enhanced networking (SriovNetSupport but not ENA) on
	// HVM-compatible AMIs. If true, add `ec2:ModifyInstanceAttribute` to your
	// AWS IAM policy. Note: you must make sure enhanced networking is enabled
	// on your instance. See [Amazon's documentation on enabling enhanced
	// networking](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/enhanced-networking.html#enabling_enhanced_networking).
	// Default `false`.
	AMISriovNetSupport bool `mapstructure:"sriov_support" required:"false"`
	// Force Packer to first deregister an existing
	// AMI if one with the same name already exists. Default false.
	AMIForceDeregister bool `mapstructure:"force_deregister" required:"false"`
	// Force Packer to delete snapshots
	// associated with AMIs, which have been deregistered by force_deregister.
	// Default false.
	AMIForceDeleteSnapshot bool `mapstructure:"force_delete_snapshot" required:"false"`
	// Whether or not to encrypt the resulting AMI when
	// copying a provisioned instance to an AMI. By default, Packer will keep the
	// encryption setting to what it was in the source image. Setting false will
	// result in an unencrypted image, and true will result in an encrypted one.
	// If you have used the `launch_block_device_mappings` to set an encryption
	// key and that key is the same as the one you want the image encrypted with
	// at the end, then you don't need to set this field; leaving it empty will
	// prevent an unnecessary extra copy step and save you some time.
	AMIEncryptBootVolume config.Trilean `mapstructure:"encrypt_boot" required:"false"`
	// ID, alias or ARN of the KMS key to use for boot volume encryption. This
	// only applies to the main `region`, other regions where the AMI will be
	// copied will be encrypted by the default EBS KMS key. For valid formats
	// see *KmsKeyId* in the [AWS API docs -
	// CopyImage](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CopyImage.html).
	// This field is validated by Packer, when using an alias, you will have to
	// prefix `kms_key_id` with `alias/`.
	AMIKmsKeyId string `mapstructure:"kms_key_id" required:"false"`
	// regions to copy the ami to, along with the custom kms key id (alias or
	// arn) to use for encryption for that region. Keys must match the regions
	// provided in `ami_regions`. If you just want to encrypt using a default
	// ID, you can stick with `kms_key_id` and `ami_regions`. If you want a
	// region to be encrypted with that region's default key ID, you can use an
	// empty string `""` instead of a key id in this map. (e.g. `"us-east-1":
	// ""`) However, you cannot use default key IDs if you are using this in
	// conjunction with `snapshot_users` -- in that situation you must use
	// custom keys. For valid formats see *KmsKeyId* in the [AWS API docs -
	// CopyImage](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CopyImage.html).
	//
	// This option supercedes the `kms_key_id` option -- if you set both, and
	// they are different, Packer will respect the value in
	// `region_kms_key_ids` for your build region and silently disregard the
	// value provided in `kms_key_id`.
	AMIRegionKMSKeyIDs map[string]string `mapstructure:"region_kms_key_ids" required:"false"`
	// If true, Packer will not check whether an AMI with the `ami_name` exists
	// in the region it is building in. It will use an intermediary AMI name,
	// which it will not convert to an AMI in the build region. It will copy
	// the intermediary AMI into any regions provided in `ami_regions`, then
	// delete the intermediary AMI. Default `false`.
	AMISkipBuildRegion bool `mapstructure:"skip_save_build_region"`
	// Tags to apply to snapshot.
	// They will override AMI tags if already applied to snapshot. This is a
	// [template engine](../templates/engine.html), see [Build template
	// data](#build-template-data) for more information.
	SnapshotTags TagMap `mapstructure:"snapshot_tags" required:"false"`
	// A list of account IDs that have
	// access to create volumes from the snapshot(s). By default no additional
	// users other than the user creating the AMI has permissions to create
	// volumes from the backing snapshot(s).
	SnapshotUsers []string `mapstructure:"snapshot_users" required:"false"`
	// A list of groups that have access to
	// create volumes from the snapshot(s). By default no groups have permission
	// to create volumes from the snapshot(s). all will make the snapshot
	// publicly accessible.
	SnapshotGroups []string `mapstructure:"snapshot_groups" required:"false"`
}

func stringInSlice(s []string, searchstr string) bool {
	for _, item := range s {
		if item == searchstr {
			return true
		}
	}
	return false
}

func (c *AMIConfig) Prepare(accessConfig *AccessConfig, ctx *interpolate.Context) []error {
	var errs []error

	if c.AMIName == "" {
		errs = append(errs, fmt.Errorf("ami_name must be specified"))
	}

	// Make sure that if we have region_kms_key_ids defined,
	//  the regions in region_kms_key_ids are also in ami_regions
	if len(c.AMIRegionKMSKeyIDs) > 0 {
		for kmsKeyRegion := range c.AMIRegionKMSKeyIDs {
			if !stringInSlice(c.AMIRegions, kmsKeyRegion) {
				errs = append(errs, fmt.Errorf("Region %s is in region_kms_key_ids but not in ami_regions", kmsKeyRegion))
			}
		}
	}

	errs = append(errs, c.prepareRegions(accessConfig)...)

	// Prevent sharing of default KMS key encrypted volumes with other aws users
	if len(c.AMIUsers) > 0 {
		if len(c.AMIKmsKeyId) == 0 && c.AMIEncryptBootVolume.True() {
			errs = append(errs, fmt.Errorf("Cannot share AMI encrypted with default KMS key"))
		}
		if len(c.AMIRegionKMSKeyIDs) > 0 {
			for _, kmsKey := range c.AMIRegionKMSKeyIDs {
				if len(kmsKey) == 0 {
					errs = append(errs, fmt.Errorf("Cannot share AMI encrypted with default KMS key for other regions"))
				}
			}
		}
	}

	var kmsKeys []string
	if len(c.AMIKmsKeyId) > 0 {
		kmsKeys = append(kmsKeys, c.AMIKmsKeyId)
	}
	if len(c.AMIRegionKMSKeyIDs) > 0 {
		for _, kmsKey := range c.AMIRegionKMSKeyIDs {
			if len(kmsKey) == 0 {
				kmsKeys = append(kmsKeys, c.AMIKmsKeyId)
			}
		}
	}
	for _, kmsKey := range kmsKeys {
		if !validateKmsKey(kmsKey) {
			errs = append(errs, fmt.Errorf("%s is not a valid KMS Key Id.", kmsKey))
		}
	}

	if len(c.SnapshotUsers) > 0 {
		if len(c.AMIKmsKeyId) == 0 && c.AMIEncryptBootVolume.True() {
			errs = append(errs, fmt.Errorf("Cannot share snapshot encrypted with default KMS key"))
		}
		if len(c.AMIRegionKMSKeyIDs) > 0 {
			for _, kmsKey := range c.AMIRegionKMSKeyIDs {
				if len(kmsKey) == 0 {
					errs = append(errs, fmt.Errorf("Cannot share snapshot encrypted with default KMS key"))
				}
			}
		}
	}

	if len(c.AMIName) < 3 || len(c.AMIName) > 128 {
		errs = append(errs, fmt.Errorf("ami_name must be between 3 and 128 characters long"))
	}

	if c.AMIName != templateCleanAMIName(c.AMIName) {
		errs = append(errs, fmt.Errorf("AMIName should only contain "+
			"alphanumeric characters, parentheses (()), square brackets ([]), spaces "+
			"( ), periods (.), slashes (/), dashes (-), single quotes ('), at-signs "+
			"(@), or underscores(_). You can use the `clean_resource_name` template "+
			"filter to automatically clean your ami name."))
	}

	if len(errs) > 0 {
		return errs
	}

	return nil
}

func (c *AMIConfig) prepareRegions(accessConfig *AccessConfig) (errs []error) {
	if len(c.AMIRegions) > 0 {
		regionSet := make(map[string]struct{})
		regions := make([]string, 0, len(c.AMIRegions))

		for _, region := range c.AMIRegions {
			// If we already saw the region, then don't look again
			if _, ok := regionSet[region]; ok {
				continue
			}

			// Mark that we saw the region
			regionSet[region] = struct{}{}

			// Make sure that if we have region_kms_key_ids defined,
			// the regions in ami_regions are also in region_kms_key_ids
			if len(c.AMIRegionKMSKeyIDs) > 0 {
				if _, ok := c.AMIRegionKMSKeyIDs[region]; !ok {
					errs = append(errs, fmt.Errorf("Region %s is in ami_regions but not in region_kms_key_ids", region))
				}
			}
			if (accessConfig != nil) && (region == accessConfig.RawRegion) {
				// make sure we don't try to copy to the region we originally
				// create the AMI in.
				log.Printf("Cannot copy AMI to AWS session region '%s', deleting it from `ami_regions`.", region)
				continue
			}
			regions = append(regions, region)
		}

		c.AMIRegions = regions
	}
	return errs
}

// See https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CopyImage.html
func validateKmsKey(kmsKey string) (valid bool) {
	kmsKeyIdPattern := `[a-f0-9-]+$`
	aliasPattern := `alias/[a-zA-Z0-9:/_-]+$`
	kmsArnStartPattern := `^arn:aws(-us-gov)?:kms:([a-z]{2}-(gov-)?[a-z]+-\d{1})?:(\d{12}):`
	if regexp.MustCompile(fmt.Sprintf("^%s", kmsKeyIdPattern)).MatchString(kmsKey) {
		return true
	}
	if regexp.MustCompile(fmt.Sprintf("^%s", aliasPattern)).MatchString(kmsKey) {
		return true
	}
	if regexp.MustCompile(fmt.Sprintf("%skey/%s", kmsArnStartPattern, kmsKeyIdPattern)).MatchString(kmsKey) {
		return true
	}
	if regexp.MustCompile(fmt.Sprintf("%s%s", kmsArnStartPattern, aliasPattern)).MatchString(kmsKey) {
		return true
	}
	return false
}
<|endoftext|>


<commit_before>// Copyright 2019 The LUCI 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.

// Command run_annotations is a LUCI executable that wraps a command that
// produces @@@ANNOTATIONS@@@, and converts annotations to build message.
package main

import (
	"os"

	"go.chromium.org/luci/buildbucket/luciexe"
)

func main() {
	os.Exit(luciexe.RunnerMain(os.Args))
}
<commit_msg>[buildbucket] Fix luci_runner doc comment<commit_after>// Copyright 2019 The LUCI 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.

// Command luci_runner runs a LUCI executable.
// It is an implementation detail of Buildbucket.
package main

import (
	"os"

	"go.chromium.org/luci/buildbucket/luciexe"
)

func main() {
	os.Exit(luciexe.RunnerMain(os.Args))
}
<|endoftext|>


<commit_before>package common

import (
	"errors"
	"fmt"
	"github.com/mitchellh/packer/packer"
	"os"
	"time"
)

// RunConfig contains configuration for running an instance from a source
// AMI and details on how to access that launched image.
type RunConfig struct {
	SourceAmi                string   `mapstructure:"source_ami"`
	IamInstanceProfile       string   `mapstructure:"iam_instance_profile"`
	InstanceType             string   `mapstructure:"instance_type"`
	UserData                 string   `mapstructure:"user_data"`
	UserDataFile             string   `mapstructure:"user_data_file"`
	RawSSHTimeout            string   `mapstructure:"ssh_timeout"`
	SSHUsername              string   `mapstructure:"ssh_username"`
	SSHPort                  int      `mapstructure:"ssh_port"`
	SecurityGroupId          string   `mapstructure:"security_group_id"`
	SecurityGroupIds         []string `mapstructure:"security_group_ids"`
	SubnetId                 string   `mapstructure:"subnet_id"`
	AssociatePublicIpAddress bool     `mapstructure:"associate_public_ip_address"`
	TemporaryKeyPairName     string   `mapstructure:"temporary_key_pair_name"`
	VpcId                    string   `mapstructure:"vpc_id"`
	AvailabilityZone         string   `mapstructure:"availability_zone"`

	// Unexported fields that are calculated from others
	sshTimeout time.Duration
}

func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error {
	if t == nil {
		var err error
		t, err = packer.NewConfigTemplate()
		if err != nil {
			return []error{err}
		}
	}

	// Defaults
	if c.SSHPort == 0 {
		c.SSHPort = 22
	}

	if c.RawSSHTimeout == "" {
		c.RawSSHTimeout = "1m"
	}

	if c.TemporaryKeyPairName == "" {
		c.TemporaryKeyPairName = "packer {{uuid}}"
	}

	// Validation
	var err error
	errs := make([]error, 0)
	if c.SourceAmi == "" {
		errs = append(errs, errors.New("A source_ami must be specified"))
	}

	if c.InstanceType == "" {
		errs = append(errs, errors.New("An instance_type must be specified"))
	}

	if c.SSHUsername == "" {
		errs = append(errs, errors.New("An ssh_username must be specified"))
	}

	if c.UserData != "" && c.UserDataFile != "" {
		errs = append(errs, fmt.Errorf("Only one of user_data or user_data_file can be specified."))
	} else if c.UserDataFile != "" {
		if _, err := os.Stat(c.UserDataFile); err != nil {
			errs = append(errs, fmt.Errorf("user_data_file not found: %s", c.UserDataFile))
		}
	}

	if c.SecurityGroupId != "" {
		if len(c.SecurityGroupIds) > 0 {
			errs = append(errs, fmt.Errorf("Only one of security_group_id or security_group_ids can be specified."))
		} else {
			c.SecurityGroupIds = []string{c.SecurityGroupId}
			c.SecurityGroupId = ""
		}
	}

	templates := map[string]*string{
		"iam_instance_profile":        &cc.IamInstanceProfile,
		"instance_type":               &cc.InstanceType,
		"ssh_timeout":                 &cc.RawSSHTimeout,
		"ssh_username":                &cc.SSHUsername,
		"source_ami":                  &cc.SourceAmi,
		"subnet_id":                   &cc.SubnetId,
		"associate_public_ip_address": &cc.AssociatePublicIpAddress,
		"temporary_key_pair_name":     &cc.TemporaryKeyPairName,
		"vpc_id":                      &cc.VpcId,
		"availability_zone":           &cc.AvailabilityZone,
	}

	for n, ptr := range templates {
		var err error
		*ptr, err = t.Process(*ptr, nil)
		if err != nil {
			errs = append(
				errs, fmt.Errorf("Error processing %s: %s", n, err))
		}
	}

	sliceTemplates := map[string][]string{
		"security_group_ids": c.SecurityGroupIds,
	}

	for n, slice := range sliceTemplates {
		for i, elem := range slice {
			var err error
			slice[i], err = t.Process(elem, nil)
			if err != nil {
				errs = append(
					errs, fmt.Errorf("Error processing %s[%d]: %s", n, i, err))
			}
		}
	}

	c.sshTimeout, err = time.ParseDuration(c.RawSSHTimeout)
	if err != nil {
		errs = append(errs, fmt.Errorf("Failed parsing ssh_timeout: %s", err))
	}

	return errs
}

func (c *RunConfig) SSHTimeout() time.Duration {
	return c.sshTimeout
}
<commit_msg>oops bugfix for work on https://github.com/mitchellh/packer/issues/578<commit_after>package common

import (
	"errors"
	"fmt"
	"github.com/mitchellh/packer/packer"
	"os"
	"time"
)

// RunConfig contains configuration for running an instance from a source
// AMI and details on how to access that launched image.
type RunConfig struct {
	SourceAmi                string   `mapstructure:"source_ami"`
	IamInstanceProfile       string   `mapstructure:"iam_instance_profile"`
	InstanceType             string   `mapstructure:"instance_type"`
	UserData                 string   `mapstructure:"user_data"`
	UserDataFile             string   `mapstructure:"user_data_file"`
	RawSSHTimeout            string   `mapstructure:"ssh_timeout"`
	SSHUsername              string   `mapstructure:"ssh_username"`
	SSHPort                  int      `mapstructure:"ssh_port"`
	SecurityGroupId          string   `mapstructure:"security_group_id"`
	SecurityGroupIds         []string `mapstructure:"security_group_ids"`
	SubnetId                 string   `mapstructure:"subnet_id"`
	AssociatePublicIpAddress bool     `mapstructure:"associate_public_ip_address"`
	TemporaryKeyPairName     string   `mapstructure:"temporary_key_pair_name"`
	VpcId                    string   `mapstructure:"vpc_id"`
	AvailabilityZone         string   `mapstructure:"availability_zone"`

	// Unexported fields that are calculated from others
	sshTimeout time.Duration
}

func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error {
	if t == nil {
		var err error
		t, err = packer.NewConfigTemplate()
		if err != nil {
			return []error{err}
		}
	}

	// Defaults
	if c.SSHPort == 0 {
		c.SSHPort = 22
	}

	if c.RawSSHTimeout == "" {
		c.RawSSHTimeout = "1m"
	}

	if c.TemporaryKeyPairName == "" {
		c.TemporaryKeyPairName = "packer {{uuid}}"
	}

	// Validation
	var err error
	errs := make([]error, 0)
	if c.SourceAmi == "" {
		errs = append(errs, errors.New("A source_ami must be specified"))
	}

	if c.InstanceType == "" {
		errs = append(errs, errors.New("An instance_type must be specified"))
	}

	if c.SSHUsername == "" {
		errs = append(errs, errors.New("An ssh_username must be specified"))
	}

	if c.UserData != "" && c.UserDataFile != "" {
		errs = append(errs, fmt.Errorf("Only one of user_data or user_data_file can be specified."))
	} else if c.UserDataFile != "" {
		if _, err := os.Stat(c.UserDataFile); err != nil {
			errs = append(errs, fmt.Errorf("user_data_file not found: %s", c.UserDataFile))
		}
	}

	if c.SecurityGroupId != "" {
		if len(c.SecurityGroupIds) > 0 {
			errs = append(errs, fmt.Errorf("Only one of security_group_id or security_group_ids can be specified."))
		} else {
			c.SecurityGroupIds = []string{c.SecurityGroupId}
			c.SecurityGroupId = ""
		}
	}

	templates := map[string]*string{
		"iam_instance_profile":        &c.IamInstanceProfile,
		"instance_type":               &c.InstanceType,
		"ssh_timeout":                 &c.RawSSHTimeout,
		"ssh_username":                &c.SSHUsername,
		"source_ami":                  &c.SourceAmi,
		"subnet_id":                   &c.SubnetId,
		"associate_public_ip_address": &c.AssociatePublicIpAddress,
		"temporary_key_pair_name":     &c.TemporaryKeyPairName,
		"vpc_id":                      &c.VpcId,
		"availability_zone":           &c.AvailabilityZone,
	}

	for n, ptr := range templates {
		var err error
		*ptr, err = t.Process(*ptr, nil)
		if err != nil {
			errs = append(
				errs, fmt.Errorf("Error processing %s: %s", n, err))
		}
	}

	sliceTemplates := map[string][]string{
		"security_group_ids": c.SecurityGroupIds,
	}

	for n, slice := range sliceTemplates {
		for i, elem := range slice {
			var err error
			slice[i], err = t.Process(elem, nil)
			if err != nil {
				errs = append(
					errs, fmt.Errorf("Error processing %s[%d]: %s", n, i, err))
			}
		}
	}

	c.sshTimeout, err = time.ParseDuration(c.RawSSHTimeout)
	if err != nil {
		errs = append(errs, fmt.Errorf("Failed parsing ssh_timeout: %s", err))
	}

	return errs
}

func (c *RunConfig) SSHTimeout() time.Duration {
	return c.sshTimeout
}
<|endoftext|>


<commit_before>package main

import (
	"code.google.com/p/ebml-go/webm"
	"code.google.com/p/portaudio-go/portaudio"
	"flag"
	gl "github.com/chsc/gogl/gl21"
	"github.com/jteeuwen/glfw"
	"log"
	"math"
	"os"
	"runtime"
	"time"
)

var (
	in         = flag.String("i", "", "Input file")
	unsync     = flag.Bool("u", false, "Unsynchronized display")
	notc       = flag.Bool("t", false, "Ignore timecodes")
	blend      = flag.Bool("b", false, "Blend between images")
	fullscreen = flag.Bool("f", false, "Fullscreen mode")
	justaudio  = flag.Bool("a", false, "Just audio")
	justvideo  = flag.Bool("v", false, "Just video")
)

var ntex int

const vss = `
void main() {
  gl_TexCoord[0] = gl_MultiTexCoord0;
  gl_Position = ftransform();
}
`

const ycbcr2rgb = `
const mat3 ycbcr2rgb = mat3(
                          1.164, 0, 1.596,
                          1.164, -0.392, -0.813,
                          1.164, 2.017, 0.0
                          );
const float ysub = 0.0625;
vec3 ycbcr2rgb(vec3 c) {
   vec3 ycbcr = vec3(c.x - ysub, c.y - 0.5, c.z - 0.5);
   return ycbcr * ycbcr2rgb;
}
`

const fss = ycbcr2rgb + `
uniform sampler2D yt1;
uniform sampler2D cbt1;
uniform sampler2D crt1;

void main() {
   vec3 c = vec3(texture2D(yt1, gl_TexCoord[0].st).r,
                 texture2D(cbt1, gl_TexCoord[0].st).r,
                 texture2D(crt1, gl_TexCoord[0].st).r);
   gl_FragColor = vec4(ycbcr2rgb(c), 1.0);
}
`
const bfss = ycbcr2rgb + `
uniform sampler2D yt1;
uniform sampler2D cbt1;
uniform sampler2D crt1;
uniform sampler2D yt0;
uniform sampler2D cbt0;
uniform sampler2D crt0;
uniform float factor;

void main() {
   vec3 c0 = vec3(texture2D(yt0, gl_TexCoord[0].st).r,
                  texture2D(cbt0, gl_TexCoord[0].st).r,
                  texture2D(crt0, gl_TexCoord[0].st).r);
   vec3 c1 = vec3(texture2D(yt1, gl_TexCoord[0].st).r,
                  texture2D(cbt1, gl_TexCoord[0].st).r,
                  texture2D(crt1, gl_TexCoord[0].st).r);
   gl_FragColor = vec4(ycbcr2rgb(mix(c0, c1, factor)), 1);
}
`

func texinit(id int) {
	gl.BindTexture(gl.TEXTURE_2D, gl.Uint(id))
	gl.TexParameterf(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
	gl.TexParameterf(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
	gl.TexParameterf(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
	gl.TexParameterf(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
}

func shinit() gl.Int {
	vs := loadShader(gl.VERTEX_SHADER, vss)
	var sfs string
	if *blend {
		sfs = bfss
	} else {
		sfs = fss
	}
	fs := loadShader(gl.FRAGMENT_SHADER, sfs)
	prg := gl.CreateProgram()
	gl.AttachShader(prg, vs)
	gl.AttachShader(prg, fs)
	gl.LinkProgram(prg)
	var l int
	if *blend {
		l = 6
	} else {
		l = 3
	}
	gl.UseProgram(prg)
	names := []string{"yt1", "cbt1", "crt1", "yt0", "cbt0", "crt0"}
	for i := 0; i < l; i++ {
		loc := gl.GetUniformLocation(prg, gl.GLString(names[i]))
		gl.Uniform1i(loc, gl.Int(i))
	}
	return gl.GetUniformLocation(prg, gl.GLString("factor"))
}

func upload(id gl.Uint, data []byte, stride int, w int, h int) {
	gl.BindTexture(gl.TEXTURE_2D, id)
	gl.PixelStorei(gl.UNPACK_ROW_LENGTH, gl.Int(stride))
	gl.PixelStorei(gl.UNPACK_ALIGNMENT, 1)
	gl.TexImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE,
		gl.Sizei(w), gl.Sizei(h), 0,
		gl.LUMINANCE, gl.UNSIGNED_BYTE, gl.Pointer(&data[0]))
}

func initquad() {
	ver := []gl.Float{-1, 1, 1, 1, -1, -1, 1, -1}
	gl.BindBuffer(gl.ARRAY_BUFFER, 1)
	gl.BufferData(gl.ARRAY_BUFFER, gl.Sizeiptr(4*len(ver)),
		gl.Pointer(&ver[0]), gl.STATIC_DRAW)
	gl.VertexPointer(2, gl.FLOAT, 0, nil)
	tex := []gl.Float{0, 0, 1, 0, 0, 1, 1, 1}
	gl.BindBuffer(gl.ARRAY_BUFFER, 2)
	gl.BufferData(gl.ARRAY_BUFFER, gl.Sizeiptr(4*len(tex)),
		gl.Pointer(&tex[0]), gl.STATIC_DRAW)
	gl.TexCoordPointer(2, gl.FLOAT, 0, nil)
	gl.EnableClientState(gl.VERTEX_ARRAY)
	gl.EnableClientState(gl.TEXTURE_COORD_ARRAY)
}

func loadShader(shtype gl.Enum, src string) gl.Uint {
	sh := gl.CreateShader(shtype)
	gsrc := gl.GLString(src)
	gl.ShaderSource(sh, 1, &gsrc, nil)
	gl.CompileShader(sh)
	return sh
}

func factor(t time.Time, tc0 time.Time, tc1 time.Time) gl.Float {
	num := t.Sub(tc0)
	den := tc1.Sub(tc0)
	res := num.Seconds() / den.Seconds()
	res = math.Max(res, 0)
	res = math.Min(res, 1)
	return gl.Float(res)
}

var steps = uint(0xffffffff)
var seek = webm.BadTC
var duration = webm.BadTC
var lasttc = webm.BadTC
var fduration = webm.BadTC

func tseek(t time.Duration) {
	seek = t
	if seek < 0 {
		seek = 0
	}
}

func xseek(x int) {
	w, _ := glfw.WindowSize()
	factor := float64(x) / float64(w)
	tseek(time.Duration(float64(duration) * factor))
}

func mphandler(x, y int) {
	if glfw.KeyPress == glfw.MouseButton(glfw.MouseLeft) {
		xseek(x)
	}
}

func mbhandler(button, state int) {
	if state == glfw.KeyPress {
		x, _ := glfw.MousePos()
		xseek(x)
	}
}

func khandler(key, state int) {
	if state == glfw.KeyRelease {
		switch key {
		case 'P':
			steps = 0
		case 'R':
			steps = 0xffffffff
			tseek(lasttc)
		case 'F':
			steps = 1
			tseek(lasttc + fduration)
		case 'B':
			steps = 1
			tseek(lasttc - fduration)
		}
	}
}

func vpresent(wchan <-chan webm.Frame, reader *webm.Reader) {
	if *blend {
		ntex = 6
	} else {
		ntex = 3
	}
	img := <-wchan
	w := img.Rect.Dx()
	h := img.Rect.Dy()
	gl.Init()
	glfw.Init()
	defer glfw.Terminate()
	mode := glfw.Windowed
	ww := w
	wh := h
	if *fullscreen {
		mode = glfw.Fullscreen
		ww = 1440
		wh = 900
	}
	glfw.OpenWindow(ww, wh, 0, 0, 0, 0, 0, 0, mode)
	glfw.SetKeyCallback(khandler)
	glfw.SetMouseButtonCallback(mbhandler)
	glfw.SetMousePosCallback(mphandler)
	defer glfw.CloseWindow()
	glfw.SetWindowSizeCallback(func(ww, wh int) {
		oaspect := float64(w) / float64(h)
		haspect := float64(ww) / float64(wh)
		vaspect := float64(wh) / float64(ww)
		var scx, scy float64
		if oaspect > haspect {
			scx = 1
			scy = haspect / oaspect
		} else {
			scx = vaspect * oaspect
			scy = 1
		}
		gl.Viewport(0, 0, gl.Sizei(ww), gl.Sizei(wh))
		gl.LoadIdentity()
		gl.Scaled(gl.Double(scx), gl.Double(scy), 1)
	})
	if !*unsync {
		glfw.SetSwapInterval(1)
	}
	glfw.SetWindowTitle(*in)
	for i := 0; i < ntex; i++ {
		texinit(i + 1)
	}
	factorloc := shinit()
	initquad()
	gl.Enable(gl.TEXTURE_2D)
	tbase := time.Now()
	pimg := img

	flushing := false
	for glfw.WindowParam(glfw.Opened) == 1 {
		if seek != webm.BadTC {
			reader.Seek(seek)
			seek = webm.BadTC
			flushing = true
		}

		gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
		t := time.Now()
		if flushing || (steps > 0 && (*notc || t.After(tbase.Add(img.Timecode)))) {
			pimg = img
			var ok bool
			var nimg webm.Frame
			nimg, ok = <-wchan
			if !ok {
				return
			}
			if false && nimg.Timecode == pimg.Timecode {
				log.Println("same timecode", img.Timecode)
			}
			if nimg.Rebase {
				tbase = time.Now().Add(-nimg.Timecode)
				flushing = false
			}
			if !flushing {
				steps--
				img = nimg
			}
		}
		lasttc = img.Timecode

		gl.ActiveTexture(gl.TEXTURE0)
		upload(1, img.Y, img.YStride, w, h)
		gl.ActiveTexture(gl.TEXTURE1)
		upload(2, img.Cb, img.CStride, w/2, h/2)
		gl.ActiveTexture(gl.TEXTURE2)
		upload(3, img.Cr, img.CStride, w/2, h/2)
		if *blend {
			gl.Uniform1f(factorloc, factor(t,
				tbase.Add(pimg.Timecode),
				tbase.Add(img.Timecode)))
			gl.ActiveTexture(gl.TEXTURE3)
			upload(4, pimg.Y, pimg.YStride, w, h)
			gl.ActiveTexture(gl.TEXTURE4)
			upload(5, pimg.Cb, pimg.CStride, w/2, h/2)
			gl.ActiveTexture(gl.TEXTURE5)
			upload(6, pimg.Cr, pimg.CStride, w/2, h/2)
		}
		gl.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
		runtime.GC()
		glfw.SwapBuffers()
	}
}

type AudioWriter struct {
	ch       <-chan webm.Samples
	channels int
	active   bool
	sofar    int
	curr     webm.Samples
}

func (aw *AudioWriter) ProcessAudio(in, out []float32) {
	for sent, lo := 0, len(out); sent < lo; {
		if aw.sofar == len(aw.curr.Data) {
			aw.curr, aw.active = <-aw.ch
			aw.sofar = 0
			//log.Println("timecode", aw.curr.Timecode)
		}
		s := copy(out[sent:], aw.curr.Data[aw.sofar:])
		sent += s
		aw.sofar += s
	}
}

func apresent(wchan <-chan webm.Samples, audio *webm.Audio) {
	chk := func(err error) {
		if err != nil {
			panic(err)
		}
	}
	channels := int(audio.Channels)
	aw := AudioWriter{ch: wchan, channels: channels, active: true}
	stream, err := portaudio.OpenDefaultStream(0, channels,
		audio.SamplingFrequency, 0, &aw)
	defer stream.Close()
	chk(err)
	chk(stream.Start())
	defer stream.Stop()
	for aw.active {
		time.Sleep(time.Second)
	}
}

func main() {
	flag.Parse()

	var err error
	var wm webm.WebM
	r, err := os.Open(*in)
	defer r.Close()
	if err != nil {
		log.Panic("Unable to open file " + *in)
	}
	reader, err := webm.Parse(r, &wm)
	if err != nil {
		log.Panic("Unable to parse file:", err)
	}
	duration = wm.GetDuration()

	splitter := webm.NewSplitter(reader.Chan)

	var vtrack *webm.TrackEntry
	var vstream *webm.Stream
	if !*justaudio {
		vtrack = wm.FindFirstVideoTrack()
		fduration = vtrack.GetDefaultDuration()
	}
	if vtrack != nil {
		vstream = webm.NewStream(vtrack)
	}
	var astream *webm.Stream
	var atrack *webm.TrackEntry
	if !*justvideo {
		atrack = wm.FindFirstAudioTrack()
	}
	if atrack != nil {
		astream = webm.NewStream(atrack)
	}
	splitter.Split(astream, vstream)
	switch {
	case astream != nil && vstream != nil:
		go apresent(astream.AudioChannel(), &atrack.Audio)
		fallthrough
	case vstream != nil:
		vpresent(vstream.VideoChannel(), reader)
	case astream != nil:
		apresent(astream.AudioChannel(), &atrack.Audio)
	}
}
<commit_msg>Seeking in paused state<commit_after>package main

import (
	"code.google.com/p/ebml-go/webm"
	"code.google.com/p/portaudio-go/portaudio"
	"flag"
	gl "github.com/chsc/gogl/gl21"
	"github.com/jteeuwen/glfw"
	"log"
	"math"
	"os"
	"runtime"
	"time"
)

var (
	in         = flag.String("i", "", "Input file")
	unsync     = flag.Bool("u", false, "Unsynchronized display")
	notc       = flag.Bool("t", false, "Ignore timecodes")
	blend      = flag.Bool("b", false, "Blend between images")
	fullscreen = flag.Bool("f", false, "Fullscreen mode")
	justaudio  = flag.Bool("a", false, "Just audio")
	justvideo  = flag.Bool("v", false, "Just video")
)

var ntex int

const vss = `
void main() {
  gl_TexCoord[0] = gl_MultiTexCoord0;
  gl_Position = ftransform();
}
`

const ycbcr2rgb = `
const mat3 ycbcr2rgb = mat3(
                          1.164, 0, 1.596,
                          1.164, -0.392, -0.813,
                          1.164, 2.017, 0.0
                          );
const float ysub = 0.0625;
vec3 ycbcr2rgb(vec3 c) {
   vec3 ycbcr = vec3(c.x - ysub, c.y - 0.5, c.z - 0.5);
   return ycbcr * ycbcr2rgb;
}
`

const fss = ycbcr2rgb + `
uniform sampler2D yt1;
uniform sampler2D cbt1;
uniform sampler2D crt1;

void main() {
   vec3 c = vec3(texture2D(yt1, gl_TexCoord[0].st).r,
                 texture2D(cbt1, gl_TexCoord[0].st).r,
                 texture2D(crt1, gl_TexCoord[0].st).r);
   gl_FragColor = vec4(ycbcr2rgb(c), 1.0);
}
`
const bfss = ycbcr2rgb + `
uniform sampler2D yt1;
uniform sampler2D cbt1;
uniform sampler2D crt1;
uniform sampler2D yt0;
uniform sampler2D cbt0;
uniform sampler2D crt0;
uniform float factor;

void main() {
   vec3 c0 = vec3(texture2D(yt0, gl_TexCoord[0].st).r,
                  texture2D(cbt0, gl_TexCoord[0].st).r,
                  texture2D(crt0, gl_TexCoord[0].st).r);
   vec3 c1 = vec3(texture2D(yt1, gl_TexCoord[0].st).r,
                  texture2D(cbt1, gl_TexCoord[0].st).r,
                  texture2D(crt1, gl_TexCoord[0].st).r);
   gl_FragColor = vec4(ycbcr2rgb(mix(c0, c1, factor)), 1);
}
`

func texinit(id int) {
	gl.BindTexture(gl.TEXTURE_2D, gl.Uint(id))
	gl.TexParameterf(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
	gl.TexParameterf(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
	gl.TexParameterf(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
	gl.TexParameterf(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
}

func shinit() gl.Int {
	vs := loadShader(gl.VERTEX_SHADER, vss)
	var sfs string
	if *blend {
		sfs = bfss
	} else {
		sfs = fss
	}
	fs := loadShader(gl.FRAGMENT_SHADER, sfs)
	prg := gl.CreateProgram()
	gl.AttachShader(prg, vs)
	gl.AttachShader(prg, fs)
	gl.LinkProgram(prg)
	var l int
	if *blend {
		l = 6
	} else {
		l = 3
	}
	gl.UseProgram(prg)
	names := []string{"yt1", "cbt1", "crt1", "yt0", "cbt0", "crt0"}
	for i := 0; i < l; i++ {
		loc := gl.GetUniformLocation(prg, gl.GLString(names[i]))
		gl.Uniform1i(loc, gl.Int(i))
	}
	return gl.GetUniformLocation(prg, gl.GLString("factor"))
}

func upload(id gl.Uint, data []byte, stride int, w int, h int) {
	gl.BindTexture(gl.TEXTURE_2D, id)
	gl.PixelStorei(gl.UNPACK_ROW_LENGTH, gl.Int(stride))
	gl.PixelStorei(gl.UNPACK_ALIGNMENT, 1)
	gl.TexImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE,
		gl.Sizei(w), gl.Sizei(h), 0,
		gl.LUMINANCE, gl.UNSIGNED_BYTE, gl.Pointer(&data[0]))
}

func initquad() {
	ver := []gl.Float{-1, 1, 1, 1, -1, -1, 1, -1}
	gl.BindBuffer(gl.ARRAY_BUFFER, 1)
	gl.BufferData(gl.ARRAY_BUFFER, gl.Sizeiptr(4*len(ver)),
		gl.Pointer(&ver[0]), gl.STATIC_DRAW)
	gl.VertexPointer(2, gl.FLOAT, 0, nil)
	tex := []gl.Float{0, 0, 1, 0, 0, 1, 1, 1}
	gl.BindBuffer(gl.ARRAY_BUFFER, 2)
	gl.BufferData(gl.ARRAY_BUFFER, gl.Sizeiptr(4*len(tex)),
		gl.Pointer(&tex[0]), gl.STATIC_DRAW)
	gl.TexCoordPointer(2, gl.FLOAT, 0, nil)
	gl.EnableClientState(gl.VERTEX_ARRAY)
	gl.EnableClientState(gl.TEXTURE_COORD_ARRAY)
}

func loadShader(shtype gl.Enum, src string) gl.Uint {
	sh := gl.CreateShader(shtype)
	gsrc := gl.GLString(src)
	gl.ShaderSource(sh, 1, &gsrc, nil)
	gl.CompileShader(sh)
	return sh
}

func factor(t time.Time, tc0 time.Time, tc1 time.Time) gl.Float {
	num := t.Sub(tc0)
	den := tc1.Sub(tc0)
	res := num.Seconds() / den.Seconds()
	res = math.Max(res, 0)
	res = math.Min(res, 1)
	return gl.Float(res)
}

var steps = uint(0xffffffff)
var seek = webm.BadTC
var duration = webm.BadTC
var lasttc = webm.BadTC
var fduration = webm.BadTC

func tseek(t time.Duration) {
	seek = t
	if seek < 0 {
		seek = 0
	}
}

func xseek(x int) {
	w, _ := glfw.WindowSize()
	factor := float64(x) / float64(w)
	tseek(time.Duration(float64(duration) * factor))
}

func mphandler(x, y int) {
	if glfw.KeyPress == glfw.MouseButton(glfw.MouseLeft) {
		xseek(x)
	}
}

func mbhandler(button, state int) {
	if state == glfw.KeyPress {
		x, _ := glfw.MousePos()
		xseek(x)
	}
}

func khandler(key, state int) {
	if state == glfw.KeyRelease {
		switch key {
		case 'P':
			steps = 0
		case 'R':
			steps = 0xffffffff
			tseek(lasttc)
		case 'F':
			steps = 1
			tseek(lasttc + fduration)
		case 'B':
			steps = 1
			tseek(lasttc - fduration)
		}
	}
}

func vpresent(wchan <-chan webm.Frame, reader *webm.Reader) {
	if *blend {
		ntex = 6
	} else {
		ntex = 3
	}
	img := <-wchan
	w := img.Rect.Dx()
	h := img.Rect.Dy()
	gl.Init()
	glfw.Init()
	defer glfw.Terminate()
	mode := glfw.Windowed
	ww := w
	wh := h
	if *fullscreen {
		mode = glfw.Fullscreen
		ww = 1440
		wh = 900
	}
	glfw.OpenWindow(ww, wh, 0, 0, 0, 0, 0, 0, mode)
	glfw.SetKeyCallback(khandler)
	glfw.SetMouseButtonCallback(mbhandler)
	glfw.SetMousePosCallback(mphandler)
	defer glfw.CloseWindow()
	glfw.SetWindowSizeCallback(func(ww, wh int) {
		oaspect := float64(w) / float64(h)
		haspect := float64(ww) / float64(wh)
		vaspect := float64(wh) / float64(ww)
		var scx, scy float64
		if oaspect > haspect {
			scx = 1
			scy = haspect / oaspect
		} else {
			scx = vaspect * oaspect
			scy = 1
		}
		gl.Viewport(0, 0, gl.Sizei(ww), gl.Sizei(wh))
		gl.LoadIdentity()
		gl.Scaled(gl.Double(scx), gl.Double(scy), 1)
	})
	if !*unsync {
		glfw.SetSwapInterval(1)
	}
	glfw.SetWindowTitle(*in)
	for i := 0; i < ntex; i++ {
		texinit(i + 1)
	}
	factorloc := shinit()
	initquad()
	gl.Enable(gl.TEXTURE_2D)
	tbase := time.Now()
	pimg := img

	flushing := false
	for glfw.WindowParam(glfw.Opened) == 1 {
		if seek != webm.BadTC {
			reader.Seek(seek)
			seek = webm.BadTC
			flushing = true
		}

		gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
		t := time.Now()
		if flushing || (steps > 0 && (*notc || t.After(tbase.Add(img.Timecode)))) {
			pimg = img
			var ok bool
			var nimg webm.Frame
			nimg, ok = <-wchan
			if !ok {
				return
			}
			if false && nimg.Timecode == pimg.Timecode {
				log.Println("same timecode", img.Timecode)
			}
			if nimg.Rebase {
				tbase = time.Now().Add(-nimg.Timecode)
				flushing = false
			}
			if !flushing {
				if steps > 0 {
					steps--
				}
				img = nimg
			}
		}
		lasttc = img.Timecode

		gl.ActiveTexture(gl.TEXTURE0)
		upload(1, img.Y, img.YStride, w, h)
		gl.ActiveTexture(gl.TEXTURE1)
		upload(2, img.Cb, img.CStride, w/2, h/2)
		gl.ActiveTexture(gl.TEXTURE2)
		upload(3, img.Cr, img.CStride, w/2, h/2)
		if *blend {
			gl.Uniform1f(factorloc, factor(t,
				tbase.Add(pimg.Timecode),
				tbase.Add(img.Timecode)))
			gl.ActiveTexture(gl.TEXTURE3)
			upload(4, pimg.Y, pimg.YStride, w, h)
			gl.ActiveTexture(gl.TEXTURE4)
			upload(5, pimg.Cb, pimg.CStride, w/2, h/2)
			gl.ActiveTexture(gl.TEXTURE5)
			upload(6, pimg.Cr, pimg.CStride, w/2, h/2)
		}
		gl.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
		runtime.GC()
		glfw.SwapBuffers()
	}
}

type AudioWriter struct {
	ch       <-chan webm.Samples
	channels int
	active   bool
	sofar    int
	curr     webm.Samples
}

func (aw *AudioWriter) ProcessAudio(in, out []float32) {
	for sent, lo := 0, len(out); sent < lo; {
		if aw.sofar == len(aw.curr.Data) {
			aw.curr, aw.active = <-aw.ch
			aw.sofar = 0
			//log.Println("timecode", aw.curr.Timecode)
		}
		s := copy(out[sent:], aw.curr.Data[aw.sofar:])
		sent += s
		aw.sofar += s
	}
}

func apresent(wchan <-chan webm.Samples, audio *webm.Audio) {
	chk := func(err error) {
		if err != nil {
			panic(err)
		}
	}
	channels := int(audio.Channels)
	aw := AudioWriter{ch: wchan, channels: channels, active: true}
	stream, err := portaudio.OpenDefaultStream(0, channels,
		audio.SamplingFrequency, 0, &aw)
	defer stream.Close()
	chk(err)
	chk(stream.Start())
	defer stream.Stop()
	for aw.active {
		time.Sleep(time.Second)
	}
}

func main() {
	flag.Parse()

	var err error
	var wm webm.WebM
	r, err := os.Open(*in)
	defer r.Close()
	if err != nil {
		log.Panic("Unable to open file " + *in)
	}
	reader, err := webm.Parse(r, &wm)
	if err != nil {
		log.Panic("Unable to parse file:", err)
	}
	duration = wm.GetDuration()

	splitter := webm.NewSplitter(reader.Chan)

	var vtrack *webm.TrackEntry
	var vstream *webm.Stream
	if !*justaudio {
		vtrack = wm.FindFirstVideoTrack()
		fduration = vtrack.GetDefaultDuration()
	}
	if vtrack != nil {
		vstream = webm.NewStream(vtrack)
	}
	var astream *webm.Stream
	var atrack *webm.TrackEntry
	if !*justvideo {
		atrack = wm.FindFirstAudioTrack()
	}
	if atrack != nil {
		astream = webm.NewStream(atrack)
	}
	splitter.Split(astream, vstream)
	switch {
	case astream != nil && vstream != nil:
		go apresent(astream.AudioChannel(), &atrack.Audio)
		fallthrough
	case vstream != nil:
		vpresent(vstream.VideoChannel(), reader)
	case astream != nil:
		apresent(astream.AudioChannel(), &atrack.Audio)
	}
}
<|endoftext|>


<commit_before>// Cryptocurrency exchange abstraction

package exchange

import (
	"time"
)

// Exchange methods for data and trading ***************************************
type Exchange interface {
	String() string
	Priority() int
	Fee() float64
	SetPosition(float64)
	Position() float64
	SetMaxPos(float64)
	MaxPos() float64
	AvailFunds() float64
	Currency() string
	CurrencyCode() byte
	CommunicateBook(bookChan chan<- Book, doneChan <-chan bool) Book
	SendOrder(action, otype string, amount, price float64) (int64, error)
	CancelOrder(id int64) (bool, error)
	GetOrderStatus(id int64) (Order, error)
	HasCryptoFee() bool
}

// Order status data from the exchange *****************************************
type Order struct {
	FilledAmount float64 // Positive number for buys and sells
	Status       string  // "live" or "dead"
}

// Book data from the exchange *************************************************
type Book struct {
	Exg   Exchange
	Time  time.Time
	Bids  BidItems // Sort by price high to low
	Asks  AskItems // Sort by price low to high
	Error error
}

// BidItems data from the exchange
type BidItems []struct {
	Price  float64
	Amount float64
}

// AskItems data from the exchange
type AskItems []struct {
	Price  float64
	Amount float64
}

// Len implements sort.Interface on BidItems
func (items BidItems) Len() int {
	return len(items)
}

// Swap implements sort.Interface on BidItems
func (items BidItems) Swap(i, j int) {
	items[i], items[j] = items[j], items[i]
}

// Less implements sort.Interface on BidItems
func (items BidItems) Less(i, j int) bool {
	return items[i].Price > items[j].Price
}

// Len implements sort.Interface on AskItems
func (items AskItems) Len() int {
	return len(items)
}

// Swap implements sort.Interface on AskItems
func (items AskItems) Swap(i, j int) {
	items[i], items[j] = items[j], items[i]
}

// Less implements sort.Interface on AskItems
func (items AskItems) Less(i, j int) bool {
	return items[i].Price < items[j].Price
}
<commit_msg>add AvailShort method to Exchange interface<commit_after>// Cryptocurrency exchange abstraction

package exchange

import (
	"time"
)

// Exchange methods for data and trading ***************************************
type Exchange interface {
	String() string
	Priority() int
	Fee() float64
	SetPosition(float64)
	Position() float64
	SetMaxPos(float64)
	MaxPos() float64
	AvailFunds() float64
	AvailShort() float64
	Currency() string
	CurrencyCode() byte
	CommunicateBook(bookChan chan<- Book, doneChan <-chan bool) Book
	SendOrder(action, otype string, amount, price float64) (int64, error)
	CancelOrder(id int64) (bool, error)
	GetOrderStatus(id int64) (Order, error)
	HasCryptoFee() bool
}

// Order status data from the exchange *****************************************
type Order struct {
	FilledAmount float64 // Positive number for buys and sells
	Status       string  // "live" or "dead"
}

// Book data from the exchange *************************************************
type Book struct {
	Exg   Exchange
	Time  time.Time
	Bids  BidItems // Sort by price high to low
	Asks  AskItems // Sort by price low to high
	Error error
}

// BidItems data from the exchange
type BidItems []struct {
	Price  float64
	Amount float64
}

// AskItems data from the exchange
type AskItems []struct {
	Price  float64
	Amount float64
}

// Len implements sort.Interface on BidItems
func (items BidItems) Len() int {
	return len(items)
}

// Swap implements sort.Interface on BidItems
func (items BidItems) Swap(i, j int) {
	items[i], items[j] = items[j], items[i]
}

// Less implements sort.Interface on BidItems
func (items BidItems) Less(i, j int) bool {
	return items[i].Price > items[j].Price
}

// Len implements sort.Interface on AskItems
func (items AskItems) Len() int {
	return len(items)
}

// Swap implements sort.Interface on AskItems
func (items AskItems) Swap(i, j int) {
	items[i], items[j] = items[j], items[i]
}

// Less implements sort.Interface on AskItems
func (items AskItems) Less(i, j int) bool {
	return items[i].Price < items[j].Price
}
<|endoftext|>


<commit_before>/*
Copyright 2017 The Kubernetes 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 provision

import (
	"errors"
	"fmt"
	"net"
	"strings"

	"github.com/golang/glog"
	"github.com/kubernetes-incubator/external-storage/lib/controller"
	"github.com/kubernetes-incubator/external-storage/lib/util"
	"github.com/miekg/dns"
	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/apimachinery/pkg/util/uuid"
	"k8s.io/client-go/kubernetes"
	"k8s.io/kubernetes/pkg/apis/core/v1/helper"
	"k8s.io/kubernetes/pkg/volume"
)

const (
	// ProvisionerName is a unique string to represent this volume provisioner. This value will be
	// added in PV annotations under 'pv.kubernetes.io/provisioned-by' key.
	ProvisionerName = "ceph.com/rbd"
	// Each provisioner have a identify string to distinguish with others. This
	// identify string will be added in PV annoations under this key.
	provisionerIDAnn = "rbdProvisionerIdentity"

	secretKeyName   = "key" // key name used in secret
	rbdImageFormat1 = "1"
	rbdImageFormat2 = "2"
)

var (
	supportedFeatures = sets.NewString("layering")
)

// rbdProvisionOptions is internal representation of rbd provision options,
// parsed out from storage class parameters.
// https://github.com/kubernetes/website/blob/master/docs/concepts/storage/storage-classes.md#ceph-rbd
type rbdProvisionOptions struct {
	// Ceph monitors.
	monitors []string
	// Ceph RBD pool. Default is "rbd".
	pool string
	// Ceph client ID that is capable of creating images in the pool. Default is "admin".
	adminID string
	// Secret of admin client ID.
	adminSecret string
	// Ceph client ID that is used to map the RBD image. Default is the same as admin client ID.
	userID string
	// The name of Ceph Secret for userID to map RBD image. This parameter is required.
	userSecretName string
	// The namespace of Ceph Secret for userID to map RBD image. This parameter is optional.
	userSecretNamespace string
	// fsType that is supported by kubernetes. Default: "ext4".
	fsType string
	// Ceph RBD image format, "1" or "2". Default is "1".
	imageFormat string
	// This parameter is optional and should only be used if you set
	// imageFormat to "2". Currently supported features are layering only.
	// Default is "", and no features are turned on.
	imageFeatures []string
}

type rbdProvisioner struct {
	// Kubernetes Client. Use to retrieve Ceph admin secret
	client kubernetes.Interface
	// Identity of this rbdProvisioner, generated. Used to identify "this"
	// provisioner's PVs.
	identity string
	rbdUtil  *RBDUtil
	dnsip    string
}

// NewRBDProvisioner creates a Provisioner that provisions Ceph RBD PVs backed by Ceph RBD images.
func NewRBDProvisioner(client kubernetes.Interface, id string) controller.Provisioner {
	return &rbdProvisioner{
		client:   client,
		identity: id,
		rbdUtil:  &RBDUtil{},
	}
}

var _ controller.Provisioner = &rbdProvisioner{}

// getAccessModes returns access modes RBD volume supported.
func (p *rbdProvisioner) getAccessModes() []v1.PersistentVolumeAccessMode {
	return []v1.PersistentVolumeAccessMode{
		v1.ReadWriteOnce,
		v1.ReadOnlyMany,
	}
}

// Provision creates a storage asset and returns a PV object representing it.
func (p *rbdProvisioner) Provision(options controller.VolumeOptions) (*v1.PersistentVolume, error) {
	if !util.AccessModesContainedInAll(p.getAccessModes(), options.PVC.Spec.AccessModes) {
		return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", options.PVC.Spec.AccessModes, p.getAccessModes())
	}
	if options.PVC.Spec.Selector != nil {
		return nil, fmt.Errorf("claim Selector is not supported")
	}
	opts, err := p.parseParameters(options.Parameters)
	if err != nil {
		return nil, err
	}
	// create random image name
	image := fmt.Sprintf("kubernetes-dynamic-pvc-%s", uuid.NewUUID())
	rbd, sizeMB, err := p.rbdUtil.CreateImage(image, opts, options)
	if err != nil {
		glog.Errorf("rbd: create volume failed, err: %v", err)
		return nil, err
	}
	glog.Infof("successfully created rbd image %q", image)

	rbd.SecretRef = new(v1.SecretReference)
	rbd.SecretRef.Name = opts.userSecretName
	if len(opts.userSecretNamespace) > 0 {
		rbd.SecretRef.Namespace = opts.userSecretNamespace
	}
	rbd.RadosUser = opts.userID

	pv := &v1.PersistentVolume{
		ObjectMeta: metav1.ObjectMeta{
			Name: options.PVName,
			Annotations: map[string]string{
				provisionerIDAnn: p.identity,
			},
		},
		Spec: v1.PersistentVolumeSpec{
			PersistentVolumeReclaimPolicy: options.PersistentVolumeReclaimPolicy,
			AccessModes:                   options.PVC.Spec.AccessModes,
			Capacity: v1.ResourceList{
				v1.ResourceName(v1.ResourceStorage): resource.MustParse(fmt.Sprintf("%dMi", sizeMB)),
			},
			PersistentVolumeSource: v1.PersistentVolumeSource{
				RBD: rbd,
			},
		},
	}
	// use default access modes if missing
	if len(pv.Spec.AccessModes) == 0 {
		glog.Warningf("no access modes specified, use default: %v", p.getAccessModes())
		pv.Spec.AccessModes = p.getAccessModes()
	}

	return pv, nil
}

// Delete removes the storage asset that was created by Provision represented
// by the given PV.
func (p *rbdProvisioner) Delete(volume *v1.PersistentVolume) error {
	// TODO: Should we check `pv.kubernetes.io/provisioned-by` key too?
	ann, ok := volume.Annotations[provisionerIDAnn]
	if !ok {
		return errors.New("identity annotation not found on PV")
	}
	if ann != p.identity {
		return &controller.IgnoredError{Reason: "identity annotation on PV does not match ours"}
	}

	class, err := p.client.StorageV1beta1().StorageClasses().Get(helper.GetPersistentVolumeClass(volume), metav1.GetOptions{})
	if err != nil {
		return err
	}
	opts, err := p.parseParameters(class.Parameters)
	if err != nil {
		return err
	}
	image := volume.Spec.PersistentVolumeSource.RBD.RBDImage
	return p.rbdUtil.DeleteImage(image, opts)
}

// Look up the cluster dns service by label "kube-dns"
func findDNSIP(p *rbdProvisioner) (dnsip string) {
	// find DNS server address through client API
	// cache result in rbdProvisioner
	if p.dnsip == "" {
		dnssvc, err := p.client.CoreV1().Services(metav1.NamespaceSystem).Get("kube-dns", metav1.GetOptions{})
		if err != nil {
			glog.Errorf("error getting kube-dns service: %v\n", err)
			return ""
		}
		if len(dnssvc.Spec.ClusterIP) == 0 {
			glog.Errorf("kube-dns service ClusterIP bad\n")
			return ""
		}
		p.dnsip = dnssvc.Spec.ClusterIP
	}
	return p.dnsip
}

// Look up hostname in dns server serverip.
func lookuphost(hostname string, serverip string) (iplist []string, err error) {
	glog.V(4).Infof("lookuphost %q on %q\n", hostname, serverip)
	m := new(dns.Msg)
	m.SetQuestion(dns.Fqdn(hostname), dns.TypeA)
	in, err := dns.Exchange(m, joinHostPort(serverip, "53"))
	if err != nil {
		glog.Errorf("dns lookup of %q failed: err %v", hostname, err)
		return nil, err
	}
	for _, a := range in.Answer {
		glog.V(4).Infof("lookuphost answer: %v\n", a)
		if t, ok := a.(*dns.A); ok {
			iplist = append(iplist, t.A.String())
		}
	}

	return iplist, nil
}

func splitHostPort(hostport string) (host, port string) {
	host, port, err := net.SplitHostPort(hostport)
	if err != nil {
		host, port = hostport, ""
	}
	return host, port
}

func joinHostPort(host, port string) (hostport string) {
	if port != "" {
		return net.JoinHostPort(host, port)
	}
	return host
}

func (p *rbdProvisioner) parseParameters(parameters map[string]string) (*rbdProvisionOptions, error) {
	// options with default values
	opts := &rbdProvisionOptions{
		pool:        "rbd",
		adminID:     "admin",
		imageFormat: rbdImageFormat1,
	}

	var (
		err                  error
		adminSecretName      = ""
		adminSecretNamespace = "default"
	)

	for k, v := range parameters {
		switch strings.ToLower(k) {
		case "monitors":
			// Try to find DNS info in local cluster DNS so that the kubernetes
			// host DNS config doesn't have to know about cluster DNS
			dnsip := findDNSIP(p)
			glog.V(4).Infof("dnsip: %q\n", dnsip)
			arr := strings.Split(v, ",")
			for _, m := range arr {
				mhost, mport := splitHostPort(m)
				if dnsip != "" {
					var lookup []string
					if lookup, err = lookuphost(mhost, dnsip); err == nil {
						for _, a := range lookup {
							glog.V(1).Infof("adding %+v from mon lookup\n", a)
							opts.monitors = append(opts.monitors, joinHostPort(a, mport))
						}
					} else {
						opts.monitors = append(opts.monitors, joinHostPort(mhost, mport))
					}
				} else {
					opts.monitors = append(opts.monitors, joinHostPort(mhost, mport))
				}
			}
			glog.V(4).Infof("final monitors list: %v\n", opts.monitors)
			if len(opts.monitors) < 1 {
				return nil, fmt.Errorf("missing Ceph monitors")
			}
		case "adminid":
			if v == "" {
				// keep consistent behavior with in-tree rbd provisioner, which use default value if user provides empty string
				// TODO: treat empty string invalid value?
				v = "admin"
			}
			opts.adminID = v
		case "adminsecretname":
			adminSecretName = v
		case "adminsecretnamespace":
			adminSecretNamespace = v
		case "userid":
			opts.userID = v
		case "pool":
			if v == "" {
				// keep consistent behavior with in-tree rbd provisioner, which use default value if user provides empty string
				// TODO: treat empty string invalid value?
				v = "rbd"
			}
			opts.pool = v
		case "usersecretname":
			if v == "" {
				return nil, fmt.Errorf("missing user secret name")
			}
			opts.userSecretName = v
		case "usersecretnamespace":
			opts.userSecretNamespace = v
		case "imageformat":
			if v != rbdImageFormat1 && v != rbdImageFormat2 {
				return nil, fmt.Errorf("invalid ceph imageformat %s, expecting %s or %s", v, rbdImageFormat1, rbdImageFormat2)
			}
			opts.imageFormat = v
		case "imagefeatures":
			arr := strings.Split(v, ",")
			for _, f := range arr {
				if !supportedFeatures.Has(f) {
					return nil, fmt.Errorf("invalid feature %q for %s provisioner, supported features are: %v", f, ProvisionerName, supportedFeatures)
				}
				opts.imageFeatures = append(opts.imageFeatures, f)
			}
		case volume.VolumeParameterFSType:
			opts.fsType = v
		default:
			return nil, fmt.Errorf("invalid option %q for %s provisioner", k, ProvisionerName)
		}
	}

	// find adminSecret
	var secret string
	if adminSecretName == "" {
		return nil, fmt.Errorf("missing Ceph admin secret name")
	}
	if secret, err = p.parsePVSecret(adminSecretNamespace, adminSecretName); err != nil {
		return nil, fmt.Errorf("failed to get admin secret from [%q/%q]: %v", adminSecretNamespace, adminSecretName, err)
	}
	opts.adminSecret = secret

	// set user ID to admin ID if empty
	if opts.userID == "" {
		opts.userID = opts.adminID
	}

	return opts, nil
}

// parsePVSecret retrives secret value for a given namespace and name.
func (p *rbdProvisioner) parsePVSecret(namespace, secretName string) (string, error) {
	if p.client == nil {
		return "", fmt.Errorf("Cannot get kube client")
	}
	secrets, err := p.client.CoreV1().Secrets(namespace).Get(secretName, metav1.GetOptions{})
	if err != nil {
		return "", err
	}
	// TODO: Should we check secret.Type, like `k8s.io/kubernetes/pkg/volume/util.GetSecretForPV` function?
	secret := ""
	for k, v := range secrets.Data {
		if k == secretKeyName {
			return string(v), nil
		}
		secret = string(v)
	}

	// If not found, the last secret in the map wins as done before
	return secret, nil
}
<commit_msg>Add support for coredns<commit_after>/*
Copyright 2017 The Kubernetes 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 provision

import (
	"errors"
	"fmt"
	"net"
	"strings"

	"github.com/golang/glog"
	"github.com/kubernetes-incubator/external-storage/lib/controller"
	"github.com/kubernetes-incubator/external-storage/lib/util"
	"github.com/miekg/dns"
	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/apimachinery/pkg/util/uuid"
	"k8s.io/client-go/kubernetes"
	"k8s.io/kubernetes/pkg/apis/core/v1/helper"
	"k8s.io/kubernetes/pkg/volume"
)

const (
	// ProvisionerName is a unique string to represent this volume provisioner. This value will be
	// added in PV annotations under 'pv.kubernetes.io/provisioned-by' key.
	ProvisionerName = "ceph.com/rbd"
	// Each provisioner have a identify string to distinguish with others. This
	// identify string will be added in PV annoations under this key.
	provisionerIDAnn = "rbdProvisionerIdentity"

	secretKeyName   = "key" // key name used in secret
	rbdImageFormat1 = "1"
	rbdImageFormat2 = "2"
)

var (
	supportedFeatures = sets.NewString("layering")
)

// rbdProvisionOptions is internal representation of rbd provision options,
// parsed out from storage class parameters.
// https://github.com/kubernetes/website/blob/master/docs/concepts/storage/storage-classes.md#ceph-rbd
type rbdProvisionOptions struct {
	// Ceph monitors.
	monitors []string
	// Ceph RBD pool. Default is "rbd".
	pool string
	// Ceph client ID that is capable of creating images in the pool. Default is "admin".
	adminID string
	// Secret of admin client ID.
	adminSecret string
	// Ceph client ID that is used to map the RBD image. Default is the same as admin client ID.
	userID string
	// The name of Ceph Secret for userID to map RBD image. This parameter is required.
	userSecretName string
	// The namespace of Ceph Secret for userID to map RBD image. This parameter is optional.
	userSecretNamespace string
	// fsType that is supported by kubernetes. Default: "ext4".
	fsType string
	// Ceph RBD image format, "1" or "2". Default is "1".
	imageFormat string
	// This parameter is optional and should only be used if you set
	// imageFormat to "2". Currently supported features are layering only.
	// Default is "", and no features are turned on.
	imageFeatures []string
}

type rbdProvisioner struct {
	// Kubernetes Client. Use to retrieve Ceph admin secret
	client kubernetes.Interface
	// Identity of this rbdProvisioner, generated. Used to identify "this"
	// provisioner's PVs.
	identity string
	rbdUtil  *RBDUtil
	dnsip    string
}

// NewRBDProvisioner creates a Provisioner that provisions Ceph RBD PVs backed by Ceph RBD images.
func NewRBDProvisioner(client kubernetes.Interface, id string) controller.Provisioner {
	return &rbdProvisioner{
		client:   client,
		identity: id,
		rbdUtil:  &RBDUtil{},
	}
}

var _ controller.Provisioner = &rbdProvisioner{}

// getAccessModes returns access modes RBD volume supported.
func (p *rbdProvisioner) getAccessModes() []v1.PersistentVolumeAccessMode {
	return []v1.PersistentVolumeAccessMode{
		v1.ReadWriteOnce,
		v1.ReadOnlyMany,
	}
}

// Provision creates a storage asset and returns a PV object representing it.
func (p *rbdProvisioner) Provision(options controller.VolumeOptions) (*v1.PersistentVolume, error) {
	if !util.AccessModesContainedInAll(p.getAccessModes(), options.PVC.Spec.AccessModes) {
		return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", options.PVC.Spec.AccessModes, p.getAccessModes())
	}
	if options.PVC.Spec.Selector != nil {
		return nil, fmt.Errorf("claim Selector is not supported")
	}
	opts, err := p.parseParameters(options.Parameters)
	if err != nil {
		return nil, err
	}
	// create random image name
	image := fmt.Sprintf("kubernetes-dynamic-pvc-%s", uuid.NewUUID())
	rbd, sizeMB, err := p.rbdUtil.CreateImage(image, opts, options)
	if err != nil {
		glog.Errorf("rbd: create volume failed, err: %v", err)
		return nil, err
	}
	glog.Infof("successfully created rbd image %q", image)

	rbd.SecretRef = new(v1.SecretReference)
	rbd.SecretRef.Name = opts.userSecretName
	if len(opts.userSecretNamespace) > 0 {
		rbd.SecretRef.Namespace = opts.userSecretNamespace
	}
	rbd.RadosUser = opts.userID

	pv := &v1.PersistentVolume{
		ObjectMeta: metav1.ObjectMeta{
			Name: options.PVName,
			Annotations: map[string]string{
				provisionerIDAnn: p.identity,
			},
		},
		Spec: v1.PersistentVolumeSpec{
			PersistentVolumeReclaimPolicy: options.PersistentVolumeReclaimPolicy,
			AccessModes:                   options.PVC.Spec.AccessModes,
			Capacity: v1.ResourceList{
				v1.ResourceName(v1.ResourceStorage): resource.MustParse(fmt.Sprintf("%dMi", sizeMB)),
			},
			PersistentVolumeSource: v1.PersistentVolumeSource{
				RBD: rbd,
			},
		},
	}
	// use default access modes if missing
	if len(pv.Spec.AccessModes) == 0 {
		glog.Warningf("no access modes specified, use default: %v", p.getAccessModes())
		pv.Spec.AccessModes = p.getAccessModes()
	}

	return pv, nil
}

// Delete removes the storage asset that was created by Provision represented
// by the given PV.
func (p *rbdProvisioner) Delete(volume *v1.PersistentVolume) error {
	// TODO: Should we check `pv.kubernetes.io/provisioned-by` key too?
	ann, ok := volume.Annotations[provisionerIDAnn]
	if !ok {
		return errors.New("identity annotation not found on PV")
	}
	if ann != p.identity {
		return &controller.IgnoredError{Reason: "identity annotation on PV does not match ours"}
	}

	class, err := p.client.StorageV1beta1().StorageClasses().Get(helper.GetPersistentVolumeClass(volume), metav1.GetOptions{})
	if err != nil {
		return err
	}
	opts, err := p.parseParameters(class.Parameters)
	if err != nil {
		return err
	}
	image := volume.Spec.PersistentVolumeSource.RBD.RBDImage
	return p.rbdUtil.DeleteImage(image, opts)
}

// Look up the cluster dns service by label "kube-dns"
func findDNSIP(p *rbdProvisioner) (dnsip string) {
	// find DNS server address through client API
	// cache result in rbdProvisioner
	var dnssvc *v1.Service

	if p.dnsip == "" {
		coredns, err := p.client.CoreV1().Services(metav1.NamespaceSystem).Get("coredns", metav1.GetOptions{})

		if err != nil {
			glog.Warningf("error getting coredns service: %v. Falling back to kube-dns\n", err)
			kubedns, err := p.client.CoreV1().Services(metav1.NamespaceSystem).Get("kube-dns", metav1.GetOptions{})
			if err != nil {
				glog.Errorf("error getting kube-dns service: %v\n", err)
				return ""
			}
			dnssvc = kubedns
		} else {
			dnssvc = coredns
		}

		if len(dnssvc.Spec.ClusterIP) == 0 {
			glog.Errorf("DNS service ClusterIP bad\n")
			return ""
		}

		p.dnsip = dnssvc.Spec.ClusterIP
	}

	return p.dnsip
}

// Look up hostname in dns server serverip.
func lookuphost(hostname string, serverip string) (iplist []string, err error) {
	glog.V(4).Infof("lookuphost %q on %q\n", hostname, serverip)
	m := new(dns.Msg)
	m.SetQuestion(dns.Fqdn(hostname), dns.TypeA)
	in, err := dns.Exchange(m, joinHostPort(serverip, "53"))
	if err != nil {
		glog.Errorf("dns lookup of %q failed: err %v", hostname, err)
		return nil, err
	}
	for _, a := range in.Answer {
		glog.V(4).Infof("lookuphost answer: %v\n", a)
		if t, ok := a.(*dns.A); ok {
			iplist = append(iplist, t.A.String())
		}
	}

	return iplist, nil
}

func splitHostPort(hostport string) (host, port string) {
	host, port, err := net.SplitHostPort(hostport)
	if err != nil {
		host, port = hostport, ""
	}
	return host, port
}

func joinHostPort(host, port string) (hostport string) {
	if port != "" {
		return net.JoinHostPort(host, port)
	}
	return host
}

func (p *rbdProvisioner) parseParameters(parameters map[string]string) (*rbdProvisionOptions, error) {
	// options with default values
	opts := &rbdProvisionOptions{
		pool:        "rbd",
		adminID:     "admin",
		imageFormat: rbdImageFormat1,
	}

	var (
		err                  error
		adminSecretName      = ""
		adminSecretNamespace = "default"
	)

	for k, v := range parameters {
		switch strings.ToLower(k) {
		case "monitors":
			// Try to find DNS info in local cluster DNS so that the kubernetes
			// host DNS config doesn't have to know about cluster DNS
			dnsip := findDNSIP(p)
			glog.V(4).Infof("dnsip: %q\n", dnsip)
			arr := strings.Split(v, ",")
			for _, m := range arr {
				mhost, mport := splitHostPort(m)
				if dnsip != "" {
					var lookup []string
					if lookup, err = lookuphost(mhost, dnsip); err == nil {
						for _, a := range lookup {
							glog.V(1).Infof("adding %+v from mon lookup\n", a)
							opts.monitors = append(opts.monitors, joinHostPort(a, mport))
						}
					} else {
						opts.monitors = append(opts.monitors, joinHostPort(mhost, mport))
					}
				} else {
					opts.monitors = append(opts.monitors, joinHostPort(mhost, mport))
				}
			}
			glog.V(4).Infof("final monitors list: %v\n", opts.monitors)
			if len(opts.monitors) < 1 {
				return nil, fmt.Errorf("missing Ceph monitors")
			}
		case "adminid":
			if v == "" {
				// keep consistent behavior with in-tree rbd provisioner, which use default value if user provides empty string
				// TODO: treat empty string invalid value?
				v = "admin"
			}
			opts.adminID = v
		case "adminsecretname":
			adminSecretName = v
		case "adminsecretnamespace":
			adminSecretNamespace = v
		case "userid":
			opts.userID = v
		case "pool":
			if v == "" {
				// keep consistent behavior with in-tree rbd provisioner, which use default value if user provides empty string
				// TODO: treat empty string invalid value?
				v = "rbd"
			}
			opts.pool = v
		case "usersecretname":
			if v == "" {
				return nil, fmt.Errorf("missing user secret name")
			}
			opts.userSecretName = v
		case "usersecretnamespace":
			opts.userSecretNamespace = v
		case "imageformat":
			if v != rbdImageFormat1 && v != rbdImageFormat2 {
				return nil, fmt.Errorf("invalid ceph imageformat %s, expecting %s or %s", v, rbdImageFormat1, rbdImageFormat2)
			}
			opts.imageFormat = v
		case "imagefeatures":
			arr := strings.Split(v, ",")
			for _, f := range arr {
				if !supportedFeatures.Has(f) {
					return nil, fmt.Errorf("invalid feature %q for %s provisioner, supported features are: %v", f, ProvisionerName, supportedFeatures)
				}
				opts.imageFeatures = append(opts.imageFeatures, f)
			}
		case volume.VolumeParameterFSType:
			opts.fsType = v
		default:
			return nil, fmt.Errorf("invalid option %q for %s provisioner", k, ProvisionerName)
		}
	}

	// find adminSecret
	var secret string
	if adminSecretName == "" {
		return nil, fmt.Errorf("missing Ceph admin secret name")
	}
	if secret, err = p.parsePVSecret(adminSecretNamespace, adminSecretName); err != nil {
		return nil, fmt.Errorf("failed to get admin secret from [%q/%q]: %v", adminSecretNamespace, adminSecretName, err)
	}
	opts.adminSecret = secret

	// set user ID to admin ID if empty
	if opts.userID == "" {
		opts.userID = opts.adminID
	}

	return opts, nil
}

// parsePVSecret retrives secret value for a given namespace and name.
func (p *rbdProvisioner) parsePVSecret(namespace, secretName string) (string, error) {
	if p.client == nil {
		return "", fmt.Errorf("Cannot get kube client")
	}
	secrets, err := p.client.CoreV1().Secrets(namespace).Get(secretName, metav1.GetOptions{})
	if err != nil {
		return "", err
	}
	// TODO: Should we check secret.Type, like `k8s.io/kubernetes/pkg/volume/util.GetSecretForPV` function?
	secret := ""
	for k, v := range secrets.Data {
		if k == secretKeyName {
			return string(v), nil
		}
		secret = string(v)
	}

	// If not found, the last secret in the map wins as done before
	return secret, nil
}
<|endoftext|>