<commit_before>/*
 Copyright 2016 Padduck, LLC

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

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

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

package operations

import (
	"github.com/pufferpanel/pufferpanel/v2"
	"github.com/pufferpanel/pufferpanel/v2/operations/alterfile"
	"github.com/pufferpanel/pufferpanel/v2/operations/archive"
	"github.com/pufferpanel/pufferpanel/v2/operations/command"
	"github.com/pufferpanel/pufferpanel/v2/operations/console"
	"github.com/pufferpanel/pufferpanel/v2/operations/download"
	"github.com/pufferpanel/pufferpanel/v2/operations/extract"
	"github.com/pufferpanel/pufferpanel/v2/operations/fabricdl"
	"github.com/pufferpanel/pufferpanel/v2/operations/forgedl"
	"github.com/pufferpanel/pufferpanel/v2/operations/mkdir"
	"github.com/pufferpanel/pufferpanel/v2/operations/mojangdl"
	"github.com/pufferpanel/pufferpanel/v2/operations/move"
	"github.com/pufferpanel/pufferpanel/v2/operations/sleep"
	"github.com/pufferpanel/pufferpanel/v2/operations/spongeforgedl"
	"github.com/pufferpanel/pufferpanel/v2/operations/writefile"
	"github.com/spf13/cast"
)

var commandMapping map[string]pufferpanel.OperationFactory

func LoadOperations() {
	commandMapping = make(map[string]pufferpanel.OperationFactory)

	loadCoreModules()
}

func GenerateProcess(directions []interface{}, environment pufferpanel.Environment, dataMapping map[string]interface{}, env map[string]string) (OperationProcess, error) {
	dataMap := make(map[string]interface{})
	for k, v := range dataMapping {
		dataMap[k] = v
	}

	dataMap["rootDir"] = environment.GetRootDirectory()
	operationList := make([]pufferpanel.Operation, 0)
	for _, mapping := range directions {

		var typeMap pufferpanel.MetadataType
		err := pufferpanel.UnmarshalTo(mapping, &typeMap)
		if err != nil {
			return OperationProcess{}, err
		}

		factory := commandMapping[typeMap.Type]
		if factory == nil {
			return OperationProcess{}, pufferpanel.ErrMissingFactory
		}

		mapCopy := make(map[string]interface{}, 0)

		//replace tokens
		for k, v := range typeMap.Metadata {
			switch r := v.(type) {
			case string:
				{
					mapCopy[k] = pufferpanel.ReplaceTokens(r, dataMap)
				}
			case []string:
				{
					mapCopy[k] = pufferpanel.ReplaceTokensInArr(r, dataMap)
				}
			case map[string]string:
				{
					mapCopy[k] = pufferpanel.ReplaceTokensInMap(r, dataMap)
				}
			case []interface{}:
				{
					//if we can convert this to a string list, we can work with it
					temp := cast.ToStringSlice(r)
					if len(temp) == len(r) {
						mapCopy[k] = pufferpanel.ReplaceTokensInArr(temp, dataMap)
					} else {
						mapCopy[k] = v
					}
				}
			default:
				mapCopy[k] = v
			}
		}

		envMap := pufferpanel.ReplaceTokensInMap(env, dataMap)

		opCreate := pufferpanel.CreateOperation{
			OperationArgs:        mapCopy,
			EnvironmentVariables: envMap,
			DataMap:              dataMap,
		}

		op, err := factory.Create(opCreate)
		if err != nil {
			return OperationProcess{}, pufferpanel.ErrFactoryError(typeMap.Type, err)
		}

		operationList = append(operationList, op)
	}
	return OperationProcess{processInstructions: operationList}, nil
}

type OperationProcess struct {
	processInstructions []pufferpanel.Operation
}

func (p *OperationProcess) Run(env pufferpanel.Environment) (err error) {
	for p.HasNext() {
		err = p.RunNext(env)
		if err != nil {
			break
		}
	}
	return
}

func (p *OperationProcess) RunNext(env pufferpanel.Environment) error {
	var op pufferpanel.Operation
	op, p.processInstructions = p.processInstructions[0], p.processInstructions[1:]
	err := op.Run(env)
	return err
}

func (p *OperationProcess) HasNext() bool {
	return len(p.processInstructions) != 0 && p.processInstructions[0] != nil
}

func loadCoreModules() {
	commandFactory := command.Factory
	commandMapping[commandFactory.Key()] = commandFactory

	downloadFactory := download.Factory
	commandMapping[downloadFactory.Key()] = downloadFactory

	mkdirFactory := mkdir.Factory
	commandMapping[mkdirFactory.Key()] = mkdirFactory

	moveFactory := move.Factory
	commandMapping[moveFactory.Key()] = moveFactory

	alterFileFactory := alterfile.Factory
	commandMapping[alterFileFactory.Key()] = alterFileFactory

	writeFileFactory := writefile.Factory
	commandMapping[writeFileFactory.Key()] = writeFileFactory

	mojangFactory := mojangdl.Factory
	commandMapping[mojangFactory.Key()] = mojangFactory

	spongeforgeDlFactory := spongeforgedl.Factory
	commandMapping[spongeforgeDlFactory.Key()] = spongeforgeDlFactory

	forgeDlFactory := forgedl.Factory
	commandMapping[forgeDlFactory.Key()] = forgeDlFactory

	fabricDlFactory := fabricdl.Factory
	commandMapping[fabricDlFactory.Key()] = fabricDlFactory

	sleepFactory := sleep.Factory
	commandMapping[sleepFactory.Key()] = sleepFactory

	consoleFactory := console.Factory
	commandMapping[consoleFactory.Key()] = consoleFactory

	archiveFactory := archive.Factory
	commandMapping[consoleFactory.Key()] = archiveFactory

	extractFactory := extract.Factory
	commandMapping[consoleFactory.Key()] = extractFactory
}
<commit_msg>Goofed up some of the operation names (#1041)<commit_after>/*
 Copyright 2016 Padduck, LLC

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

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

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

package operations

import (
	"github.com/pufferpanel/pufferpanel/v2"
	"github.com/pufferpanel/pufferpanel/v2/operations/alterfile"
	"github.com/pufferpanel/pufferpanel/v2/operations/archive"
	"github.com/pufferpanel/pufferpanel/v2/operations/command"
	"github.com/pufferpanel/pufferpanel/v2/operations/console"
	"github.com/pufferpanel/pufferpanel/v2/operations/download"
	"github.com/pufferpanel/pufferpanel/v2/operations/extract"
	"github.com/pufferpanel/pufferpanel/v2/operations/fabricdl"
	"github.com/pufferpanel/pufferpanel/v2/operations/forgedl"
	"github.com/pufferpanel/pufferpanel/v2/operations/mkdir"
	"github.com/pufferpanel/pufferpanel/v2/operations/mojangdl"
	"github.com/pufferpanel/pufferpanel/v2/operations/move"
	"github.com/pufferpanel/pufferpanel/v2/operations/sleep"
	"github.com/pufferpanel/pufferpanel/v2/operations/spongeforgedl"
	"github.com/pufferpanel/pufferpanel/v2/operations/writefile"
	"github.com/spf13/cast"
)

var commandMapping map[string]pufferpanel.OperationFactory

func LoadOperations() {
	commandMapping = make(map[string]pufferpanel.OperationFactory)

	loadCoreModules()
}

func GenerateProcess(directions []interface{}, environment pufferpanel.Environment, dataMapping map[string]interface{}, env map[string]string) (OperationProcess, error) {
	dataMap := make(map[string]interface{})
	for k, v := range dataMapping {
		dataMap[k] = v
	}

	dataMap["rootDir"] = environment.GetRootDirectory()
	operationList := make([]pufferpanel.Operation, 0)
	for _, mapping := range directions {

		var typeMap pufferpanel.MetadataType
		err := pufferpanel.UnmarshalTo(mapping, &typeMap)
		if err != nil {
			return OperationProcess{}, err
		}

		factory := commandMapping[typeMap.Type]
		if factory == nil {
			return OperationProcess{}, pufferpanel.ErrMissingFactory
		}

		mapCopy := make(map[string]interface{}, 0)

		//replace tokens
		for k, v := range typeMap.Metadata {
			switch r := v.(type) {
			case string:
				{
					mapCopy[k] = pufferpanel.ReplaceTokens(r, dataMap)
				}
			case []string:
				{
					mapCopy[k] = pufferpanel.ReplaceTokensInArr(r, dataMap)
				}
			case map[string]string:
				{
					mapCopy[k] = pufferpanel.ReplaceTokensInMap(r, dataMap)
				}
			case []interface{}:
				{
					//if we can convert this to a string list, we can work with it
					temp := cast.ToStringSlice(r)
					if len(temp) == len(r) {
						mapCopy[k] = pufferpanel.ReplaceTokensInArr(temp, dataMap)
					} else {
						mapCopy[k] = v
					}
				}
			default:
				mapCopy[k] = v
			}
		}

		envMap := pufferpanel.ReplaceTokensInMap(env, dataMap)

		opCreate := pufferpanel.CreateOperation{
			OperationArgs:        mapCopy,
			EnvironmentVariables: envMap,
			DataMap:              dataMap,
		}

		op, err := factory.Create(opCreate)
		if err != nil {
			return OperationProcess{}, pufferpanel.ErrFactoryError(typeMap.Type, err)
		}

		operationList = append(operationList, op)
	}
	return OperationProcess{processInstructions: operationList}, nil
}

type OperationProcess struct {
	processInstructions []pufferpanel.Operation
}

func (p *OperationProcess) Run(env pufferpanel.Environment) (err error) {
	for p.HasNext() {
		err = p.RunNext(env)
		if err != nil {
			break
		}
	}
	return
}

func (p *OperationProcess) RunNext(env pufferpanel.Environment) error {
	var op pufferpanel.Operation
	op, p.processInstructions = p.processInstructions[0], p.processInstructions[1:]
	err := op.Run(env)
	return err
}

func (p *OperationProcess) HasNext() bool {
	return len(p.processInstructions) != 0 && p.processInstructions[0] != nil
}

func loadCoreModules() {
	commandFactory := command.Factory
	commandMapping[commandFactory.Key()] = commandFactory

	downloadFactory := download.Factory
	commandMapping[downloadFactory.Key()] = downloadFactory

	mkdirFactory := mkdir.Factory
	commandMapping[mkdirFactory.Key()] = mkdirFactory

	moveFactory := move.Factory
	commandMapping[moveFactory.Key()] = moveFactory

	alterFileFactory := alterfile.Factory
	commandMapping[alterFileFactory.Key()] = alterFileFactory

	writeFileFactory := writefile.Factory
	commandMapping[writeFileFactory.Key()] = writeFileFactory

	mojangFactory := mojangdl.Factory
	commandMapping[mojangFactory.Key()] = mojangFactory

	spongeforgeDlFactory := spongeforgedl.Factory
	commandMapping[spongeforgeDlFactory.Key()] = spongeforgeDlFactory

	forgeDlFactory := forgedl.Factory
	commandMapping[forgeDlFactory.Key()] = forgeDlFactory

	fabricDlFactory := fabricdl.Factory
	commandMapping[fabricDlFactory.Key()] = fabricDlFactory

	sleepFactory := sleep.Factory
	commandMapping[sleepFactory.Key()] = sleepFactory

	consoleFactory := console.Factory
	commandMapping[consoleFactory.Key()] = consoleFactory

	archiveFactory := archive.Factory
	commandMapping[archiveFactory.Key()] = archiveFactory

	extractFactory := extract.Factory
	commandMapping[extractFactory.Key()] = extractFactory
}
<|endoftext|>


<commit_before>/*
Copyright 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 v1

import (
	"fmt"
	"sync"
	"time"

	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/util/managedfields"
	"k8s.io/client-go/discovery"
	"k8s.io/kube-openapi/pkg/util/proto"
	"sigs.k8s.io/structured-merge-diff/v4/typed"
)

// openAPISchemaTTL is how frequently we need to check
// whether the open API schema has changed or not.
const openAPISchemaTTL = time.Minute

// UnstructuredExtractor enables extracting the applied configuration state from object for fieldManager into an
// unstructured object type.
type UnstructuredExtractor interface {
	Extract(object *unstructured.Unstructured, fieldManager string) (*unstructured.Unstructured, error)
	ExtractStatus(object *unstructured.Unstructured, fieldManager string) (*unstructured.Unstructured, error)
}

// gvkParserCache caches the GVKParser in order to prevent from having to repeatedly
// parse the models from the open API schema when the schema itself changes infrequently.
type gvkParserCache struct {
	// discoveryClient is the client for retrieving the openAPI document and checking
	// whether the document has changed recently
	discoveryClient discovery.DiscoveryInterface
	// mu protects the gvkParser
	mu sync.Mutex
	// gvkParser retrieves the objectType for a given gvk
	gvkParser *managedfields.GvkParser
	// lastChecked is the last time we checked if the openAPI doc has changed.
	lastChecked time.Time
}

// regenerateGVKParser builds the parser from the raw OpenAPI schema.
func regenerateGVKParser(dc discovery.DiscoveryInterface) (*managedfields.GvkParser, error) {
	doc, err := dc.OpenAPISchema()
	if err != nil {
		return nil, err
	}

	models, err := proto.NewOpenAPIData(doc)
	if err != nil {
		return nil, err
	}

	return managedfields.NewGVKParser(models, false)
}

// objectTypeForGVK retrieves the typed.ParseableType for a given gvk from the cache
func (c *gvkParserCache) objectTypeForGVK(gvk schema.GroupVersionKind) (*typed.ParseableType, error) {
	c.mu.Lock()
	defer c.mu.Unlock()
	// if the ttl on the openAPISchema has expired,
	// regenerate the gvk parser
	if time.Since(c.lastChecked) > openAPISchemaTTL {
		c.lastChecked = time.Now()
		parser, err := regenerateGVKParser(c.discoveryClient)
		if err != nil {
			return nil, err
		}
		c.gvkParser = parser
	}
	return c.gvkParser.Type(gvk), nil
}

type extractor struct {
	cache *gvkParserCache
}

// NewUnstructuredExtractor creates the extractor with which you can extract the applied configuration
// for a given manager from an unstructured object.
func NewUnstructuredExtractor(dc discovery.DiscoveryInterface) (UnstructuredExtractor, error) {
	parser, err := regenerateGVKParser(dc)
	if err != nil {
		return nil, fmt.Errorf("failed generating initial GVK Parser: %v", err)
	}
	return &extractor{
		cache: &gvkParserCache{
			gvkParser:       parser,
			discoveryClient: dc,
		},
	}, nil
}

// Extract extracts the applied configuration owned by fiieldManager from an unstructured object.
// Note that the apply configuration itself is also an unstructured object.
func (e *extractor) Extract(object *unstructured.Unstructured, fieldManager string) (*unstructured.Unstructured, error) {
	return e.extractUnstructured(object, fieldManager, "")
}

// ExtractStatus is the same as ExtractUnstructured except
// that it extracts the status subresource applied configuration.
// Experimental!
func (e *extractor) ExtractStatus(object *unstructured.Unstructured, fieldManager string) (*unstructured.Unstructured, error) {
	return e.extractUnstructured(object, fieldManager, "status")
}

func (e *extractor) extractUnstructured(object *unstructured.Unstructured, fieldManager string, subresource string) (*unstructured.Unstructured, error) {
	gvk := object.GetObjectKind().GroupVersionKind()
	objectType, err := e.cache.objectTypeForGVK(gvk)
	if err != nil {
		return nil, fmt.Errorf("failed to fetch the objectType: %v", err)
	}
	result := &unstructured.Unstructured{}
	err = managedfields.ExtractInto(object, *objectType, fieldManager, result, subresource)
	if err != nil {
		return nil, fmt.Errorf("failed calling ExtractInto for unstructured: %v", err)
	}
	result.SetName(object.GetName())
	result.SetNamespace(object.GetNamespace())
	result.SetKind(object.GetKind())
	result.SetAPIVersion(object.GetAPIVersion())
	return result, nil
}
<commit_msg>fix boilerplate and staticcheck<commit_after>/*
Copyright 2021 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 v1

import (
	"fmt"
	"sync"
	"time"

	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/util/managedfields"
	"k8s.io/client-go/discovery"
	"k8s.io/kube-openapi/pkg/util/proto"
	"sigs.k8s.io/structured-merge-diff/v4/typed"
)

// openAPISchemaTTL is how frequently we need to check
// whether the open API schema has changed or not.
const openAPISchemaTTL = time.Minute

// UnstructuredExtractor enables extracting the applied configuration state from object for fieldManager into an
// unstructured object type.
type UnstructuredExtractor interface {
	Extract(object *unstructured.Unstructured, fieldManager string) (*unstructured.Unstructured, error)
	ExtractStatus(object *unstructured.Unstructured, fieldManager string) (*unstructured.Unstructured, error)
}

// gvkParserCache caches the GVKParser in order to prevent from having to repeatedly
// parse the models from the open API schema when the schema itself changes infrequently.
type gvkParserCache struct {
	// discoveryClient is the client for retrieving the openAPI document and checking
	// whether the document has changed recently
	discoveryClient discovery.DiscoveryInterface
	// mu protects the gvkParser
	mu sync.Mutex
	// gvkParser retrieves the objectType for a given gvk
	gvkParser *managedfields.GvkParser
	// lastChecked is the last time we checked if the openAPI doc has changed.
	lastChecked time.Time
}

// regenerateGVKParser builds the parser from the raw OpenAPI schema.
func regenerateGVKParser(dc discovery.DiscoveryInterface) (*managedfields.GvkParser, error) {
	doc, err := dc.OpenAPISchema()
	if err != nil {
		return nil, err
	}

	models, err := proto.NewOpenAPIData(doc)
	if err != nil {
		return nil, err
	}

	return managedfields.NewGVKParser(models, false)
}

// objectTypeForGVK retrieves the typed.ParseableType for a given gvk from the cache
func (c *gvkParserCache) objectTypeForGVK(gvk schema.GroupVersionKind) (*typed.ParseableType, error) {
	c.mu.Lock()
	defer c.mu.Unlock()
	// if the ttl on the openAPISchema has expired,
	// regenerate the gvk parser
	if time.Since(c.lastChecked) > openAPISchemaTTL {
		c.lastChecked = time.Now()
		parser, err := regenerateGVKParser(c.discoveryClient)
		if err != nil {
			return nil, err
		}
		c.gvkParser = parser
	}
	return c.gvkParser.Type(gvk), nil
}

type extractor struct {
	cache *gvkParserCache
}

// NewUnstructuredExtractor creates the extractor with which you can extract the applied configuration
// for a given manager from an unstructured object.
func NewUnstructuredExtractor(dc discovery.DiscoveryInterface) (UnstructuredExtractor, error) {
	parser, err := regenerateGVKParser(dc)
	if err != nil {
		return nil, fmt.Errorf("failed generating initial GVK Parser: %v", err)
	}
	return &extractor{
		cache: &gvkParserCache{
			gvkParser:       parser,
			discoveryClient: dc,
		},
	}, nil
}

// Extract extracts the applied configuration owned by fiieldManager from an unstructured object.
// Note that the apply configuration itself is also an unstructured object.
func (e *extractor) Extract(object *unstructured.Unstructured, fieldManager string) (*unstructured.Unstructured, error) {
	return e.extractUnstructured(object, fieldManager, "")
}

// ExtractStatus is the same as ExtractUnstructured except
// that it extracts the status subresource applied configuration.
// Experimental!
func (e *extractor) ExtractStatus(object *unstructured.Unstructured, fieldManager string) (*unstructured.Unstructured, error) {
	return e.extractUnstructured(object, fieldManager, "status")
}

func (e *extractor) extractUnstructured(object *unstructured.Unstructured, fieldManager string, subresource string) (*unstructured.Unstructured, error) {
	gvk := object.GetObjectKind().GroupVersionKind()
	objectType, err := e.cache.objectTypeForGVK(gvk)
	if err != nil {
		return nil, fmt.Errorf("failed to fetch the objectType: %v", err)
	}
	result := &unstructured.Unstructured{}
	err = managedfields.ExtractInto(object, *objectType, fieldManager, result, subresource)
	if err != nil {
		return nil, fmt.Errorf("failed calling ExtractInto for unstructured: %v", err)
	}
	result.SetName(object.GetName())
	result.SetNamespace(object.GetNamespace())
	result.SetKind(object.GetKind())
	result.SetAPIVersion(object.GetAPIVersion())
	return result, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2018 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 internal

import (
	"fmt"

	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/kube-openapi/pkg/schemaconv"
	"k8s.io/kube-openapi/pkg/util/proto"
	smdschema "sigs.k8s.io/structured-merge-diff/schema"
	"sigs.k8s.io/structured-merge-diff/typed"
)

// groupVersionKindExtensionKey is the key used to lookup the
// GroupVersionKind value for an object definition from the
// definition's "extensions" map.
const groupVersionKindExtensionKey = "x-kubernetes-group-version-kind"

type gvkParser struct {
	gvks   map[schema.GroupVersionKind]string
	parser typed.Parser
}

func (p *gvkParser) Type(gvk schema.GroupVersionKind) typed.ParseableType {
	typeName, ok := p.gvks[gvk]
	if !ok {
		return nil
	}
	return p.parser.Type(typeName)
}

func newGVKParser(models proto.Models) (*gvkParser, error) {
	typeSchema, err := schemaconv.ToSchema(models)
	if err != nil {
		return nil, fmt.Errorf("failed to convert models to schema: %v", err)
	}
	typeSchema = makeRawExtensionUntyped(typeSchema)
	parser := gvkParser{
		gvks: map[schema.GroupVersionKind]string{},
	}
	parser.parser = typed.Parser{Schema: *typeSchema}
	for _, modelName := range models.ListModels() {
		model := models.LookupModel(modelName)
		if model == nil {
			panic(fmt.Sprintf("ListModels returns a model that can't be looked-up for: %v", modelName))
		}
		gvkList := parseGroupVersionKind(model)
		for _, gvk := range gvkList {
			if len(gvk.Kind) > 0 {
				_, ok := parser.gvks[gvk]
				if ok {
					return nil, fmt.Errorf("duplicate entry for %v", gvk)
				}
				parser.gvks[gvk] = modelName
			}
		}
	}
	return &parser, nil
}

// Get and parse GroupVersionKind from the extension. Returns empty if it doesn't have one.
func parseGroupVersionKind(s proto.Schema) []schema.GroupVersionKind {
	extensions := s.GetExtensions()

	gvkListResult := []schema.GroupVersionKind{}

	// Get the extensions
	gvkExtension, ok := extensions[groupVersionKindExtensionKey]
	if !ok {
		return []schema.GroupVersionKind{}
	}

	// gvk extension must be a list of at least 1 element.
	gvkList, ok := gvkExtension.([]interface{})
	if !ok {
		return []schema.GroupVersionKind{}
	}

	for _, gvk := range gvkList {
		// gvk extension list must be a map with group, version, and
		// kind fields
		gvkMap, ok := gvk.(map[interface{}]interface{})
		if !ok {
			continue
		}
		group, ok := gvkMap["group"].(string)
		if !ok {
			continue
		}
		version, ok := gvkMap["version"].(string)
		if !ok {
			continue
		}
		kind, ok := gvkMap["kind"].(string)
		if !ok {
			continue
		}

		gvkListResult = append(gvkListResult, schema.GroupVersionKind{
			Group:   group,
			Version: version,
			Kind:    kind,
		})
	}

	return gvkListResult
}

// makeRawExtensionUntyped explicitly sets RawExtension's type in the schema to Untyped atomic
// TODO: remove this once kube-openapi is updated to include
// https://github.com/kubernetes/kube-openapi/pull/133
func makeRawExtensionUntyped(s *smdschema.Schema) *smdschema.Schema {
	s2 := &smdschema.Schema{}
	for _, t := range s.Types {
		t2 := t
		if t2.Name == "io.k8s.apimachinery.pkg.runtime.RawExtension" {
			t2.Atom = smdschema.Atom{
				Untyped: &smdschema.Untyped{},
			}
		}
		s2.Types = append(s2.Types, t2)
	}
	return s2
}
<commit_msg>Revert "Temporary fix for alpha features test"<commit_after>/*
Copyright 2018 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 internal

import (
	"fmt"

	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/kube-openapi/pkg/schemaconv"
	"k8s.io/kube-openapi/pkg/util/proto"
	"sigs.k8s.io/structured-merge-diff/typed"
)

// groupVersionKindExtensionKey is the key used to lookup the
// GroupVersionKind value for an object definition from the
// definition's "extensions" map.
const groupVersionKindExtensionKey = "x-kubernetes-group-version-kind"

type gvkParser struct {
	gvks   map[schema.GroupVersionKind]string
	parser typed.Parser
}

func (p *gvkParser) Type(gvk schema.GroupVersionKind) typed.ParseableType {
	typeName, ok := p.gvks[gvk]
	if !ok {
		return nil
	}
	return p.parser.Type(typeName)
}

func newGVKParser(models proto.Models) (*gvkParser, error) {
	typeSchema, err := schemaconv.ToSchema(models)
	if err != nil {
		return nil, fmt.Errorf("failed to convert models to schema: %v", err)
	}
	parser := gvkParser{
		gvks: map[schema.GroupVersionKind]string{},
	}
	parser.parser = typed.Parser{Schema: *typeSchema}
	for _, modelName := range models.ListModels() {
		model := models.LookupModel(modelName)
		if model == nil {
			panic(fmt.Sprintf("ListModels returns a model that can't be looked-up for: %v", modelName))
		}
		gvkList := parseGroupVersionKind(model)
		for _, gvk := range gvkList {
			if len(gvk.Kind) > 0 {
				_, ok := parser.gvks[gvk]
				if ok {
					return nil, fmt.Errorf("duplicate entry for %v", gvk)
				}
				parser.gvks[gvk] = modelName
			}
		}
	}
	return &parser, nil
}

// Get and parse GroupVersionKind from the extension. Returns empty if it doesn't have one.
func parseGroupVersionKind(s proto.Schema) []schema.GroupVersionKind {
	extensions := s.GetExtensions()

	gvkListResult := []schema.GroupVersionKind{}

	// Get the extensions
	gvkExtension, ok := extensions[groupVersionKindExtensionKey]
	if !ok {
		return []schema.GroupVersionKind{}
	}

	// gvk extension must be a list of at least 1 element.
	gvkList, ok := gvkExtension.([]interface{})
	if !ok {
		return []schema.GroupVersionKind{}
	}

	for _, gvk := range gvkList {
		// gvk extension list must be a map with group, version, and
		// kind fields
		gvkMap, ok := gvk.(map[interface{}]interface{})
		if !ok {
			continue
		}
		group, ok := gvkMap["group"].(string)
		if !ok {
			continue
		}
		version, ok := gvkMap["version"].(string)
		if !ok {
			continue
		}
		kind, ok := gvkMap["kind"].(string)
		if !ok {
			continue
		}

		gvkListResult = append(gvkListResult, schema.GroupVersionKind{
			Group:   group,
			Version: version,
			Kind:    kind,
		})
	}

	return gvkListResult
}
<|endoftext|>


<commit_before>package input

import "testing"

/*
 * RFC5424 parser tests
 */

func Test_SuccessfulRFC5424Parsing(t *testing.T) {
	p := NewRFC5424Parser()

	tests := []struct {
		message  string
		expected RFC5424Message
	}{
		{
			message:  "<134>1 2003-08-24T05:14:15.000003-07:00 ubuntu sshd 1999 - password accepted",
			expected: RFC5424Message{Priority: 134, Version: 1, Timestamp: "2003-08-24T05:14:15.000003-07:00", Host: "ubuntu", App: "sshd", Pid: 1999, MsgId: "-", Message: "password accepted"},
		},
		{
			message:  "<33>5 1985-04-12T23:20:50.52Z test.com cron 304 - password accepted",
			expected: RFC5424Message{Priority: 33, Version: 5, Timestamp: "1985-04-12T23:20:50.52Z", Host: "test.com", App: "cron", Pid: 304, MsgId: "-", Message: "password accepted"},
		},
		{
			message:  "<1>0 1985-04-12T19:20:50.52-04:00 test.com cron 65535 - password accepted",
			expected: RFC5424Message{Priority: 1, Version: 0, Timestamp: "1985-04-12T19:20:50.52-04:00", Host: "test.com", App: "cron", Pid: 65535, MsgId: "-", Message: "password accepted"},
		},
		{
			message:  "<1>0 2003-10-11T22:14:15.003Z test.com cron 65535 msgid1234 password accepted",
			expected: RFC5424Message{Priority: 1, Version: 0, Timestamp: "2003-10-11T22:14:15.003Z", Host: "test.com", App: "cron", Pid: 65535, MsgId: "msgid1234", Message: "password accepted"},
		},
		{
			message:  "<1>0 2003-08-24T05:14:15.000003-07:00 test.com cron 65535 - JVM NPE\nsome_file.java:48\n\tsome_other_file.java:902",
			expected: RFC5424Message{Priority: 1, Version: 0, Timestamp: "2003-08-24T05:14:15.000003-07:00", Host: "test.com", App: "cron", Pid: 65535, MsgId: "-", Message: "JVM NPE\nsome_file.java:48\n\tsome_other_file.java:902"},
		},
		{
			message:  "<27>1 2015-03-02T22:53:45-08:00 localhost.localdomain puppet-agent 5334 - mirrorurls.extend(list(self.metalink_data.urls()))",
			expected: RFC5424Message{Priority: 27, Version: 1, Timestamp: "2015-03-02T22:53:45-08:00", Host: "localhost.localdomain", App: "puppet-agent", Pid: 5334, MsgId: "-", Message: "mirrorurls.extend(list(self.metalink_data.urls()))"},
		},
		{
			message:  "<29>1 2015-03-03T06:49:08-08:00 localhost.localdomain puppet-agent 51564 - (/Stage[main]/Users_prd/Ssh_authorized_key[1063-username]) Dependency Group[group] has failures: true",
			expected: RFC5424Message{Priority: 29, Version: 1, Timestamp: "2015-03-03T06:49:08-08:00", Host: "localhost.localdomain", App: "puppet-agent", Pid: 51564, MsgId: "-", Message: "(/Stage[main]/Users_prd/Ssh_authorized_key[1063-username]) Dependency Group[group] has failures: true"},
		},
		{
			message:  "<142>1 2015-03-02T22:23:07-08:00 localhost.localdomain Keepalived_vrrp 21125 - VRRP_Instance(VI_1) ignoring received advertisment...",
			expected: RFC5424Message{Priority: 142, Version: 1, Timestamp: "2015-03-02T22:23:07-08:00", Host: "localhost.localdomain", App: "Keepalived_vrrp", Pid: 21125, MsgId: "-", Message: "VRRP_Instance(VI_1) ignoring received advertisment..."},
		},
		{
			message:  `<142>1 2015-03-02T22:23:07-08:00 localhost.localdomain Keepalived_vrrp 21125 - HEAD /wp-login.php HTTP/1.1" 200 167 "http://www.philipotoole.com/" "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.97 Safari/537.11`,
			expected: RFC5424Message{Priority: 142, Version: 1, Timestamp: "2015-03-02T22:23:07-08:00", Host: "localhost.localdomain", App: "Keepalived_vrrp", Pid: 21125, MsgId: "-", Message: `HEAD /wp-login.php HTTP/1.1" 200 167 "http://www.philipotoole.com/" "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.97 Safari/537.11`},
		},
		{
			message:  `<134>0 2015-05-05T21:20:00.493320+00:00 fisher apache-access - - 173.247.206.174 - - [05/May/2015:21:19:52 +0000] "GET /2013/11/ HTTP/1.1" 200 22056 "http://www.philipotoole.com/" "Wget/1.15 (linux-gnu)"`,
			expected: RFC5424Message{Priority: 134, Version: 0, Timestamp: "2015-05-05T21:20:00.493320+00:00", Host: "fisher", App: "apache-access", Pid: 0, MsgId: "-", Message: `173.247.206.174 - - [05/May/2015:21:19:52 +0000] "GET /2013/11/ HTTP/1.1" 200 22056 "http://www.philipotoole.com/" "Wget/1.15 (linux-gnu)"`},
		},
	}

	for i, tt := range tests {
		m := p.Parse(tt.message)
		if m == nil {
			t.Fatalf("test %d: failed to parse: %s", i, tt.message)
		}
		if tt.expected != *m {
			t.Errorf("test %d: incorrect parsing of: %v", i, tt.message)
			t.Logf("Priority: %d (match: %v)", m.Priority, m.Priority == tt.expected.Priority)
			t.Logf("Version: %d (match: %v)", m.Version, m.Version == tt.expected.Version)
			t.Logf("Timestamp: %s (match: %v)", m.Timestamp, m.Timestamp == tt.expected.Timestamp)
			t.Logf("Host: %s (match: %v)", m.Host, m.Host == tt.expected.Host)
			t.Logf("App: %s (match: %v)", m.App, m.App == tt.expected.App)
			t.Logf("PID: %d (match: %v)", m.Pid, m.Pid == tt.expected.Pid)
			t.Logf("MsgId: %s (match: %v)", m.MsgId, m.MsgId == tt.expected.MsgId)
			t.Logf("Message: %s (match: %v)", m.Message, m.Message == tt.expected.Message)
		}
	}
}

func Test_FailedRFC5424Parsing(t *testing.T) {
	p := NewRFC5424Parser()

	tests := []string{
		"<134> 2013-09-04T10:25:52.618085 ubuntu sshd 1999 - password accepted",
		"<33> 7 2013-09-04T10:25:52.618085 test.com cron 304 - password accepted",
		"<33> 7 2013-09-04T10:25:52.618085 test.com cron 304 $ password accepted",
		"<33> 7 2013-09-04T10:25:52.618085 test.com cron 304 - - password accepted",
		"<33>7 2013-09-04T10:25:52.618085 test.com cron not_a_pid - password accepted",
		"5:52.618085 test.com cron 65535 - password accepted",
	}

	for _, message := range tests {
		if p.Parse(message) != nil {
			t.Errorf("parsed '%s', not expected", message)
		}
	}
}
<commit_msg>Add simple parsing benchmark test<commit_after>package input

import "testing"

/*
 * RFC5424 parser tests
 */

func Test_SuccessfulRFC5424Parsing(t *testing.T) {
	p := NewRFC5424Parser()

	tests := []struct {
		message  string
		expected RFC5424Message
	}{
		{
			message:  "<134>1 2003-08-24T05:14:15.000003-07:00 ubuntu sshd 1999 - password accepted",
			expected: RFC5424Message{Priority: 134, Version: 1, Timestamp: "2003-08-24T05:14:15.000003-07:00", Host: "ubuntu", App: "sshd", Pid: 1999, MsgId: "-", Message: "password accepted"},
		},
		{
			message:  "<33>5 1985-04-12T23:20:50.52Z test.com cron 304 - password accepted",
			expected: RFC5424Message{Priority: 33, Version: 5, Timestamp: "1985-04-12T23:20:50.52Z", Host: "test.com", App: "cron", Pid: 304, MsgId: "-", Message: "password accepted"},
		},
		{
			message:  "<1>0 1985-04-12T19:20:50.52-04:00 test.com cron 65535 - password accepted",
			expected: RFC5424Message{Priority: 1, Version: 0, Timestamp: "1985-04-12T19:20:50.52-04:00", Host: "test.com", App: "cron", Pid: 65535, MsgId: "-", Message: "password accepted"},
		},
		{
			message:  "<1>0 2003-10-11T22:14:15.003Z test.com cron 65535 msgid1234 password accepted",
			expected: RFC5424Message{Priority: 1, Version: 0, Timestamp: "2003-10-11T22:14:15.003Z", Host: "test.com", App: "cron", Pid: 65535, MsgId: "msgid1234", Message: "password accepted"},
		},
		{
			message:  "<1>0 2003-08-24T05:14:15.000003-07:00 test.com cron 65535 - JVM NPE\nsome_file.java:48\n\tsome_other_file.java:902",
			expected: RFC5424Message{Priority: 1, Version: 0, Timestamp: "2003-08-24T05:14:15.000003-07:00", Host: "test.com", App: "cron", Pid: 65535, MsgId: "-", Message: "JVM NPE\nsome_file.java:48\n\tsome_other_file.java:902"},
		},
		{
			message:  "<27>1 2015-03-02T22:53:45-08:00 localhost.localdomain puppet-agent 5334 - mirrorurls.extend(list(self.metalink_data.urls()))",
			expected: RFC5424Message{Priority: 27, Version: 1, Timestamp: "2015-03-02T22:53:45-08:00", Host: "localhost.localdomain", App: "puppet-agent", Pid: 5334, MsgId: "-", Message: "mirrorurls.extend(list(self.metalink_data.urls()))"},
		},
		{
			message:  "<29>1 2015-03-03T06:49:08-08:00 localhost.localdomain puppet-agent 51564 - (/Stage[main]/Users_prd/Ssh_authorized_key[1063-username]) Dependency Group[group] has failures: true",
			expected: RFC5424Message{Priority: 29, Version: 1, Timestamp: "2015-03-03T06:49:08-08:00", Host: "localhost.localdomain", App: "puppet-agent", Pid: 51564, MsgId: "-", Message: "(/Stage[main]/Users_prd/Ssh_authorized_key[1063-username]) Dependency Group[group] has failures: true"},
		},
		{
			message:  "<142>1 2015-03-02T22:23:07-08:00 localhost.localdomain Keepalived_vrrp 21125 - VRRP_Instance(VI_1) ignoring received advertisment...",
			expected: RFC5424Message{Priority: 142, Version: 1, Timestamp: "2015-03-02T22:23:07-08:00", Host: "localhost.localdomain", App: "Keepalived_vrrp", Pid: 21125, MsgId: "-", Message: "VRRP_Instance(VI_1) ignoring received advertisment..."},
		},
		{
			message:  `<142>1 2015-03-02T22:23:07-08:00 localhost.localdomain Keepalived_vrrp 21125 - HEAD /wp-login.php HTTP/1.1" 200 167 "http://www.philipotoole.com/" "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.97 Safari/537.11`,
			expected: RFC5424Message{Priority: 142, Version: 1, Timestamp: "2015-03-02T22:23:07-08:00", Host: "localhost.localdomain", App: "Keepalived_vrrp", Pid: 21125, MsgId: "-", Message: `HEAD /wp-login.php HTTP/1.1" 200 167 "http://www.philipotoole.com/" "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.97 Safari/537.11`},
		},
		{
			message:  `<134>0 2015-05-05T21:20:00.493320+00:00 fisher apache-access - - 173.247.206.174 - - [05/May/2015:21:19:52 +0000] "GET /2013/11/ HTTP/1.1" 200 22056 "http://www.philipotoole.com/" "Wget/1.15 (linux-gnu)"`,
			expected: RFC5424Message{Priority: 134, Version: 0, Timestamp: "2015-05-05T21:20:00.493320+00:00", Host: "fisher", App: "apache-access", Pid: 0, MsgId: "-", Message: `173.247.206.174 - - [05/May/2015:21:19:52 +0000] "GET /2013/11/ HTTP/1.1" 200 22056 "http://www.philipotoole.com/" "Wget/1.15 (linux-gnu)"`},
		},
	}

	for i, tt := range tests {
		m := p.Parse(tt.message)
		if m == nil {
			t.Fatalf("test %d: failed to parse: %s", i, tt.message)
		}
		if tt.expected != *m {
			t.Errorf("test %d: incorrect parsing of: %v", i, tt.message)
			t.Logf("Priority: %d (match: %v)", m.Priority, m.Priority == tt.expected.Priority)
			t.Logf("Version: %d (match: %v)", m.Version, m.Version == tt.expected.Version)
			t.Logf("Timestamp: %s (match: %v)", m.Timestamp, m.Timestamp == tt.expected.Timestamp)
			t.Logf("Host: %s (match: %v)", m.Host, m.Host == tt.expected.Host)
			t.Logf("App: %s (match: %v)", m.App, m.App == tt.expected.App)
			t.Logf("PID: %d (match: %v)", m.Pid, m.Pid == tt.expected.Pid)
			t.Logf("MsgId: %s (match: %v)", m.MsgId, m.MsgId == tt.expected.MsgId)
			t.Logf("Message: %s (match: %v)", m.Message, m.Message == tt.expected.Message)
		}
	}
}

func Test_FailedRFC5424Parsing(t *testing.T) {
	p := NewRFC5424Parser()

	tests := []string{
		"<134> 2013-09-04T10:25:52.618085 ubuntu sshd 1999 - password accepted",
		"<33> 7 2013-09-04T10:25:52.618085 test.com cron 304 - password accepted",
		"<33> 7 2013-09-04T10:25:52.618085 test.com cron 304 $ password accepted",
		"<33> 7 2013-09-04T10:25:52.618085 test.com cron 304 - - password accepted",
		"<33>7 2013-09-04T10:25:52.618085 test.com cron not_a_pid - password accepted",
		"5:52.618085 test.com cron 65535 - password accepted",
	}

	for _, message := range tests {
		if p.Parse(message) != nil {
			t.Errorf("parsed '%s', not expected", message)
		}
	}
}

func Benchmark_Parsing(b *testing.B) {
	p := NewRFC5424Parser()
	for n := 0; n < b.N; n++ {
		m := p.Parse(`<134>0 2015-05-05T21:20:00.493320+00:00 fisher apache-access - - 173.247.206.174 - - [05/May/2015:21:19:52 +0000] "GET /2013/11/ HTTP/1.  1" 200 22056 "http://www.philipotoole.com/" "Wget/1.15 (linux-gnu)"`)
		if m == nil {
			panic("message failed to parse during benchmarking")
		}

	}
}
<|endoftext|>


<commit_before>package calcium

import (
	"fmt"
	"io"
	"io/ioutil"
	"path/filepath"
	"strings"

	engineapi "github.com/docker/engine-api/client"
	enginetypes "github.com/docker/engine-api/types"
	enginecontainer "github.com/docker/engine-api/types/container"
	enginenetwork "github.com/docker/engine-api/types/network"
	"gitlab.ricebook.net/platform/core/types"
	"gitlab.ricebook.net/platform/core/utils"
	"golang.org/x/net/context"
)

// As the name says,
// blocks until the stream is empty, until we meet EOF
func ensureReaderClosed(stream io.ReadCloser) {
	if stream == nil {
		return
	}
	io.Copy(ioutil.Discard, stream)
	stream.Close()
}

// Copies config from container
// And make a new name for container
func makeContainerConfig(info enginetypes.ContainerJSON, image string) (
	*enginecontainer.Config,
	*enginecontainer.HostConfig,
	*enginenetwork.NetworkingConfig,
	string,
	error) {

	// we use `_` to join container name
	// since we don't support `_` in entrypoint, and no `_` is in suffix,
	// the last part will be suffix and second last part will be entrypoint,
	// the rest will be the appname
	parts := strings.Split(trimLeftSlash(info.Name), "_")
	length := len(parts)
	if length < 3 {
		return nil, nil, nil, "", fmt.Errorf("Bad container name format: %q", info.Name)
	}

	entrypoint := parts[length-2]
	appname := strings.Join(parts[:length-2], "_")

	suffix := utils.RandomString(6)
	containerName := strings.Join([]string{appname, entrypoint, suffix}, "_")

	config := info.Config
	config.Image = image

	hostConfig := info.HostConfig
	networkConfig := &enginenetwork.NetworkingConfig{
		EndpointsConfig: info.NetworkSettings.Networks,
	}
	return config, hostConfig, networkConfig, containerName, nil
}

// see https://github.com/docker/docker/issues/6705
// docker's stupid problem
func trimLeftSlash(name string) string {
	return strings.TrimPrefix(name, "/")
}

// make mount paths
// app.yaml支持三种写法.
// app.yaml里可以支持mount_paths的写法, 例如
// mount_paths:
//     - "/var/www/html"
//     - "/data/eggsy"
// 这样的路径会被直接挂载到permdir下面去, 例如上面的路径就是
// /mnt/mfs/permdirs/eggsy/data/eggsy
// /mnt/mfs/permdirs/eggsy/var/www/html
// 而且这些路径是可以读写的.
//
// 或者使用volumes, 参数格式跟docker一样, 例如
// volumes:
//     - "/data/test:/test:ro"
//     - "/data/testx:/testx"
// 说明把宿主机的/data/test映射到容器里的/test, 只读, 同时
// 把宿主机的/data/tests映射到容器里的/testx, 读写.
//
// 或者使用binds, 例如
// binds:
//     "/host/path":
//         bind: "/container/path"
//         ro: true
// 说明把宿主机的/host/path映射到容器里的/container/path, 并且只读
func makeMountPaths(specs types.Specs, config types.Config) ([]string, map[string]struct{}) {
	binds := []string{}
	volumes := make(map[string]struct{})
	permDirHost := filepath.Join(config.PermDir, specs.Appname)

	// mount_paths
	for _, path := range specs.MountPaths {
		hostPath := filepath.Join(permDirHost, path)
		binds = append(binds, fmt.Sprintf("%s:%s:rw", hostPath, path))
		volumes[path] = struct{}{}
	}

	// volumes
	for _, path := range specs.Volumes {
		parts := strings.Split(path, ":")
		if len(parts) == 2 {
			binds = append(binds, fmt.Sprintf("%s:%s:ro", parts[0], parts[1]))
			volumes[parts[1]] = struct{}{}
		} else if len(parts) == 3 {
			binds = append(binds, fmt.Sprintf("%s:%s:%s", parts[0], parts[1], parts[2]))
			volumes[parts[1]] = struct{}{}
		}
	}

	// binds
	var mode string
	for hostPath, bind := range specs.Binds {
		if bind.ReadOnly {
			mode = "ro"
		} else {
			mode = "rw"
		}
		binds = append(binds, fmt.Sprintf("%s:%s:%s", hostPath, bind.InContainerPath, mode))
		volumes[bind.InContainerPath] = struct{}{}
	}

	// /proc/sys
	volumes["/writable-proc/sys"] = struct{}{}
	binds = append(binds, "/proc/sys:/writable-proc/sys:ro")
	return binds, volumes
}

// 跑存在labels里的exec
// 为什么要存labels呢, 因为下线容器的时候根本不知道entrypoint是啥
func runExec(client *engineapi.Client, container enginetypes.ContainerJSON, label string) error {
	cmd, ok := container.Config.Labels[label]
	if !ok {
		return fmt.Errorf("No %q found in container %q", label, container.ID)
	}

	cmds := utils.MakeCommandLineArgs(cmd)
	execConfig := enginetypes.ExecConfig{User: container.Config.User, Cmd: cmds}
	resp, err := client.ContainerExecCreate(context.Background(), container.ID, execConfig)
	if err != nil {
		return err
	}

	return client.ContainerExecStart(context.Background(), resp.ID, enginetypes.ExecStartCheck{})
}
<commit_msg>如果没有就不要继续了...<commit_after>package calcium

import (
	"fmt"
	"io"
	"io/ioutil"
	"path/filepath"
	"strings"

	engineapi "github.com/docker/engine-api/client"
	enginetypes "github.com/docker/engine-api/types"
	enginecontainer "github.com/docker/engine-api/types/container"
	enginenetwork "github.com/docker/engine-api/types/network"
	"gitlab.ricebook.net/platform/core/types"
	"gitlab.ricebook.net/platform/core/utils"
	"golang.org/x/net/context"
)

// As the name says,
// blocks until the stream is empty, until we meet EOF
func ensureReaderClosed(stream io.ReadCloser) {
	if stream == nil {
		return
	}
	io.Copy(ioutil.Discard, stream)
	stream.Close()
}

// Copies config from container
// And make a new name for container
func makeContainerConfig(info enginetypes.ContainerJSON, image string) (
	*enginecontainer.Config,
	*enginecontainer.HostConfig,
	*enginenetwork.NetworkingConfig,
	string,
	error) {

	// we use `_` to join container name
	// since we don't support `_` in entrypoint, and no `_` is in suffix,
	// the last part will be suffix and second last part will be entrypoint,
	// the rest will be the appname
	parts := strings.Split(trimLeftSlash(info.Name), "_")
	length := len(parts)
	if length < 3 {
		return nil, nil, nil, "", fmt.Errorf("Bad container name format: %q", info.Name)
	}

	entrypoint := parts[length-2]
	appname := strings.Join(parts[:length-2], "_")

	suffix := utils.RandomString(6)
	containerName := strings.Join([]string{appname, entrypoint, suffix}, "_")

	config := info.Config
	config.Image = image

	hostConfig := info.HostConfig
	networkConfig := &enginenetwork.NetworkingConfig{
		EndpointsConfig: info.NetworkSettings.Networks,
	}
	return config, hostConfig, networkConfig, containerName, nil
}

// see https://github.com/docker/docker/issues/6705
// docker's stupid problem
func trimLeftSlash(name string) string {
	return strings.TrimPrefix(name, "/")
}

// make mount paths
// app.yaml支持三种写法.
// app.yaml里可以支持mount_paths的写法, 例如
// mount_paths:
//     - "/var/www/html"
//     - "/data/eggsy"
// 这样的路径会被直接挂载到permdir下面去, 例如上面的路径就是
// /mnt/mfs/permdirs/eggsy/data/eggsy
// /mnt/mfs/permdirs/eggsy/var/www/html
// 而且这些路径是可以读写的.
//
// 或者使用volumes, 参数格式跟docker一样, 例如
// volumes:
//     - "/data/test:/test:ro"
//     - "/data/testx:/testx"
// 说明把宿主机的/data/test映射到容器里的/test, 只读, 同时
// 把宿主机的/data/tests映射到容器里的/testx, 读写.
//
// 或者使用binds, 例如
// binds:
//     "/host/path":
//         bind: "/container/path"
//         ro: true
// 说明把宿主机的/host/path映射到容器里的/container/path, 并且只读
func makeMountPaths(specs types.Specs, config types.Config) ([]string, map[string]struct{}) {
	binds := []string{}
	volumes := make(map[string]struct{})
	permDirHost := filepath.Join(config.PermDir, specs.Appname)

	// mount_paths
	for _, path := range specs.MountPaths {
		hostPath := filepath.Join(permDirHost, path)
		binds = append(binds, fmt.Sprintf("%s:%s:rw", hostPath, path))
		volumes[path] = struct{}{}
	}

	// volumes
	for _, path := range specs.Volumes {
		parts := strings.Split(path, ":")
		if len(parts) == 2 {
			binds = append(binds, fmt.Sprintf("%s:%s:ro", parts[0], parts[1]))
			volumes[parts[1]] = struct{}{}
		} else if len(parts) == 3 {
			binds = append(binds, fmt.Sprintf("%s:%s:%s", parts[0], parts[1], parts[2]))
			volumes[parts[1]] = struct{}{}
		}
	}

	// binds
	var mode string
	for hostPath, bind := range specs.Binds {
		if bind.ReadOnly {
			mode = "ro"
		} else {
			mode = "rw"
		}
		binds = append(binds, fmt.Sprintf("%s:%s:%s", hostPath, bind.InContainerPath, mode))
		volumes[bind.InContainerPath] = struct{}{}
	}

	// /proc/sys
	volumes["/writable-proc/sys"] = struct{}{}
	binds = append(binds, "/proc/sys:/writable-proc/sys:ro")
	return binds, volumes
}

// 跑存在labels里的exec
// 为什么要存labels呢, 因为下线容器的时候根本不知道entrypoint是啥
func runExec(client *engineapi.Client, container enginetypes.ContainerJSON, label string) error {
	cmd, ok := container.Config.Labels[label]
	if !ok || cmd == "" {
		return fmt.Errorf("No %q found in container %q", label, container.ID)
	}

	cmds := utils.MakeCommandLineArgs(cmd)
	execConfig := enginetypes.ExecConfig{User: container.Config.User, Cmd: cmds}
	resp, err := client.ContainerExecCreate(context.Background(), container.ID, execConfig)
	if err != nil {
		return err
	}

	return client.ContainerExecStart(context.Background(), resp.ID, enginetypes.ExecStartCheck{})
}
<|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.

package ui

var currentInput = &Input{}

type Touch interface {
	ID() int
	Position() (x, y int)
}

func CurrentInput() *Input {
	return currentInput
}

func (i *Input) CursorPosition() (x, y int) {
	i.m.RLock()
	defer i.m.RUnlock()
	return adjustCursorPosition(i.cursorX, i.cursorY)
}

var emptyIDs = []int{}

func (i *Input) GamepadIDs() []int {
	i.m.RLock()
	defer i.m.RUnlock()
	if len(i.gamepads) == 0 {
		return emptyIDs
	}
	r := []int{}
	for id, g := range i.gamepads {
		if g.valid {
			r = append(r, id)
		}
	}
	return r
}

func (i *Input) GamepadAxisNum(id int) int {
	i.m.RLock()
	defer i.m.RUnlock()
	if len(i.gamepads) <= id {
		return 0
	}
	return i.gamepads[id].axisNum
}

func (i *Input) GamepadAxis(id int, axis int) float64 {
	i.m.RLock()
	defer i.m.RUnlock()
	if len(i.gamepads) <= id {
		return 0
	}
	return i.gamepads[id].axes[axis]
}

func (i *Input) GamepadButtonNum(id int) int {
	i.m.RLock()
	defer i.m.RUnlock()
	if len(i.gamepads) <= id {
		return 0
	}
	return i.gamepads[id].buttonNum
}

func (i *Input) IsGamepadButtonPressed(id int, button GamepadButton) bool {
	i.m.RLock()
	defer i.m.RUnlock()
	if len(i.gamepads) <= id {
		return false
	}
	return i.gamepads[id].buttonPressed[button]
}

func (in *Input) Touches() []Touch {
	in.m.RLock()
	defer in.m.RUnlock()
	t := make([]Touch, len(in.touches))
	for i := 0; i < len(t); i++ {
		t[i] = &in.touches[i]
	}
	return t
}

type gamePad struct {
	valid         bool
	axisNum       int
	axes          [16]float64
	buttonNum     int
	buttonPressed [256]bool
}

type touch struct {
	id int
	x  int
	y  int
}

func (t *touch) ID() int {
	return t.id
}

func (t *touch) Position() (x, y int) {
	return t.x, t.y
}
<commit_msg>ui: Avoid creating empty slice at Touches<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.

package ui

var currentInput = &Input{}

type Touch interface {
	ID() int
	Position() (x, y int)
}

func CurrentInput() *Input {
	return currentInput
}

func (i *Input) CursorPosition() (x, y int) {
	i.m.RLock()
	defer i.m.RUnlock()
	return adjustCursorPosition(i.cursorX, i.cursorY)
}

var emptyIDs = []int{}

func (i *Input) GamepadIDs() []int {
	i.m.RLock()
	defer i.m.RUnlock()
	if len(i.gamepads) == 0 {
		// Avoid creating a slice if possible.
		// This is a performance optimization for browsers.
		return emptyIDs
	}
	r := []int{}
	for id, g := range i.gamepads {
		if g.valid {
			r = append(r, id)
		}
	}
	return r
}

func (i *Input) GamepadAxisNum(id int) int {
	i.m.RLock()
	defer i.m.RUnlock()
	if len(i.gamepads) <= id {
		return 0
	}
	return i.gamepads[id].axisNum
}

func (i *Input) GamepadAxis(id int, axis int) float64 {
	i.m.RLock()
	defer i.m.RUnlock()
	if len(i.gamepads) <= id {
		return 0
	}
	return i.gamepads[id].axes[axis]
}

func (i *Input) GamepadButtonNum(id int) int {
	i.m.RLock()
	defer i.m.RUnlock()
	if len(i.gamepads) <= id {
		return 0
	}
	return i.gamepads[id].buttonNum
}

func (i *Input) IsGamepadButtonPressed(id int, button GamepadButton) bool {
	i.m.RLock()
	defer i.m.RUnlock()
	if len(i.gamepads) <= id {
		return false
	}
	return i.gamepads[id].buttonPressed[button]
}

var emptyTouches = []Touch{}

func (in *Input) Touches() []Touch {
	in.m.RLock()
	defer in.m.RUnlock()

	if len(in.touches) == 0 {
		// Avoid creating a slice if possible.
		// This is a performance optimization for browsers.
		return emptyTouches
	}

	t := make([]Touch, len(in.touches))
	for i := 0; i < len(t); i++ {
		t[i] = &in.touches[i]
	}
	return t
}

type gamePad struct {
	valid         bool
	axisNum       int
	axes          [16]float64
	buttonNum     int
	buttonPressed [256]bool
}

type touch struct {
	id int
	x  int
	y  int
}

func (t *touch) ID() int {
	return t.id
}

func (t *touch) Position() (x, y int) {
	return t.x, t.y
}
<|endoftext|>


<commit_before>package main

import (
	"time"

	"github.com/banzaicloud/bank-vaults/pkg/vault"
	"github.com/hashicorp/vault/api"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
)

const cfgUnsealPeriod = "unseal-period"

type unsealCfg struct {
	unsealPeriod time.Duration
}

var unsealConfig unsealCfg

var unsealCmd = &cobra.Command{
	Use:   "unseal",
	Short: "A brief description of your command",
	Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:

Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
	Run: func(cmd *cobra.Command, args []string) {
		appConfig.BindPFlag(cfgUnsealPeriod, cmd.PersistentFlags().Lookup(cfgUnsealPeriod))
		unsealConfig.unsealPeriod = appConfig.GetDuration(cfgUnsealPeriod)

		store, err := kvStoreForConfig(appConfig)

		if err != nil {
			logrus.Fatalf("error creating kv store: %s", err.Error())
		}

		cl, err := api.NewClient(nil)

		if err != nil {
			logrus.Fatalf("error connecting to vault: %s", err.Error())
		}

		if err != nil {
			logrus.Fatalf("error building vault config: %s", err.Error())
		}

		vaultConfig, err := vaultConfigForConfig(appConfig)

		v, err := vault.New(store, cl, vaultConfig)

		if err != nil {
			logrus.Fatalf("error creating vault helper: %s", err.Error())
		}

		for {
			func() {
				logrus.Infof("checking if vault is sealed...")
				sealed, err := v.Sealed()
				if err != nil {
					logrus.Errorf("error checking if vault is sealed: %s", err.Error())
					return
				}

				logrus.Infof("vault sealed: %t", sealed)

				// If vault is not sealed, we stop here and wait another unsealPeriod
				if !sealed {
					return
				}

				if err = v.Unseal(); err != nil {
					logrus.Errorf("error unsealing vault: %s", err.Error())
					return
				}

				logrus.Infof("successfully unsealed vault")
			}()
			// wait unsealPeriod before trying again
			time.Sleep(unsealConfig.unsealPeriod)
		}
	},
}

func init() {
	unsealCmd.PersistentFlags().Duration(cfgUnsealPeriod, time.Second*30, "How often to attempt to unseal the vault instance")

	rootCmd.AddCommand(unsealCmd)
}
<commit_msg>Return condition to a proper place<commit_after>package main

import (
	"time"

	"github.com/banzaicloud/bank-vaults/pkg/vault"
	"github.com/hashicorp/vault/api"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
)

const cfgUnsealPeriod = "unseal-period"

type unsealCfg struct {
	unsealPeriod time.Duration
}

var unsealConfig unsealCfg

var unsealCmd = &cobra.Command{
	Use:   "unseal",
	Short: "A brief description of your command",
	Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:

Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
	Run: func(cmd *cobra.Command, args []string) {
		appConfig.BindPFlag(cfgUnsealPeriod, cmd.PersistentFlags().Lookup(cfgUnsealPeriod))
		unsealConfig.unsealPeriod = appConfig.GetDuration(cfgUnsealPeriod)

		store, err := kvStoreForConfig(appConfig)

		if err != nil {
			logrus.Fatalf("error creating kv store: %s", err.Error())
		}

		cl, err := api.NewClient(nil)

		if err != nil {
			logrus.Fatalf("error connecting to vault: %s", err.Error())
		}

		vaultConfig, err := vaultConfigForConfig(appConfig)

		if err != nil {
			logrus.Fatalf("error building vault config: %s", err.Error())
		}

		v, err := vault.New(store, cl, vaultConfig)

		if err != nil {
			logrus.Fatalf("error creating vault helper: %s", err.Error())
		}

		for {
			func() {
				logrus.Infof("checking if vault is sealed...")
				sealed, err := v.Sealed()
				if err != nil {
					logrus.Errorf("error checking if vault is sealed: %s", err.Error())
					return
				}

				logrus.Infof("vault sealed: %t", sealed)

				// If vault is not sealed, we stop here and wait another unsealPeriod
				if !sealed {
					return
				}

				if err = v.Unseal(); err != nil {
					logrus.Errorf("error unsealing vault: %s", err.Error())
					return
				}

				logrus.Infof("successfully unsealed vault")
			}()
			// wait unsealPeriod before trying again
			time.Sleep(unsealConfig.unsealPeriod)
		}
	},
}

func init() {
	unsealCmd.PersistentFlags().Duration(cfgUnsealPeriod, time.Second*30, "How often to attempt to unseal the vault instance")

	rootCmd.AddCommand(unsealCmd)
}
<|endoftext|>


<commit_before>/*
Copyright 2011 Google Inc.

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

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

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

package main

import (
	"bytes"
	"io"
	"os"
	"runtime"
	"testing"
	"time"

	"camlistore.org/pkg/cmdmain"
)

// env is the environment that a camput test runs within.
type env struct {
	// stdin is the standard input, or /dev/null if nil
	stdin io.Reader

	// TODO(bradfitz): vfs files.
}

func (e *env) Run(args ...string) (out, err []byte, exitCode int) {
	outbuf := new(bytes.Buffer)
	errbuf := new(bytes.Buffer)
	os.Args = append(os.Args[:1], args...)
	cmdmain.Stdout, cmdmain.Stderr = outbuf, errbuf
	exitc := make(chan int, 1)
	cmdmain.Exit = func(code int) {
		exitc <- code
		runtime.Goexit()
	}
	go func() {
		cmdmain.Main()
		cmdmain.Exit(0)
	}()
	select {
	case exitCode = <-exitc:
	case <-time.After(15 * time.Second):
		panic("timeout running command")
	}
	out = outbuf.Bytes()
	err = errbuf.Bytes()
	return
}

// TestUsageOnNoargs tests that we output a usage message when given no args, and return
// with a non-zero exit status.
func TestUsageOnNoargs(t *testing.T) {
	var e env
	out, err, code := e.Run()
	if code != 1 {
		t.Errorf("exit code = %d; want 1", code)
	}
	if len(out) != 0 {
		t.Errorf("wanted nothing on stdout; got:\n%s", out)
	}
	if !bytes.Contains(err, []byte("Usage: camput")) {
		t.Errorf("stderr doesn't contain usage. Got:\n%s", err)
	}
}

func TestUploadingChangingDirectory(t *testing.T) {
	// TODO(bradfitz):
	//    $ mkdir /tmp/somedir
	//    $ cp dev-camput /tmp/somedir
	//    $ ./dev-camput  -file /tmp/somedir/ 2>&1 | tee /tmp/somedir/log
	// ... verify it doesn't hang.
	t.Logf("TODO")
}
<commit_msg>camput: configurable timeout on env<commit_after>/*
Copyright 2011 Google Inc.

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

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

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

package main

import (
	"bytes"
	"io"
	"os"
	"runtime"
	"testing"
	"time"

	"camlistore.org/pkg/cmdmain"
)

// env is the environment that a camput test runs within.
type env struct {
	// stdin is the standard input, or /dev/null if nil
	stdin io.Reader

	// Timeout optionally specifies the timeout on the command.
	Timeout time.Duration

	// TODO(bradfitz): vfs files.
}

func (e *env) timeout() time.Duration {
	if e.Timeout != 0 {
		return e.Timeout
	}
	return 15 * time.Second

}
func (e *env) Run(args ...string) (out, err []byte, exitCode int) {
	outbuf := new(bytes.Buffer)
	errbuf := new(bytes.Buffer)
	os.Args = append(os.Args[:1], args...)
	cmdmain.Stdout, cmdmain.Stderr = outbuf, errbuf
	exitc := make(chan int, 1)
	cmdmain.Exit = func(code int) {
		exitc <- code
		runtime.Goexit()
	}
	go func() {
		cmdmain.Main()
		cmdmain.Exit(0)
	}()
	select {
	case exitCode = <-exitc:
	case <-time.After(e.timeout()):
		panic("timeout running command")
	}
	out = outbuf.Bytes()
	err = errbuf.Bytes()
	return
}

// TestUsageOnNoargs tests that we output a usage message when given no args, and return
// with a non-zero exit status.
func TestUsageOnNoargs(t *testing.T) {
	var e env
	out, err, code := e.Run()
	if code != 1 {
		t.Errorf("exit code = %d; want 1", code)
	}
	if len(out) != 0 {
		t.Errorf("wanted nothing on stdout; got:\n%s", out)
	}
	if !bytes.Contains(err, []byte("Usage: camput")) {
		t.Errorf("stderr doesn't contain usage. Got:\n%s", err)
	}
}

func TestUploadingChangingDirectory(t *testing.T) {
	// TODO(bradfitz):
	//    $ mkdir /tmp/somedir
	//    $ cp dev-camput /tmp/somedir
	//    $ ./dev-camput  -file /tmp/somedir/ 2>&1 | tee /tmp/somedir/log
	// ... verify it doesn't hang.
	t.Logf("TODO")
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"os/signal"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"
	"time"

	flags "github.com/jessevdk/go-flags"
	"github.com/labstack/echo"
	"github.com/lestrrat/go-server-starter/listener"
	"github.com/monochromegane/gannoy"
)

type Options struct {
	DataDir           string `short:"d" long:"data-dir" default:"." description:"Specify the directory where the meta files are located."`
	WithServerStarter bool   `short:"s" long:"server-starter" default:"false" description:"Use server-starter listener for server address."`
	ShutDownTimeout   int    `short:"t" long:"timeout" default:"10" description:"Specify the number of seconds for shutdown timeout."`
}

var opts Options

type Feature struct {
	W []float64 `json:"features"`
}

func main() {
	_, err := flags.Parse(&opts)
	if err != nil {
		os.Exit(1)
	}

	files, err := ioutil.ReadDir(opts.DataDir)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	databases := map[string]gannoy.GannoyIndex{}
	for _, file := range files {
		if file.IsDir() || filepath.Ext(file.Name()) != ".meta" {
			continue
		}
		key := strings.TrimSuffix(file.Name(), ".meta")
		gannoy, err := gannoy.NewGannoyIndex(filepath.Join(opts.DataDir, file.Name()), gannoy.Angular{}, gannoy.RandRandom{})
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
		databases[key] = gannoy
	}

	e := echo.New()
	e.GET("/search", func(c echo.Context) error {
		database := c.QueryParam("database")
		if _, ok := databases[database]; !ok {
			return c.NoContent(http.StatusNotFound)
		}
		key, err := strconv.Atoi(c.QueryParam("key"))
		if err != nil {
			key = -1
		}
		limit, err := strconv.Atoi(c.QueryParam("limit"))
		if err != nil {
			limit = 10
		}

		gannoy := databases[database]
		r, err := gannoy.GetNnsByKey(key, limit, -1)
		if err != nil || len(r) == 0 {
			return c.NoContent(http.StatusNotFound)
		}

		return c.JSON(http.StatusOK, r)
	})

	e.PUT("/databases/:database/features/:key", func(c echo.Context) error {
		database := c.Param("database")
		if _, ok := databases[database]; !ok {
			return c.NoContent(http.StatusUnprocessableEntity)
		}
		key, err := strconv.Atoi(c.Param("key"))
		if err != nil {
			return c.NoContent(http.StatusUnprocessableEntity)
		}
		feature := new(Feature)
		if err := c.Bind(feature); err != nil {
			return err
		}

		gannoy := databases[database]
		err = gannoy.AddItem(key, feature.W)
		if err != nil {
			return c.NoContent(http.StatusUnprocessableEntity)
		}
		return c.NoContent(http.StatusOK)
	})

	e.DELETE("/databases/:database/features/:key", func(c echo.Context) error {
		database := c.Param("database")
		if _, ok := databases[database]; !ok {
			return c.NoContent(http.StatusUnprocessableEntity)
		}
		key, err := strconv.Atoi(c.Param("key"))
		if err != nil {
			return c.NoContent(http.StatusUnprocessableEntity)
		}
		gannoy := databases[database]
		err = gannoy.RemoveItem(key)
		if err != nil {
			return c.NoContent(http.StatusUnprocessableEntity)
		}

		return c.NoContent(http.StatusOK)
	})

	address := ":1323"
	sig := os.Interrupt
	if opts.WithServerStarter {
		address = ""
		sig = syscall.SIGTERM
		listeners, err := listener.ListenAll()
		if err != nil && err != listener.ErrNoListeningTarget {
			fmt.Println(err)
			os.Exit(1)
		}
		e.Listener = listeners[0]
	}

	go func() {
		if err := e.Start(address); err != nil {
			e.Logger.Info("shutting down the server")
		}
	}()

	sigCh := make(chan os.Signal, 1)
	signal.Notify(sigCh, sig)
	<-sigCh

	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(opts.ShutDownTimeout)*time.Second)
	defer cancel()
	if err := e.Shutdown(ctx); err != nil {
		e.Logger.Fatal(err)
	}
}
<commit_msg>Use stderr for error message.<commit_after>package main

import (
	"context"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"os/signal"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"
	"time"

	flags "github.com/jessevdk/go-flags"
	"github.com/labstack/echo"
	"github.com/lestrrat/go-server-starter/listener"
	"github.com/monochromegane/gannoy"
)

type Options struct {
	DataDir           string `short:"d" long:"data-dir" default:"." description:"Specify the directory where the meta files are located."`
	WithServerStarter bool   `short:"s" long:"server-starter" default:"false" description:"Use server-starter listener for server address."`
	ShutDownTimeout   int    `short:"t" long:"timeout" default:"10" description:"Specify the number of seconds for shutdown timeout."`
}

var opts Options

type Feature struct {
	W []float64 `json:"features"`
}

func main() {
	_, err := flags.Parse(&opts)
	if err != nil {
		os.Exit(1)
	}

	files, err := ioutil.ReadDir(opts.DataDir)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	databases := map[string]gannoy.GannoyIndex{}
	for _, file := range files {
		if file.IsDir() || filepath.Ext(file.Name()) != ".meta" {
			continue
		}
		key := strings.TrimSuffix(file.Name(), ".meta")
		gannoy, err := gannoy.NewGannoyIndex(filepath.Join(opts.DataDir, file.Name()), gannoy.Angular{}, gannoy.RandRandom{})
		if err != nil {
			fmt.Fprintln(os.Stderr, err)
			os.Exit(1)
		}
		databases[key] = gannoy
	}

	e := echo.New()
	e.GET("/search", func(c echo.Context) error {
		database := c.QueryParam("database")
		if _, ok := databases[database]; !ok {
			return c.NoContent(http.StatusNotFound)
		}
		key, err := strconv.Atoi(c.QueryParam("key"))
		if err != nil {
			key = -1
		}
		limit, err := strconv.Atoi(c.QueryParam("limit"))
		if err != nil {
			limit = 10
		}

		gannoy := databases[database]
		r, err := gannoy.GetNnsByKey(key, limit, -1)
		if err != nil || len(r) == 0 {
			return c.NoContent(http.StatusNotFound)
		}

		return c.JSON(http.StatusOK, r)
	})

	e.PUT("/databases/:database/features/:key", func(c echo.Context) error {
		database := c.Param("database")
		if _, ok := databases[database]; !ok {
			return c.NoContent(http.StatusUnprocessableEntity)
		}
		key, err := strconv.Atoi(c.Param("key"))
		if err != nil {
			return c.NoContent(http.StatusUnprocessableEntity)
		}
		feature := new(Feature)
		if err := c.Bind(feature); err != nil {
			return err
		}

		gannoy := databases[database]
		err = gannoy.AddItem(key, feature.W)
		if err != nil {
			return c.NoContent(http.StatusUnprocessableEntity)
		}
		return c.NoContent(http.StatusOK)
	})

	e.DELETE("/databases/:database/features/:key", func(c echo.Context) error {
		database := c.Param("database")
		if _, ok := databases[database]; !ok {
			return c.NoContent(http.StatusUnprocessableEntity)
		}
		key, err := strconv.Atoi(c.Param("key"))
		if err != nil {
			return c.NoContent(http.StatusUnprocessableEntity)
		}
		gannoy := databases[database]
		err = gannoy.RemoveItem(key)
		if err != nil {
			return c.NoContent(http.StatusUnprocessableEntity)
		}

		return c.NoContent(http.StatusOK)
	})

	address := ":1323"
	sig := os.Interrupt
	if opts.WithServerStarter {
		address = ""
		sig = syscall.SIGTERM
		listeners, err := listener.ListenAll()
		if err != nil && err != listener.ErrNoListeningTarget {
			fmt.Fprintln(os.Stderr, err)
			os.Exit(1)
		}
		e.Listener = listeners[0]
	}

	go func() {
		if err := e.Start(address); err != nil {
			e.Logger.Info("shutting down the server")
		}
	}()

	sigCh := make(chan os.Signal, 1)
	signal.Notify(sigCh, sig)
	<-sigCh

	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(opts.ShutDownTimeout)*time.Second)
	defer cancel()
	if err := e.Shutdown(ctx); err != nil {
		e.Logger.Fatal(err)
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2019 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 cmd

import (
	"bytes"
	"context"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"text/template"

	"github.com/blang/semver"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	"gopkg.in/russross/blackfriday.v2"

	"k8s.io/release/pkg/git"
	"k8s.io/release/pkg/notes"
	"k8s.io/release/pkg/notes/options"
	"k8s.io/release/pkg/util"
)

// changelogCmd represents the subcommand for `krel changelog`
var changelogCmd = &cobra.Command{
	Use:   "changelog",
	Short: "changelog maintains the lifecycle of CHANGELOG-x.y.{md,html} files",
	Long: `krel changelog

The 'changelog' subcommand of 'krel' does the following things by utilizing
the golang based 'release-notes' tool:

1. Generate the release notes for either a patch or a new minor release. Minor
   releases can be alpha, beta or rc’s, too.
   a) Create a new CHANGELOG-x.y.md file if not existing.
   b) Correctly prepend the generated notes to the existing CHANGELOG-x.y.md
      file if already existing. This also includes the modification of the
	  table of contents.

2. Convert the markdown release notes into a HTML equivalent on purpose of
   sending it by mail to the announce list. The HTML file will be dropped into
   the current working directly as 'CHANGELOG-x.y.html'. Sending the
   announcement is done by another subcommand of 'krel', not "changelog'.

3. Commit the modified CHANGELOG-x.y.md into the master branch as well as the
   corresponding release-branch of kubernetes/kubernetes. The release branch
   will be pruned from all other CHANGELOG-*.md files which do not belong to
   this release branch.
`,
	SilenceUsage:  true,
	SilenceErrors: true,
	PreRunE:       initLogging,
	RunE: func(cmd *cobra.Command, args []string) error {
		return runChangelog()
	},
}

type changelogOptions struct {
	tag       string
	branch    string
	bucket    string
	tars      string
	token     string
	htmlFile  string
	recordDir string
	replayDir string
}

var changelogOpts = &changelogOptions{}

const (
	tocStart = "<!-- BEGIN MUNGE: GENERATED_TOC -->"
	tocEnd   = "<!-- END MUNGE: GENERATED_TOC -->"
)

func init() {
	const (
		tagFlag   = "tag"
		tarsFlag  = "tars"
		tokenFlag = "token"
	)
	changelogCmd.PersistentFlags().StringVar(&changelogOpts.bucket, "bucket", "kubernetes-release", "Specify gs bucket to point to in generated notes")
	changelogCmd.PersistentFlags().StringVar(&changelogOpts.tag, tagFlag, "", "The version tag of the release, for example v1.17.0-rc.1")
	changelogCmd.PersistentFlags().StringVar(&changelogOpts.branch, "branch", "", "The branch to be used. Will be automatically inherited by the tag if not set.")
	changelogCmd.PersistentFlags().StringVar(&changelogOpts.tars, tarsFlag, ".", "Directory of tars to SHA512 sum for display")
	changelogCmd.PersistentFlags().StringVar(&changelogOpts.htmlFile, "html-file", "", "The target html file to be written. If empty, then it will be CHANGELOG-x.y.html in the current path.")
	changelogCmd.PersistentFlags().StringVar(&changelogOpts.recordDir, "record", "", "Record the API into a directory")
	changelogCmd.PersistentFlags().StringVar(&changelogOpts.replayDir, "replay", "", "Replay a previously recorded API from a directory")

	if err := changelogCmd.MarkPersistentFlagRequired(tagFlag); err != nil {
		logrus.Fatal(err)
	}

	rootCmd.AddCommand(changelogCmd)
}

func runChangelog() (err error) {
	token, ok := os.LookupEnv("GITHUB_TOKEN")
	if !ok && changelogOpts.replayDir == "" {
		return errors.New("neither environment variable `GITHUB_TOKEN` nor `--replay` is set")
	}
	changelogOpts.token = token

	tag, err := semver.Make(util.TrimTagPrefix(changelogOpts.tag))
	if err != nil {
		return err
	}
	branch := changelogOpts.branch
	if changelogOpts.branch == "" {
		branch = fmt.Sprintf("release-%d.%d", tag.Major, tag.Minor)
	}
	logrus.Infof("Using release branch %s", branch)

	logrus.Infof("Using local repository path %s", rootOpts.repoPath)
	repo, err := git.OpenRepo(rootOpts.repoPath)
	if err != nil {
		return err
	}

	head, err := repo.RevParse("HEAD")
	if err != nil {
		return err
	}
	logrus.Infof("Found HEAD commit %s", head)

	var markdown string
	if tag.Patch == 0 {
		if len(tag.Pre) == 0 {
			// New final minor versions should have remote release notes
			markdown, err = lookupRemoteReleaseNotes(branch)
		} else {
			// New minor alphas, betas and rc get generated notes
			start, e := repo.LatestTagForBranch(branch)
			if e != nil {
				return e
			}
			startTag := util.AddTagPrefix(start.String())

			logrus.Infof("Found latest tag %s", start)
			markdown, err = generateReleaseNotes(branch, startTag, head, changelogOpts.tag)
		}
	} else {
		// A patch version, let’s just use the previous patch
		start := util.AddTagPrefix(semver.Version{
			Major: tag.Major, Minor: tag.Minor, Patch: tag.Patch - 1,
		}.String())

		markdown, err = generateReleaseNotes(branch, start, head, changelogOpts.tag)
	}
	if err != nil {
		return err
	}

	logrus.Info("Generating TOC")
	toc, err := notes.GenerateTOC(markdown)
	if err != nil {
		return err
	}

	// Restore the currently checked out branch
	currentBranch, err := repo.CurrentBranch()
	if err != nil {
		return err
	}
	defer func() {
		if err := repo.Checkout(currentBranch); err != nil {
			logrus.Errorf("unable to restore branch %s: %v", currentBranch, err)
		}
	}()

	if err := repo.Checkout(git.Master); err != nil {
		return errors.Wrap(err, "checking out master branch")
	}

	if err := writeMarkdown(repo, toc, markdown, tag); err != nil {
		return err
	}

	if err := writeHTML(tag, markdown); err != nil {
		return err
	}

	return commitChanges(repo, branch, tag)
}

func generateReleaseNotes(branch, startRev, endRev, tag string) (string, error) {
	logrus.Info("Generating release notes")

	notesOptions := options.New()
	notesOptions.Branch = branch
	notesOptions.StartRev = startRev
	notesOptions.EndSHA = endRev
	notesOptions.EndRev = tag
	notesOptions.GithubOrg = git.DefaultGithubOrg
	notesOptions.GithubRepo = git.DefaultGithubRepo
	notesOptions.GithubToken = changelogOpts.token
	notesOptions.RepoPath = rootOpts.repoPath
	notesOptions.ReleaseBucket = changelogOpts.bucket
	notesOptions.ReleaseTars = changelogOpts.tars
	notesOptions.Debug = logrus.StandardLogger().Level >= logrus.DebugLevel
	notesOptions.RecordDir = changelogOpts.recordDir
	notesOptions.ReplayDir = changelogOpts.replayDir
	notesOptions.Pull = false

	if err := notesOptions.ValidateAndFinish(); err != nil {
		return "", err
	}

	gatherer := notes.NewGatherer(context.Background(), notesOptions)
	releaseNotes, history, err := gatherer.ListReleaseNotes()
	if err != nil {
		return "", errors.Wrapf(err, "listing release notes")
	}

	// Create the markdown
	doc, err := notes.CreateDocument(releaseNotes, history)
	if err != nil {
		return "", errors.Wrapf(err, "creating release note document")
	}

	markdown, err := doc.RenderMarkdown(
		changelogOpts.bucket, changelogOpts.tars,
		notesOptions.StartRev, notesOptions.EndRev,
	)
	if err != nil {
		return "", errors.Wrapf(
			err, "rendering release notes to markdown",
		)
	}

	return markdown, nil
}

func writeMarkdown(repo *git.Repo, toc, markdown string, tag semver.Version) error {
	changelogPath := markdownChangelogFilename(repo, tag)
	writeFile := func(t, m string) error {
		return ioutil.WriteFile(
			changelogPath, []byte(strings.Join(
				[]string{addTocMarkers(t), strings.TrimSpace(m)}, "\n",
			)), 0o644,
		)
	}

	// No changelog exists, simply write the content to a new one
	if _, err := os.Stat(changelogPath); os.IsNotExist(err) {
		logrus.Infof("Changelog %q does not exist, creating it", changelogPath)
		return writeFile(toc, markdown)
	}

	// Changelog seems to exist, prepend the notes and re-generate the TOC
	logrus.Infof("Adding new content to changelog file %s ", changelogPath)
	content, err := ioutil.ReadFile(changelogPath)
	if err != nil {
		return err
	}

	tocEndIndex := bytes.Index(content, []byte(tocEnd))
	if tocEndIndex < 0 {
		return errors.Errorf(
			"unable to find table of contents end marker `%s` in %q",
			tocEnd, changelogPath,
		)
	}

	mergedMarkdown := fmt.Sprintf(
		"%s\n%s", markdown, string(content[(len(tocEnd)+tocEndIndex):]),
	)
	mergedTOC, err := notes.GenerateTOC(mergedMarkdown)
	if err != nil {
		return err
	}
	return writeFile(mergedTOC, mergedMarkdown)
}

func htmlChangelogFilename(tag semver.Version) string {
	if changelogOpts.htmlFile != "" {
		return changelogOpts.htmlFile
	}
	return changelogFilename(tag, "html")
}

func markdownChangelogFilename(repo *git.Repo, tag semver.Version) string {
	return filepath.Join(repo.Dir(), changelogFilename(tag, "md"))
}

func changelogFilename(tag semver.Version, ext string) string {
	return fmt.Sprintf("CHANGELOG-%d.%d.%s", tag.Major, tag.Minor, ext)
}

func addTocMarkers(toc string) string {
	return fmt.Sprintf("%s\n\n%s\n%s\n", tocStart, toc, tocEnd)
}

const htmlTemplate = `<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>{{ .Title }}</title>
    <style type="text/css">
      table,
      th,
      tr,
      td {
        border: 1px solid gray;
        border-collapse: collapse;
        padding: 5px;
      }
    </style>
  </head>
  <body>
    {{ .Content }}
  </body>
</html>`

func writeHTML(tag semver.Version, markdown string) error {
	content := blackfriday.Run([]byte(markdown))

	t, err := template.New("html").Parse(htmlTemplate)
	if err != nil {
		return err
	}

	output := bytes.Buffer{}
	if err := t.Execute(&output, struct {
		Title, Content string
	}{util.AddTagPrefix(tag.String()), string(content)}); err != nil {
		return err
	}

	absOutputPath, err := filepath.Abs(htmlChangelogFilename(tag))
	if err != nil {
		return err
	}
	logrus.Infof("Writing single HTML to %s", absOutputPath)
	return ioutil.WriteFile(absOutputPath, output.Bytes(), 0o644)
}

func lookupRemoteReleaseNotes(branch string) (string, error) {
	logrus.Info("Assuming new minor release")

	remote := fmt.Sprintf(
		"https://raw.githubusercontent.com/kubernetes/sig-release/master/"+
			"releases/%s/release-notes-draft.md", branch,
	)
	resp, err := http.Get(remote)
	if err != nil {
		return "", errors.Wrapf(err,
			"fetching release notes from remote: %s", remote,
		)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return "", errors.Errorf(
			"remote release notes not found at: %s", remote,
		)
	}
	logrus.Info("Found release notes")

	content, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	return string(content), nil
}

func commitChanges(repo *git.Repo, branch string, tag semver.Version) error {
	// Master branch modifications
	filename := filepath.Base(markdownChangelogFilename(repo, tag))
	logrus.Infof("Adding %s to repository", filename)
	if err := repo.Add(filename); err != nil {
		return errors.Wrapf(err, "trying to add file %s to repository", filename)
	}

	logrus.Info("Committing changes to master branch in repository")
	if err := repo.Commit(fmt.Sprintf(
		"Add %s for %s", filename, util.AddTagPrefix(tag.String()),
	)); err != nil {
		return errors.Wrap(err, "committing changes into repository")
	}

	if branch != git.Master {
		// Release branch modifications
		if err := repo.Checkout(branch); err != nil {
			return errors.Wrapf(err, "checking out release branch %s", branch)
		}

		// Remove all other changelog files
		if err := repo.Rm(true, "CHANGELOG-*.md"); err != nil {
			return errors.Wrap(err, "unable to remove CHANGELOG-*.md files")
		}

		logrus.Info("Checking out changelog from master branch")
		if err := repo.Checkout(git.Master, filename); err != nil {
			return errors.Wrap(err, "checking out master branch changelog")
		}

		logrus.Info("Committing changes to release branch in repository")
		if err := repo.Commit(fmt.Sprintf(
			"Update %s for %s", filename, util.AddTagPrefix(tag.String()),
		)); err != nil {
			return errors.Wrap(err, "committing changes into repository")
		}
	}

	return nil
}
<commit_msg>Use tag from changelogOpts instead of function argument<commit_after>/*
Copyright 2019 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 cmd

import (
	"bytes"
	"context"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"text/template"

	"github.com/blang/semver"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	"gopkg.in/russross/blackfriday.v2"

	"k8s.io/release/pkg/git"
	"k8s.io/release/pkg/notes"
	"k8s.io/release/pkg/notes/options"
	"k8s.io/release/pkg/util"
)

// changelogCmd represents the subcommand for `krel changelog`
var changelogCmd = &cobra.Command{
	Use:   "changelog",
	Short: "changelog maintains the lifecycle of CHANGELOG-x.y.{md,html} files",
	Long: `krel changelog

The 'changelog' subcommand of 'krel' does the following things by utilizing
the golang based 'release-notes' tool:

1. Generate the release notes for either a patch or a new minor release. Minor
   releases can be alpha, beta or rc’s, too.
   a) Create a new CHANGELOG-x.y.md file if not existing.
   b) Correctly prepend the generated notes to the existing CHANGELOG-x.y.md
      file if already existing. This also includes the modification of the
	  table of contents.

2. Convert the markdown release notes into a HTML equivalent on purpose of
   sending it by mail to the announce list. The HTML file will be dropped into
   the current working directly as 'CHANGELOG-x.y.html'. Sending the
   announcement is done by another subcommand of 'krel', not "changelog'.

3. Commit the modified CHANGELOG-x.y.md into the master branch as well as the
   corresponding release-branch of kubernetes/kubernetes. The release branch
   will be pruned from all other CHANGELOG-*.md files which do not belong to
   this release branch.
`,
	SilenceUsage:  true,
	SilenceErrors: true,
	PreRunE:       initLogging,
	RunE: func(cmd *cobra.Command, args []string) error {
		return runChangelog()
	},
}

type changelogOptions struct {
	tag       string
	branch    string
	bucket    string
	tars      string
	token     string
	htmlFile  string
	recordDir string
	replayDir string
}

var changelogOpts = &changelogOptions{}

const (
	tocStart = "<!-- BEGIN MUNGE: GENERATED_TOC -->"
	tocEnd   = "<!-- END MUNGE: GENERATED_TOC -->"
)

func init() {
	const (
		tagFlag   = "tag"
		tarsFlag  = "tars"
		tokenFlag = "token"
	)
	changelogCmd.PersistentFlags().StringVar(&changelogOpts.bucket, "bucket", "kubernetes-release", "Specify gs bucket to point to in generated notes")
	changelogCmd.PersistentFlags().StringVar(&changelogOpts.tag, tagFlag, "", "The version tag of the release, for example v1.17.0-rc.1")
	changelogCmd.PersistentFlags().StringVar(&changelogOpts.branch, "branch", "", "The branch to be used. Will be automatically inherited by the tag if not set.")
	changelogCmd.PersistentFlags().StringVar(&changelogOpts.tars, tarsFlag, ".", "Directory of tars to SHA512 sum for display")
	changelogCmd.PersistentFlags().StringVar(&changelogOpts.htmlFile, "html-file", "", "The target html file to be written. If empty, then it will be CHANGELOG-x.y.html in the current path.")
	changelogCmd.PersistentFlags().StringVar(&changelogOpts.recordDir, "record", "", "Record the API into a directory")
	changelogCmd.PersistentFlags().StringVar(&changelogOpts.replayDir, "replay", "", "Replay a previously recorded API from a directory")

	if err := changelogCmd.MarkPersistentFlagRequired(tagFlag); err != nil {
		logrus.Fatal(err)
	}

	rootCmd.AddCommand(changelogCmd)
}

func runChangelog() (err error) {
	token, ok := os.LookupEnv("GITHUB_TOKEN")
	if !ok && changelogOpts.replayDir == "" {
		return errors.New("neither environment variable `GITHUB_TOKEN` nor `--replay` is set")
	}
	changelogOpts.token = token

	tag, err := semver.Make(util.TrimTagPrefix(changelogOpts.tag))
	if err != nil {
		return err
	}
	branch := changelogOpts.branch
	if changelogOpts.branch == "" {
		branch = fmt.Sprintf("release-%d.%d", tag.Major, tag.Minor)
	}
	logrus.Infof("Using release branch %s", branch)

	logrus.Infof("Using local repository path %s", rootOpts.repoPath)
	repo, err := git.OpenRepo(rootOpts.repoPath)
	if err != nil {
		return err
	}

	head, err := repo.RevParse("HEAD")
	if err != nil {
		return err
	}
	logrus.Infof("Found HEAD commit %s", head)

	var markdown string
	if tag.Patch == 0 {
		if len(tag.Pre) == 0 {
			// New final minor versions should have remote release notes
			markdown, err = lookupRemoteReleaseNotes(branch)
		} else {
			// New minor alphas, betas and rc get generated notes
			start, e := repo.LatestTagForBranch(branch)
			if e != nil {
				return e
			}
			startTag := util.AddTagPrefix(start.String())

			logrus.Infof("Found latest tag %s", start)
			markdown, err = generateReleaseNotes(branch, startTag, head)
		}
	} else {
		// A patch version, let’s just use the previous patch
		start := util.AddTagPrefix(semver.Version{
			Major: tag.Major, Minor: tag.Minor, Patch: tag.Patch - 1,
		}.String())

		markdown, err = generateReleaseNotes(branch, start, head)
	}
	if err != nil {
		return err
	}

	logrus.Info("Generating TOC")
	toc, err := notes.GenerateTOC(markdown)
	if err != nil {
		return err
	}

	// Restore the currently checked out branch
	currentBranch, err := repo.CurrentBranch()
	if err != nil {
		return err
	}
	defer func() {
		if err := repo.Checkout(currentBranch); err != nil {
			logrus.Errorf("unable to restore branch %s: %v", currentBranch, err)
		}
	}()

	if err := repo.Checkout(git.Master); err != nil {
		return errors.Wrap(err, "checking out master branch")
	}

	if err := writeMarkdown(repo, toc, markdown, tag); err != nil {
		return err
	}

	if err := writeHTML(tag, markdown); err != nil {
		return err
	}

	return commitChanges(repo, branch, tag)
}

func generateReleaseNotes(branch, startRev, endRev string) (string, error) {
	logrus.Info("Generating release notes")

	notesOptions := options.New()
	notesOptions.Branch = branch
	notesOptions.StartRev = startRev
	notesOptions.EndSHA = endRev
	notesOptions.EndRev = changelogOpts.tag
	notesOptions.GithubOrg = git.DefaultGithubOrg
	notesOptions.GithubRepo = git.DefaultGithubRepo
	notesOptions.GithubToken = changelogOpts.token
	notesOptions.RepoPath = rootOpts.repoPath
	notesOptions.ReleaseBucket = changelogOpts.bucket
	notesOptions.ReleaseTars = changelogOpts.tars
	notesOptions.Debug = logrus.StandardLogger().Level >= logrus.DebugLevel
	notesOptions.RecordDir = changelogOpts.recordDir
	notesOptions.ReplayDir = changelogOpts.replayDir
	notesOptions.Pull = false

	if err := notesOptions.ValidateAndFinish(); err != nil {
		return "", err
	}

	gatherer := notes.NewGatherer(context.Background(), notesOptions)
	releaseNotes, history, err := gatherer.ListReleaseNotes()
	if err != nil {
		return "", errors.Wrapf(err, "listing release notes")
	}

	// Create the markdown
	doc, err := notes.CreateDocument(releaseNotes, history)
	if err != nil {
		return "", errors.Wrapf(err, "creating release note document")
	}

	markdown, err := doc.RenderMarkdown(
		changelogOpts.bucket, changelogOpts.tars,
		notesOptions.StartRev, notesOptions.EndRev,
	)
	if err != nil {
		return "", errors.Wrapf(
			err, "rendering release notes to markdown",
		)
	}

	return markdown, nil
}

func writeMarkdown(repo *git.Repo, toc, markdown string, tag semver.Version) error {
	changelogPath := markdownChangelogFilename(repo, tag)
	writeFile := func(t, m string) error {
		return ioutil.WriteFile(
			changelogPath, []byte(strings.Join(
				[]string{addTocMarkers(t), strings.TrimSpace(m)}, "\n",
			)), 0o644,
		)
	}

	// No changelog exists, simply write the content to a new one
	if _, err := os.Stat(changelogPath); os.IsNotExist(err) {
		logrus.Infof("Changelog %q does not exist, creating it", changelogPath)
		return writeFile(toc, markdown)
	}

	// Changelog seems to exist, prepend the notes and re-generate the TOC
	logrus.Infof("Adding new content to changelog file %s ", changelogPath)
	content, err := ioutil.ReadFile(changelogPath)
	if err != nil {
		return err
	}

	tocEndIndex := bytes.Index(content, []byte(tocEnd))
	if tocEndIndex < 0 {
		return errors.Errorf(
			"unable to find table of contents end marker `%s` in %q",
			tocEnd, changelogPath,
		)
	}

	mergedMarkdown := fmt.Sprintf(
		"%s\n%s", markdown, string(content[(len(tocEnd)+tocEndIndex):]),
	)
	mergedTOC, err := notes.GenerateTOC(mergedMarkdown)
	if err != nil {
		return err
	}
	return writeFile(mergedTOC, mergedMarkdown)
}

func htmlChangelogFilename(tag semver.Version) string {
	if changelogOpts.htmlFile != "" {
		return changelogOpts.htmlFile
	}
	return changelogFilename(tag, "html")
}

func markdownChangelogFilename(repo *git.Repo, tag semver.Version) string {
	return filepath.Join(repo.Dir(), changelogFilename(tag, "md"))
}

func changelogFilename(tag semver.Version, ext string) string {
	return fmt.Sprintf("CHANGELOG-%d.%d.%s", tag.Major, tag.Minor, ext)
}

func addTocMarkers(toc string) string {
	return fmt.Sprintf("%s\n\n%s\n%s\n", tocStart, toc, tocEnd)
}

const htmlTemplate = `<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>{{ .Title }}</title>
    <style type="text/css">
      table,
      th,
      tr,
      td {
        border: 1px solid gray;
        border-collapse: collapse;
        padding: 5px;
      }
    </style>
  </head>
  <body>
    {{ .Content }}
  </body>
</html>`

func writeHTML(tag semver.Version, markdown string) error {
	content := blackfriday.Run([]byte(markdown))

	t, err := template.New("html").Parse(htmlTemplate)
	if err != nil {
		return err
	}

	output := bytes.Buffer{}
	if err := t.Execute(&output, struct {
		Title, Content string
	}{util.AddTagPrefix(tag.String()), string(content)}); err != nil {
		return err
	}

	absOutputPath, err := filepath.Abs(htmlChangelogFilename(tag))
	if err != nil {
		return err
	}
	logrus.Infof("Writing single HTML to %s", absOutputPath)
	return ioutil.WriteFile(absOutputPath, output.Bytes(), 0o644)
}

func lookupRemoteReleaseNotes(branch string) (string, error) {
	logrus.Info("Assuming new minor release")

	remote := fmt.Sprintf(
		"https://raw.githubusercontent.com/kubernetes/sig-release/master/"+
			"releases/%s/release-notes-draft.md", branch,
	)
	resp, err := http.Get(remote)
	if err != nil {
		return "", errors.Wrapf(err,
			"fetching release notes from remote: %s", remote,
		)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return "", errors.Errorf(
			"remote release notes not found at: %s", remote,
		)
	}
	logrus.Info("Found release notes")

	content, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	return string(content), nil
}

func commitChanges(repo *git.Repo, branch string, tag semver.Version) error {
	// Master branch modifications
	filename := filepath.Base(markdownChangelogFilename(repo, tag))
	logrus.Infof("Adding %s to repository", filename)
	if err := repo.Add(filename); err != nil {
		return errors.Wrapf(err, "trying to add file %s to repository", filename)
	}

	logrus.Info("Committing changes to master branch in repository")
	if err := repo.Commit(fmt.Sprintf(
		"Add %s for %s", filename, util.AddTagPrefix(tag.String()),
	)); err != nil {
		return errors.Wrap(err, "committing changes into repository")
	}

	if branch != git.Master {
		// Release branch modifications
		if err := repo.Checkout(branch); err != nil {
			return errors.Wrapf(err, "checking out release branch %s", branch)
		}

		// Remove all other changelog files
		if err := repo.Rm(true, "CHANGELOG-*.md"); err != nil {
			return errors.Wrap(err, "unable to remove CHANGELOG-*.md files")
		}

		logrus.Info("Checking out changelog from master branch")
		if err := repo.Checkout(git.Master, filename); err != nil {
			return errors.Wrap(err, "checking out master branch changelog")
		}

		logrus.Info("Committing changes to release branch in repository")
		if err := repo.Commit(fmt.Sprintf(
			"Update %s for %s", filename, util.AddTagPrefix(tag.String()),
		)); err != nil {
			return errors.Wrap(err, "committing changes into repository")
		}
	}

	return nil
}
<|endoftext|>