<commit_before>package jsonapi

import (
	"encoding/json"
	"errors"
	"fmt"
	"reflect"
)

type marshalingContext struct {
	root           map[string]interface{}
	rootName       string
	isSingleStruct bool
	prefix         string
}

func makeContext(rootName string, isSingleStruct bool, prefix string) *marshalingContext {
	ctx := &marshalingContext{}
	ctx.rootName = rootName
	ctx.root = map[string]interface{}{}
	ctx.root[rootName] = []interface{}{}
	ctx.isSingleStruct = isSingleStruct
	ctx.prefix = prefix
	return ctx
}

// MarshalIdentifier interface is necessary to give an element
// a unique ID. This interface must be implemented for
// marshal and unmarshal in order to let them store
// elements
type MarshalIdentifier interface {
	GetID() string
}

// ReferenceID todo later
type ReferenceID struct {
	ID   string
	Type string // Todo: Must be removed, is redundant because it's already in `Reference` struct
	Name string
}

// Reference information about possible references of a struct
type Reference struct {
	Type string
	Name string
}

// MarshalReferences must be implemented if the struct to be serialized has relations. This must be done
// because jsonapi needs information about relations even if many to many relations or many to one relations
// are empty
type MarshalReferences interface {
	GetReferences() []Reference
}

// MarshalLinkedRelations must be implemented if there are references and the reference IDs should be included
type MarshalLinkedRelations interface {
	MarshalReferences
	GetReferencedIDs() []ReferenceID
}

// MarshalIncludedRelations must be implemented if referenced structs should be included
type MarshalIncludedRelations interface {
	MarshalReferences
	GetReferencedStructs() []MarshalIdentifier
}

// MarshalPrefix does the same as Marshal but adds a prefix to generated URLs
func MarshalPrefix(data interface{}, prefix string) (interface{}, error) {
	return nil, errors.New("Will never be implemented, must be moved into API layer")
}

// Marshal is the new shit
func Marshal(data interface{}) (map[string]interface{}, error) {
	if data == nil {
		return map[string]interface{}{}, errors.New("nil cannot be marshalled")
	}

	switch reflect.TypeOf(data).Kind() {
	case reflect.Slice:
		return marshalSlice(data)
	case reflect.Struct:
		return marshalStruct(data.(MarshalIdentifier), "")
	default:
		return map[string]interface{}{}, errors.New("Marshal only accepts slice, struct or ptr types")
	}
}

// marshalSlice marshals a slice TODO
func marshalSlice(data interface{}) (map[string]interface{}, error) {
	result := make(map[string]interface{})

	val := reflect.ValueOf(data)
	if val.Kind() != reflect.Slice {
		return result, errors.New("data must be a slice")
	}

	dataElements := []map[string]interface{}{}
	var referencedStructs []MarshalIdentifier

	for i := 0; i < val.Len(); i++ {
		k := val.Index(i).Interface()
		element, ok := k.(MarshalIdentifier)
		if !ok {
			return result, errors.New("all elements within the slice must implement api2go.MarshalIdentifier")
		}

		content, err := marshalData(element)
		if err != nil {
			return result, err
		}

		dataElements = append(dataElements, content)

		included, ok := k.(MarshalIncludedRelations)
		if ok {
			referencedStructs = append(referencedStructs, included.GetReferencedStructs()...)
		}
	}

	includedElements, err := reduceDuplicates(referencedStructs, marshalData)
	if err != nil {
		return result, err
	}

	//data key is always present
	result["data"] = dataElements

	//included elements is only present when included
	if includedElements != nil {
		result["linked"] = includedElements
	}

	return result, nil
}

// reduceDuplicates eliminates duplicate MarshalIdentifier from input and calls `method` on every unique MarshalIdentifier
func reduceDuplicates(input []MarshalIdentifier, method func(MarshalIdentifier) (map[string]interface{}, error)) ([]map[string]interface{}, error) {
	var (
		alreadyIncluded  = make(map[string]map[string]bool)
		includedElements []map[string]interface{}
	)

	for _, referencedStruct := range input {
		if referencedStruct == nil {
			continue
		}

		structType := getStructType(referencedStruct)
		if alreadyIncluded[structType] == nil {
			alreadyIncluded[structType] = make(map[string]bool)
		}

		if !alreadyIncluded[structType][referencedStruct.GetID()] {
			marshalled, err := method(referencedStruct)
			if err != nil {
				return includedElements, err
			}

			includedElements = append(includedElements, marshalled)
			alreadyIncluded[structType][referencedStruct.GetID()] = true
		}
	}

	return includedElements, nil
}

func marshalData(element MarshalIdentifier) (map[string]interface{}, error) {
	result := make(map[string]interface{})

	refValue := reflect.ValueOf(element)
	if refValue.Kind() == reflect.Ptr && refValue.IsNil() {
		return result, errors.New("MarshalIdentifier must not be nil")
	}

	fmt.Printf("\n%#v", element)

	id := element.GetID()
	content := getStructFields(element)
	for k, v := range content {
		result[k] = v
	}

	// its important that the id from the interface
	// gets added afterwards, otherwise an ID field
	// could conflict with the actual marshalling
	result["id"] = id
	result["type"] = getStructType(element)

	// optional relationship interface for struct
	references, ok := element.(MarshalLinkedRelations)
	if ok {
		result["links"] = getStructLinks(references)
	}

	return result, nil
}

// getStructLinks returns the link struct with ids
func getStructLinks(relationer MarshalLinkedRelations) map[string]interface{} {
	referencedIDs := relationer.GetReferencedIDs()
	sortedResults := make(map[string][]ReferenceID)
	links := make(map[string]interface{})

	for _, referenceID := range referencedIDs {
		sortedResults[referenceID.Type] = append(sortedResults[referenceID.Type], referenceID)
	}

	for referenceType, referenceIDs := range sortedResults {
		switch len(sortedResults[referenceType]) {
		case 0:
			continue
		case 1:
			links[referenceIDs[0].Name] = map[string]interface{}{
				"id":   referenceIDs[0].ID,
				"type": referenceType,
			}
		default:
			// multiple elements in links
			var ids []string

			for _, referenceID := range referenceIDs {
				ids = append(ids, referenceID.ID)
			}

			links[referenceIDs[0].Name] = map[string]interface{}{
				"ids":  ids,
				"type": referenceType,
			}
		}
	}
	return links
}

func getIncludedStructs(included MarshalIncludedRelations) ([]map[string]interface{}, error) {
	var result = make([]map[string]interface{}, 0)
	includedStructs := included.GetReferencedStructs()

	for key := range includedStructs {
		marshalled, err := marshalData(includedStructs[key])
		if err != nil {
			return result, err
		}

		result = append(result, marshalled)
	}

	return result, nil
}

func marshalStruct(data MarshalIdentifier, prefix string) (map[string]interface{}, error) {
	result := make(map[string]interface{})
	contentData, err := marshalData(data)
	if err != nil {
		return result, err
	}

	result["data"] = contentData

	included, ok := data.(MarshalIncludedRelations)
	if ok {
		linked, err := getIncludedStructs(included)
		if err != nil {
			return result, err
		}

		result["linked"] = linked
	}

	return result, nil
}

func getStructType(data MarshalIdentifier) string {
	reflectType := reflect.TypeOf(data)
	if reflectType.Kind() == reflect.Ptr {
		return Pluralize(Jsonify(reflectType.Elem().Name()))
	}

	return Pluralize(Jsonify(reflectType.Name()))
}

func getStructFields(data MarshalIdentifier) map[string]interface{} {
	result := make(map[string]interface{})
	val := reflect.ValueOf(data)
	if val.Kind() == reflect.Ptr {
		val = val.Elem()
	}
	valType := val.Type()
	for i := 0; i < val.NumField(); i++ {
		tag := valType.Field(i).Tag.Get("json")
		if tag == "-" {
			continue
		}

		field := val.Field(i)
		keyName := Jsonify(valType.Field(i).Name)
		result[keyName] = field.Interface()
	}

	return result
}

// MarshalToJSON marshals a struct to json
func MarshalToJSON(val interface{}) ([]byte, error) {
	result, err := Marshal(val)
	if err != nil {
		return []byte{}, err
	}

	return json.Marshal(result)
}

// MarshalToJSONPrefix does the same as MarshalToJSON but adds a prefix to generated URLs
func MarshalToJSONPrefix(val interface{}, prefix string) ([]byte, error) {
	//TODO must either be implemented with prefix or removed
	result, err := Marshal(val)
	if err != nil {
		return nil, err
	}
	return json.Marshal(result)
}
<commit_msg>determine if relations are to-one or to-many<commit_after>package jsonapi

import (
	"encoding/json"
	"errors"
	"fmt"
	"reflect"
)

type marshalingContext struct {
	root           map[string]interface{}
	rootName       string
	isSingleStruct bool
	prefix         string
}

func makeContext(rootName string, isSingleStruct bool, prefix string) *marshalingContext {
	ctx := &marshalingContext{}
	ctx.rootName = rootName
	ctx.root = map[string]interface{}{}
	ctx.root[rootName] = []interface{}{}
	ctx.isSingleStruct = isSingleStruct
	ctx.prefix = prefix
	return ctx
}

// MarshalIdentifier interface is necessary to give an element
// a unique ID. This interface must be implemented for
// marshal and unmarshal in order to let them store
// elements
type MarshalIdentifier interface {
	GetID() string
}

// ReferenceID todo later
type ReferenceID struct {
	ID   string
	Type string // Todo: Must be removed, is redundant because it's already in `Reference` struct
	Name string
}

// Reference information about possible references of a struct
type Reference struct {
	Type string
	Name string
}

// MarshalReferences must be implemented if the struct to be serialized has relations. This must be done
// because jsonapi needs information about relations even if many to many relations or many to one relations
// are empty
type MarshalReferences interface {
	GetReferences() []Reference
}

// MarshalLinkedRelations must be implemented if there are references and the reference IDs should be included
type MarshalLinkedRelations interface {
	MarshalReferences
	GetReferencedIDs() []ReferenceID
}

// MarshalIncludedRelations must be implemented if referenced structs should be included
type MarshalIncludedRelations interface {
	MarshalReferences
	GetReferencedStructs() []MarshalIdentifier
}

// MarshalPrefix does the same as Marshal but adds a prefix to generated URLs
func MarshalPrefix(data interface{}, prefix string) (interface{}, error) {
	return nil, errors.New("Will never be implemented, must be moved into API layer")
}

// Marshal is the new shit
func Marshal(data interface{}) (map[string]interface{}, error) {
	if data == nil {
		return map[string]interface{}{}, errors.New("nil cannot be marshalled")
	}

	switch reflect.TypeOf(data).Kind() {
	case reflect.Slice:
		return marshalSlice(data)
	case reflect.Struct, reflect.Ptr:
		return marshalStruct(data.(MarshalIdentifier), "")
	default:
		return map[string]interface{}{}, errors.New("Marshal only accepts slice, struct or ptr types")
	}
}

// marshalSlice marshals a slice TODO
func marshalSlice(data interface{}) (map[string]interface{}, error) {
	result := make(map[string]interface{})

	val := reflect.ValueOf(data)
	if val.Kind() != reflect.Slice {
		return result, errors.New("data must be a slice")
	}

	dataElements := []map[string]interface{}{}
	var referencedStructs []MarshalIdentifier

	for i := 0; i < val.Len(); i++ {
		k := val.Index(i).Interface()
		element, ok := k.(MarshalIdentifier)
		if !ok {
			return result, errors.New("all elements within the slice must implement api2go.MarshalIdentifier")
		}

		content, err := marshalData(element)
		if err != nil {
			return result, err
		}

		dataElements = append(dataElements, content)

		included, ok := k.(MarshalIncludedRelations)
		if ok {
			referencedStructs = append(referencedStructs, included.GetReferencedStructs()...)
		}
	}

	includedElements, err := reduceDuplicates(referencedStructs, marshalData)
	if err != nil {
		return result, err
	}

	//data key is always present
	result["data"] = dataElements

	//included elements is only present when included
	if includedElements != nil {
		result["linked"] = includedElements
	}

	return result, nil
}

// reduceDuplicates eliminates duplicate MarshalIdentifier from input and calls `method` on every unique MarshalIdentifier
func reduceDuplicates(input []MarshalIdentifier, method func(MarshalIdentifier) (map[string]interface{}, error)) ([]map[string]interface{}, error) {
	var (
		alreadyIncluded  = make(map[string]map[string]bool)
		includedElements []map[string]interface{}
	)

	for _, referencedStruct := range input {
		if referencedStruct == nil {
			continue
		}

		structType := getStructType(referencedStruct)
		if alreadyIncluded[structType] == nil {
			alreadyIncluded[structType] = make(map[string]bool)
		}

		if !alreadyIncluded[structType][referencedStruct.GetID()] {
			marshalled, err := method(referencedStruct)
			if err != nil {
				return includedElements, err
			}

			includedElements = append(includedElements, marshalled)
			alreadyIncluded[structType][referencedStruct.GetID()] = true
		}
	}

	return includedElements, nil
}

func marshalData(element MarshalIdentifier) (map[string]interface{}, error) {
	result := make(map[string]interface{})

	refValue := reflect.ValueOf(element)
	if refValue.Kind() == reflect.Ptr && refValue.IsNil() {
		return result, errors.New("MarshalIdentifier must not be nil")
	}

	id := element.GetID()
	content := getStructFields(element)
	for k, v := range content {
		result[k] = v
	}

	// its important that the id from the interface
	// gets added afterwards, otherwise an ID field
	// could conflict with the actual marshalling
	result["id"] = id
	result["type"] = getStructType(element)

	// optional relationship interface for struct
	references, ok := element.(MarshalLinkedRelations)
	if ok {
		result["links"] = getStructLinks(references)
	}

	return result, nil
}

// getStructLinks returns the link struct with ids
func getStructLinks(relationer MarshalLinkedRelations) map[string]interface{} {
	referencedIDs := relationer.GetReferencedIDs()
	sortedResults := make(map[string][]ReferenceID)
	links := make(map[string]interface{})

	for _, referenceID := range referencedIDs {
		sortedResults[referenceID.Type] = append(sortedResults[referenceID.Type], referenceID)
	}

	references := relationer.GetReferences()

	// helper mad to check if all references are included to also include mepty ones
	notIncludedReferences := map[string]Reference{}
	for _, reference := range references {
		notIncludedReferences[reference.Name] = reference
	}

	for referenceType, referenceIDs := range sortedResults {
		name := referenceIDs[0].Name
		// if referenceType is plural, we have ids, otherwise it's just one id
		if Pluralize(name) == name {
			// multiple elements in links
			var ids []string

			for _, referenceID := range referenceIDs {
				ids = append(ids, referenceID.ID)
			}

			links[name] = map[string]interface{}{
				"ids":  ids,
				"type": referenceType,
			}
		} else {
			links[name] = map[string]interface{}{
				"id":   referenceIDs[0].ID,
				"type": referenceType,
			}
		}

		// this marks the reference as already included
		delete(notIncludedReferences, referenceIDs[0].Name)
	}

	// check for empty references
	for name, reference := range notIncludedReferences {
		links[name] = map[string]interface{}{
			"type": reference.Type,
		}
	}

	return links
}

func getIncludedStructs(included MarshalIncludedRelations) ([]map[string]interface{}, error) {
	var result = make([]map[string]interface{}, 0)
	includedStructs := included.GetReferencedStructs()

	for key := range includedStructs {
		marshalled, err := marshalData(includedStructs[key])
		if err != nil {
			return result, err
		}

		result = append(result, marshalled)
	}

	return result, nil
}

func marshalStruct(data MarshalIdentifier, prefix string) (map[string]interface{}, error) {
	result := make(map[string]interface{})
	contentData, err := marshalData(data)
	if err != nil {
		return result, err
	}

	result["data"] = contentData

	included, ok := data.(MarshalIncludedRelations)
	if ok {
		linked, err := getIncludedStructs(included)
		if err != nil {
			return result, err
		}

		result["linked"] = linked
	}

	return result, nil
}

func getStructType(data MarshalIdentifier) string {
	reflectType := reflect.TypeOf(data)
	if reflectType.Kind() == reflect.Ptr {
		return Pluralize(Jsonify(reflectType.Elem().Name()))
	}

	return Pluralize(Jsonify(reflectType.Name()))
}

func getStructFields(data MarshalIdentifier) map[string]interface{} {
	result := make(map[string]interface{})
	val := reflect.ValueOf(data)
	if val.Kind() == reflect.Ptr {
		val = val.Elem()
	}
	valType := val.Type()
	for i := 0; i < val.NumField(); i++ {
		tag := valType.Field(i).Tag.Get("json")
		if tag == "-" {
			continue
		}

		field := val.Field(i)
		keyName := Jsonify(valType.Field(i).Name)
		result[keyName] = field.Interface()
	}

	return result
}

// MarshalToJSON marshals a struct to json
func MarshalToJSON(val interface{}) ([]byte, error) {
	result, err := Marshal(val)
	if err != nil {
		return []byte{}, err
	}

	return json.Marshal(result)
}

// MarshalToJSONPrefix does the same as MarshalToJSON but adds a prefix to generated URLs
func MarshalToJSONPrefix(val interface{}, prefix string) ([]byte, error) {
	//TODO must either be implemented with prefix or removed
	result, err := Marshal(val)
	if err != nil {
		return nil, err
	}
	return json.Marshal(result)
}
<|endoftext|>


<commit_before>// Copyright 2015 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//////////////////////////////////////////////////////////////////////
// This binary pulls images from a Docker Registry using the go-containerregistry as backend.
// For the format specification, if the format is:
// 		1. 'docker': image is pulled as tarball and may be used with `docker load -i`.
// 		2. 'oci' (default): image will be pulled as a collection of files in OCI layout to directory.
// 		3. 'both': both formats of image are pulled.
// Unlike regular docker pull, the format this package uses is proprietary.

package main

import (
	"flag"
	"fmt"
	"log"
	ospkg "os"
	"path"
	"strings"

	"github.com/pkg/errors"

	"github.com/bazelbuild/rules_docker/container/go/pkg/oci"
	"github.com/bazelbuild/rules_docker/container/go/pkg/compat"
	"github.com/google/go-containerregistry/pkg/authn"
	"github.com/google/go-containerregistry/pkg/name"
	v1 "github.com/google/go-containerregistry/pkg/v1"
	"github.com/google/go-containerregistry/pkg/v1/cache"

	"github.com/google/go-containerregistry/pkg/v1/remote"
	"github.com/google/go-containerregistry/pkg/v1/tarball"
)

var (
	imgName         = flag.String("name", "", "The name location including repo and digest/tag of the docker image to pull and save. Supports fully-qualified tag or digest references.")
	directory       = flag.String("directory", "", "Where to save the images files. If pulling as Docker tarball, please specify the directory to save the tarball. The tarball is named as image.tar.")
	format          = flag.String("format", "", "Format to pull image from remote registry: If 'docker', image is pulled as tarball. If 'oci' (default), image will be pulled as a collection of files in OCI layout.")
	clientConfigDir = flag.String("client-config-dir", "", "The path to the directory where the client configuration files are located. Overiddes the value from DOCKER_CONFIG.")
	cachePath       = flag.String("cache", "", "Image's files cache directory.")
	arch            = flag.String("architecture", "", "Image platform's CPU architecture.")
	os              = flag.String("os", "", "Image's operating system, if referring to a multi-platform manifest list. Default linux.")
	osVersion       = flag.String("os-version", "", "Image's operating system version, if referring to a multi-platform manifest list. Input strings are space separated.")
	osFeatures      = flag.String("os-features", "", "Image's operating system features, if referring to a multi-platform manifest list. Input strings are space separated.")
	variant         = flag.String("variant", "", "Image's CPU variant, if referring to a multi-platform manifest list.")
	features        = flag.String("features", "", "Image's CPU features, if referring to a multi-platform manifest list.")
)

// Tag applied to images that were pulled by digest. This denotes
// that the image was (probably) not tagged with this, but avoids
// applying the ":latest" tag which might be misleading.
const iWasADigestTag = "i-was-a-digest"

// Directory containing the pulled OCI image artifacts without modification.
const ociImageDir = "image-oci"

// Directory containing actual images (symlinks or image.tar) to be used by container_import as the image.
const imageDir = "image"

// getTag parses the reference inside the name flag and returns the apt tag.
// WriteToFile requires a tag to write to the tarball, but may have been given a digest,
// in which case we tag the image with :i-was-a-digest instead.
func getTag(ref name.Reference) name.Reference {
	var err error
	tag, ok := ref.(name.Tag)
	if !ok {
		d, ok := ref.(name.Digest)
		if !ok {
			log.Fatal("ref wasn't a tag or digest")
		}
		s := fmt.Sprintf("%s:%s", d.Repository.Name(), iWasADigestTag)
		tag, err = name.NewTag(s)
		if err != nil {
			log.Fatalf("parsing digest as tag (%s): %v", s, err)
		}
	}
	return tag
}

// NOTE: This function is adapted from https://github.com/google/go-containerregistry/blob/master/pkg/crane/pull.go
// with slight modification to take in a platform argument.
// Pull the image with given <imgName> to destination <dstPath> with optional cache files and required platform specifications.
func pull(imgName, dstPath, format, cachePath string, platform v1.Platform) error {
	// Get a digest/tag based on the name.
	ref, err := name.ParseReference(imgName)
	if err != nil {
		return errors.Wrapf(err, "parsing tag %q", imgName)
	}

	// Fetch the image with desired cache files and platform specs.
	img, err := remote.Image(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain), remote.WithPlatform(platform))
	if err != nil {
		return errors.Wrapf(err, "reading image %q", ref)
	}
	if cachePath != "" {
		img = cache.Image(img, cache.NewFilesystemCache(cachePath))
	}

	// Image file to write to disk, either a tarball or OCI layout.
	ociPath := path.Join(dstPath, ociImageDir)
	legacyPath := path.Join(dstPath, imageDir)
	switch format {
	case "docker":
		tag := getTag(ref)
		if err := tarball.WriteToFile(path.Join(legacyPath, "image.tar"), tag, img); err != nil {
			log.Fatalf("failed to write image tarball to %q: %v", legacyPath, err)
		}
	default:
		if err := oci.Write(img, ociPath); err != nil {
			log.Fatalf("failed to write image to %q: %v", ociPath, err)
		}
	}

	if format != "docker" {
		if err := compat.LegacyFromOCIImage(img, ociPath, legacyPath); err != nil {
			return errors.Wrapf(err, "failed to generate symbolic links to pulled image at %s", dstPath)
		}
	}

	return nil
}

func main() {
	flag.Parse()
	log.Println("Running the Image Puller to pull images from a Docker Registry...")

	if *imgName == "" {
		log.Fatalln("Required option -name was not specified.")
	}
	if *directory == "" {
		log.Fatalln("Required option -directory was not specified.")
	}

	// If the user provided a client config directory, instruct the keychain resolver
	// to use it to look for the docker client config.
	if *clientConfigDir != "" {
		ospkg.Setenv("DOCKER_CONFIG", *clientConfigDir)
	}

	formatOptions := map[string]bool{
		"oci":    true,
		"docker": true,
		"both":   true,
	}
	if *format != "" && !formatOptions[*format] {
		log.Fatalln("Invalid option -format. Must be one of 'oci', 'docker', or 'both'.")
	}

	// Create a Platform struct with given arguments.
	platform := v1.Platform{
		Architecture: *arch,
		OS:           *os,
		OSVersion:    *osVersion,
		OSFeatures:   strings.Fields(*osFeatures),
		Variant:      *variant,
		Features:     strings.Fields(*features),
	}

	if err := pull(*imgName, *directory, *format, *cachePath, platform); err != nil {
		log.Fatalf("Image pull was unsuccessful: %v", err)
	}

	log.Printf("Successfully pulled image %q into %q", *imgName, *directory)
}
<commit_msg>Update new puller binary (#960)<commit_after>// Copyright 2015 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//////////////////////////////////////////////////////////////////////
// This binary pulls images from a Docker Registry using the go-containerregistry as backend.
// For the format specification, if the format is:
// 		1. 'docker': image is pulled as tarball and may be used with `docker load -i`.
// 		2. 'oci' (default): image will be pulled as a collection of files in OCI layout to directory.
// 		3. 'both': both formats of image are pulled.
// Unlike regular docker pull, the format this package uses is proprietary.

package main

import (
	"flag"
	"fmt"
	"log"
	ospkg "os"
	"path"
	"strings"

	"github.com/pkg/errors"

	"github.com/bazelbuild/rules_docker/container/go/pkg/oci"
	"github.com/bazelbuild/rules_docker/container/go/pkg/compat"
	"github.com/google/go-containerregistry/pkg/authn"
	"github.com/google/go-containerregistry/pkg/name"
	v1 "github.com/google/go-containerregistry/pkg/v1"
	"github.com/google/go-containerregistry/pkg/v1/cache"

	"github.com/google/go-containerregistry/pkg/v1/remote"
	"github.com/google/go-containerregistry/pkg/v1/tarball"
)

var (
	imgName         = flag.String("name", "", "The name location including repo and digest/tag of the docker image to pull and save. Supports fully-qualified tag or digest references.")
	directory       = flag.String("directory", "", "Where to save the images files. If pulling as Docker tarball, please specify the directory to save the tarball. The tarball is named as image.tar.")
	format          = flag.String("format", "", "Format to pull image from remote registry: If 'docker', image is pulled as tarball. If 'oci' (default), image will be pulled as a collection of files in OCI layout.")
	clientConfigDir = flag.String("client-config-dir", "", "The path to the directory where the client configuration files are located. Overiddes the value from DOCKER_CONFIG.")
	cachePath       = flag.String("cache", "", "Image's files cache directory.")
	arch            = flag.String("architecture", "", "Image platform's CPU architecture.")
	os              = flag.String("os", "", "Image's operating system, if referring to a multi-platform manifest list. Default linux.")
	osVersion       = flag.String("os-version", "", "Image's operating system version, if referring to a multi-platform manifest list. Input strings are space separated.")
	osFeatures      = flag.String("os-features", "", "Image's operating system features, if referring to a multi-platform manifest list. Input strings are space separated.")
	variant         = flag.String("variant", "", "Image's CPU variant, if referring to a multi-platform manifest list.")
	features        = flag.String("features", "", "Image's CPU features, if referring to a multi-platform manifest list.")
)

// Tag applied to images that were pulled by digest. This denotes
// that the image was (probably) not tagged with this, but avoids
// applying the ":latest" tag which might be misleading.
const iWasADigestTag = "i-was-a-digest"

// Directory containing the pulled OCI image artifacts without modification.
const ociImageDir = "image-oci"

// Directory containing actual images (symlinks or image.tar) to be used by container_import as the image.
const imageDir = "image"

// getTag parses the reference inside the name flag and returns the apt tag.
// WriteToFile requires a tag to write to the tarball, but may have been given a digest,
// in which case we tag the image with :i-was-a-digest instead.
func getTag(ref name.Reference) name.Reference {
	var err error
	tag, ok := ref.(name.Tag)
	if !ok {
		d, ok := ref.(name.Digest)
		if !ok {
			log.Fatal("ref wasn't a tag or digest")
		}
		s := fmt.Sprintf("%s:%s", d.Repository.Name(), iWasADigestTag)
		tag, err = name.NewTag(s)
		if err != nil {
			log.Fatalf("parsing digest as tag (%s): %v", s, err)
		}
	}
	return tag
}

// NOTE: This function is adapted from https://github.com/google/go-containerregistry/blob/master/pkg/crane/pull.go
// with slight modification to take in a platform argument.
// Pull the image with given <imgName> to destination <dstPath> with optional cache files and required platform specifications.
func pull(imgName, dstPath, format, cachePath string, platform v1.Platform) error {
	// Get a digest/tag based on the name.
	ref, err := name.ParseReference(imgName)
	if err != nil {
		return errors.Wrapf(err, "parsing tag %q", imgName)
	}

	// Fetch the image with desired cache files and platform specs.
	img, err := remote.Image(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain), remote.WithPlatform(platform))
	if err != nil {
		return errors.Wrapf(err, "reading image %q", ref)
	}
	if cachePath != "" {
		img = cache.Image(img, cache.NewFilesystemCache(cachePath))
	}

	// Image file to write to disk, either a tarball or OCI layout.
	ociPath := path.Join(dstPath, ociImageDir)
	legacyPath := path.Join(dstPath, imageDir)
	switch format {
	case "docker":
		tag := getTag(ref)
		if err := tarball.WriteToFile(path.Join(legacyPath, "image.tar"), tag, img); err != nil {
			log.Fatalf("failed to write image tarball to %q: %v", legacyPath, err)
		}
	default:
		if err := oci.Write(img, ociPath); err != nil {
			log.Fatalf("failed to write image to %q: %v", ociPath, err)
		}
	}

	if format != "docker" {
		if err := compat.LegacyFromOCIImage(img, ociPath, legacyPath); err != nil {
			return errors.Wrapf(err, "failed to generate symbolic links to pulled image at %s", dstPath)
		}
	}

	return nil
}

func main() {
	flag.Parse()
	log.Println("Running the Image Puller to pull images from a Docker Registry...")

	if *imgName == "" {
		log.Fatalln("Required option -name was not specified.")
	}
	if *directory == "" {
		log.Fatalln("Required option -directory was not specified.")
	}

	// If the user provided a client config directory, instruct the keychain resolver
	// to use it to look for the docker client config.
	if *clientConfigDir != "" {
		ospkg.Setenv("DOCKER_CONFIG", *clientConfigDir)
	}

	formatOptions := map[string]bool{
		"oci":    true,
		"docker": true,
	}
	if *format != "" && !formatOptions[*format] {
		log.Fatalln("Invalid option -format. Must be one of 'oci' or 'docker'.")
	}

	// Create a Platform struct with given arguments.
	platform := v1.Platform{
		Architecture: *arch,
		OS:           *os,
		OSVersion:    *osVersion,
		OSFeatures:   strings.Fields(*osFeatures),
		Variant:      *variant,
		Features:     strings.Fields(*features),
	}

	if err := pull(*imgName, *directory, *format, *cachePath, platform); err != nil {
		log.Fatalf("Image pull was unsuccessful: %v", err)
	}

	log.Printf("Successfully pulled image %q into %q", *imgName, *directory)
}
<|endoftext|>


<commit_before>package reducers

import (
	"reflect"
	"testing"

	"github.com/paulmach/go.geo"
)

func TestDouglasPeucker(t *testing.T) {
	p := geo.NewPath()
	if reducedPath := DouglasPeucker(p, 0.1); !reducedPath.Equals(p) {
		t.Error("dp should return same path if of length 0")
	}

	p.Push(geo.NewPoint(0, 0))
	if reducedPath := DouglasPeucker(p, 0.1); !reducedPath.Equals(p) {
		t.Error("dp should return same path if of length 1")
	}

	p.Push(geo.NewPoint(0.5, .2))
	if reducedPath := DouglasPeucker(p, 0.1); !reducedPath.Equals(p) {
		t.Error("dp should return same path if of length 2")
	}

	p.Push(geo.NewPoint(1, 0))

	if l := DouglasPeucker(p, 0.1).Length(); l != 3 {
		t.Errorf("dp reduce to incorrect number of points, expected 2, got %d", l)
	}

	if l := DouglasPeucker(p, 0.3).Length(); l != 2 {
		t.Errorf("dp reduce to incorrect number of points, expected 3, got %d", l)
	}

	p = geo.NewPath()
	p.Push(geo.NewPoint(0, 0))
	p.Push(geo.NewPoint(0, 1))
	p.Push(geo.NewPoint(0, 2))

	if l := DouglasPeucker(p, 0.0).Length(); l != 2 {
		t.Errorf("dp reduce should remove coliniar points")
	}

	dp := NewDouglasPeucker(0.1)
	p1 := p.Clone()
	dp.GeoReduce(p)
	if !p.Equals(p1){
		t.Errorf("dp GeoReduce should not modify original path")
	}
}

func TestDouglasPeuckerIndexMap(t *testing.T) {
	p := geo.NewPath()

	// zero length
	reduced, indexMap := DouglasPeuckerIndexMap(p, 0.1)
	if reduced.Length() != 0 {
		t.Error("dpim should return same path if of length 0")
	}

	if len(indexMap) != 0 {
		t.Error("dpim should have map of zero length for empty path input")
	}

	if reduced == p {
		t.Error("should create new path and not modify original")
	}

	// 1 length
	p.Push(geo.NewPoint(0, 0))
	reduced, indexMap = DouglasPeuckerIndexMap(p, 0.1)
	if !reduced.Equals(p) {
		t.Error("dpim should return same path if of length 1")
	}

	if !reflect.DeepEqual(indexMap, []int{0}) {
		t.Error("dpim should return []int{0} for index map when path is length 1")
	}

	if reduced == p {
		t.Error("should create new path and not modify original")
	}

	// 2 length
	p.Push(geo.NewPoint(0.5, .2))
	reduced, indexMap = DouglasPeuckerIndexMap(p, 0.1)
	if !reduced.Equals(p) {
		t.Error("dpim should return same path if of length 2")
	}

	if !reflect.DeepEqual(indexMap, []int{0, 1}) {
		t.Error("dpim should return []int{0, 1} for index map when path is length 2")
	}

	if reduced == p {
		t.Error("should create new path and not modify original")
	}

	// 3 length, does reduce
	p.Push(geo.NewPoint(1, 0))
	reduced, indexMap = DouglasPeuckerIndexMap(p, 0.3)
	if l := reduced.Length(); l != 2 {
		t.Errorf("dpim reduce to incorrect number of points, expected 2, got %d", l)
	}

	if !reflect.DeepEqual(indexMap, []int{0, 2}) {
		t.Errorf("dpim should return []int{0, 2} for index map, got %v %v", indexMap, []int{0, 2})
	}

	if reduced == p {
		t.Error("should create new path and not modify original")
	}

	// 3 length, doesn't reduce
	reduced, indexMap = DouglasPeuckerIndexMap(p, 0.1)
	if l := reduced.Length(); l != 3 {
		t.Errorf("dpim reduce to incorrect number of points, expected 3, got %d", l)
	}

	if !reflect.DeepEqual(indexMap, []int{0, 1, 2}) {
		t.Errorf("dpim should return []int{0, 1, 2} for index map, got %v", indexMap)
	}

	if reduced == p {
		t.Error("should create new path and not modify original")
	}
}

func TestDouglasPeuckerGeoIndexMap(t *testing.T) {
	p := geo.NewPath()

	// zero length
	reduced, indexMap := DouglasPeuckerGeoIndexMap(p, 1)
	if reduced.Length() != 0 {
		t.Error("dpim should return same path if of length 0")
	}

	if len(indexMap) != 0 {
		t.Error("dpim should have map of zero length for empty path input")
	}

	if reduced == p {
		t.Error("should create new path and not modify original")
	}

	// 1 length
	p.Push(geo.NewPointFromLatLng(0, 0))
	reduced, indexMap = DouglasPeuckerGeoIndexMap(p, 1)
	if !reduced.Equals(p) {
		t.Error("dpim should return same path if of length 1")
	}

	if !reflect.DeepEqual(indexMap, []int{0}) {
		t.Error("dpim should return []int{0} for index map when path is length 1")
	}

	if reduced == p {
		t.Error("should create new path and not modify original")
	}

	// 2 length
	p.Push(geo.NewPointFromLatLng(0.0001, 0.0002)) // 4th decimal place ~= 10m
	threshold := 20.0
	reduced, indexMap = DouglasPeuckerGeoIndexMap(p, threshold)
	if !reduced.Equals(p) {
		t.Error("dpim should return same path if of length 2")
	}

	if !reflect.DeepEqual(indexMap, []int{0, 1}) {
		t.Error("dpim should return []int{0, 1} for index map when path is length 2")
	}

	if reduced == p {
		t.Error("should create new path and not modify original")
	}

	// 3 length, does reduce
	p.Push(geo.NewPointFromLatLng(0, 0.0003))
	reduced, indexMap = DouglasPeuckerGeoIndexMap(p, threshold)
	if l := reduced.Length(); l != 2 {
		t.Errorf("dpim reduce to incorrect number of points, expected 2, got %d", l)
	}

	if !reflect.DeepEqual(indexMap, []int{0, 2}) {
		t.Errorf("dpim should return []int{0, 2} for index map, got %v %v", indexMap, []int{0, 2})
	}

	if reduced == p {
		t.Error("should create new path and not modify original")
	}

	// 3 length, doesn't reduce
	reduced, indexMap = DouglasPeuckerGeoIndexMap(p, threshold / 2)
	if l := reduced.Length(); l != 3 {
		t.Errorf("dpim reduce to incorrect number of points, expected 3, got %d", l)
	}

	if !reflect.DeepEqual(indexMap, []int{0, 1, 2}) {
		t.Errorf("dpim should return []int{0, 1, 2} for index map, got %v", indexMap)
	}

	if reduced == p {
		t.Error("should create new path and not modify original")
	}
}<commit_msg>run gofmt on dp tests<commit_after>package reducers

import (
	"reflect"
	"testing"

	"github.com/paulmach/go.geo"
)

func TestDouglasPeucker(t *testing.T) {
	p := geo.NewPath()
	if reducedPath := DouglasPeucker(p, 0.1); !reducedPath.Equals(p) {
		t.Error("dp should return same path if of length 0")
	}

	p.Push(geo.NewPoint(0, 0))
	if reducedPath := DouglasPeucker(p, 0.1); !reducedPath.Equals(p) {
		t.Error("dp should return same path if of length 1")
	}

	p.Push(geo.NewPoint(0.5, .2))
	if reducedPath := DouglasPeucker(p, 0.1); !reducedPath.Equals(p) {
		t.Error("dp should return same path if of length 2")
	}

	p.Push(geo.NewPoint(1, 0))

	if l := DouglasPeucker(p, 0.1).Length(); l != 3 {
		t.Errorf("dp reduce to incorrect number of points, expected 2, got %d", l)
	}

	if l := DouglasPeucker(p, 0.3).Length(); l != 2 {
		t.Errorf("dp reduce to incorrect number of points, expected 3, got %d", l)
	}

	p = geo.NewPath()
	p.Push(geo.NewPoint(0, 0))
	p.Push(geo.NewPoint(0, 1))
	p.Push(geo.NewPoint(0, 2))

	if l := DouglasPeucker(p, 0.0).Length(); l != 2 {
		t.Errorf("dp reduce should remove coliniar points")
	}

	dp := NewDouglasPeucker(0.1)
	p1 := p.Clone()
	dp.GeoReduce(p)
	if !p.Equals(p1) {
		t.Errorf("dp GeoReduce should not modify original path")
	}
}

func TestDouglasPeuckerIndexMap(t *testing.T) {
	p := geo.NewPath()

	// zero length
	reduced, indexMap := DouglasPeuckerIndexMap(p, 0.1)
	if reduced.Length() != 0 {
		t.Error("dpim should return same path if of length 0")
	}

	if len(indexMap) != 0 {
		t.Error("dpim should have map of zero length for empty path input")
	}

	if reduced == p {
		t.Error("should create new path and not modify original")
	}

	// 1 length
	p.Push(geo.NewPoint(0, 0))
	reduced, indexMap = DouglasPeuckerIndexMap(p, 0.1)
	if !reduced.Equals(p) {
		t.Error("dpim should return same path if of length 1")
	}

	if !reflect.DeepEqual(indexMap, []int{0}) {
		t.Error("dpim should return []int{0} for index map when path is length 1")
	}

	if reduced == p {
		t.Error("should create new path and not modify original")
	}

	// 2 length
	p.Push(geo.NewPoint(0.5, .2))
	reduced, indexMap = DouglasPeuckerIndexMap(p, 0.1)
	if !reduced.Equals(p) {
		t.Error("dpim should return same path if of length 2")
	}

	if !reflect.DeepEqual(indexMap, []int{0, 1}) {
		t.Error("dpim should return []int{0, 1} for index map when path is length 2")
	}

	if reduced == p {
		t.Error("should create new path and not modify original")
	}

	// 3 length, does reduce
	p.Push(geo.NewPoint(1, 0))
	reduced, indexMap = DouglasPeuckerIndexMap(p, 0.3)
	if l := reduced.Length(); l != 2 {
		t.Errorf("dpim reduce to incorrect number of points, expected 2, got %d", l)
	}

	if !reflect.DeepEqual(indexMap, []int{0, 2}) {
		t.Errorf("dpim should return []int{0, 2} for index map, got %v %v", indexMap, []int{0, 2})
	}

	if reduced == p {
		t.Error("should create new path and not modify original")
	}

	// 3 length, doesn't reduce
	reduced, indexMap = DouglasPeuckerIndexMap(p, 0.1)
	if l := reduced.Length(); l != 3 {
		t.Errorf("dpim reduce to incorrect number of points, expected 3, got %d", l)
	}

	if !reflect.DeepEqual(indexMap, []int{0, 1, 2}) {
		t.Errorf("dpim should return []int{0, 1, 2} for index map, got %v", indexMap)
	}

	if reduced == p {
		t.Error("should create new path and not modify original")
	}
}

func TestDouglasPeuckerGeoIndexMap(t *testing.T) {
	p := geo.NewPath()

	// zero length
	reduced, indexMap := DouglasPeuckerGeoIndexMap(p, 1)
	if reduced.Length() != 0 {
		t.Error("dpim should return same path if of length 0")
	}

	if len(indexMap) != 0 {
		t.Error("dpim should have map of zero length for empty path input")
	}

	if reduced == p {
		t.Error("should create new path and not modify original")
	}

	// 1 length
	p.Push(geo.NewPointFromLatLng(0, 0))
	reduced, indexMap = DouglasPeuckerGeoIndexMap(p, 1)
	if !reduced.Equals(p) {
		t.Error("dpim should return same path if of length 1")
	}

	if !reflect.DeepEqual(indexMap, []int{0}) {
		t.Error("dpim should return []int{0} for index map when path is length 1")
	}

	if reduced == p {
		t.Error("should create new path and not modify original")
	}

	// 2 length
	p.Push(geo.NewPointFromLatLng(0.0001, 0.0002)) // 4th decimal place ~= 10m
	threshold := 20.0
	reduced, indexMap = DouglasPeuckerGeoIndexMap(p, threshold)
	if !reduced.Equals(p) {
		t.Error("dpim should return same path if of length 2")
	}

	if !reflect.DeepEqual(indexMap, []int{0, 1}) {
		t.Error("dpim should return []int{0, 1} for index map when path is length 2")
	}

	if reduced == p {
		t.Error("should create new path and not modify original")
	}

	// 3 length, does reduce
	p.Push(geo.NewPointFromLatLng(0, 0.0003))
	reduced, indexMap = DouglasPeuckerGeoIndexMap(p, threshold)
	if l := reduced.Length(); l != 2 {
		t.Errorf("dpim reduce to incorrect number of points, expected 2, got %d", l)
	}

	if !reflect.DeepEqual(indexMap, []int{0, 2}) {
		t.Errorf("dpim should return []int{0, 2} for index map, got %v %v", indexMap, []int{0, 2})
	}

	if reduced == p {
		t.Error("should create new path and not modify original")
	}

	// 3 length, doesn't reduce
	reduced, indexMap = DouglasPeuckerGeoIndexMap(p, threshold/2)
	if l := reduced.Length(); l != 3 {
		t.Errorf("dpim reduce to incorrect number of points, expected 3, got %d", l)
	}

	if !reflect.DeepEqual(indexMap, []int{0, 1, 2}) {
		t.Errorf("dpim should return []int{0, 1, 2} for index map, got %v", indexMap)
	}

	if reduced == p {
		t.Error("should create new path and not modify original")
	}
}
<|endoftext|>


<commit_before>package bigcommerce

import (
	"context"
	"fmt"
	"net/http"

	"github.com/dghubble/sling"
)

// Order describes the product resource
type Order struct {
	ID                   int           `json:"id"`
	CustomerID           int           `json:"customer_id"`
	DateCreated          BCTime        `json:"date_created"`
	DateModified         BCTime        `json:"date_modified"`
	DateShipped          BCTime        `json:"date_shipped"`
	StatusID             int           `json:"status_id"`
	Status               string        `json:"status"`
	HandlingCostExTax    float64       `json:"handling_cost_ex_tax,string"`
	HandlingCostIncTax   float64       `json:"handling_cost_inc_tax,string"`
	HandlingCostTax      float64       `json:"handling_cost_tax,string"`
	ShippingCostExTax    float64       `json:"shipping_cost_ex_tax,string"`
	ShippingCostIncTax   float64       `json:"shipping_cost_inc_tax,string"`
	ShippingCostTax      float64       `json:"shipping_cost_tax,string"`
	SubTotalExTax        float64       `json:"subtotal_ex_tax,string"`
	SubTotalIncTax       float64       `json:"subtotal_inc_tax,string"`
	SubTotalTax          float64       `json:"subtotal_tax,string"`
	DiscountAmount       float64       `json:"discount_amount"`
	TotalExTax           float64       `json:"total_ex_tax,string"`
	TotalIncTax          float64       `json:"total_inc_tax,string"`
	TotalTax             float64       `json:"total_tax,string"`
	BaseShippingCost     float64       `json:"base_shipping_cost,string"`
	ItemsTotal           int           `json:"items_total"`
	PaymentMethod        string        `json:"payment_method"`
	PaymentStatus        string        `json:"payment_status"`
	IPAddress            string        `json:"ip_address"`
	CurrencyID           int           `json:"currency_id"`
	CurrencyCode         string        `json:"currency_code"`
	StaffNotes           string        `json:"staff_notes"`
	CustomerMessage      string        `json:"customer_message"`
	DiscountAmount       string        `json:"discount_amount"`
	CouponDiscount       string        `json:"counpon_discount"`
	ShippingAddressCount int           `json:"shipping_address_count"`
	BillingAddress       AddressEntity `json:"billing_address"`
}

// OrderService adds the APIs for the Order resource.
type OrderService struct {
	sling      *sling.Sling
	httpClient *http.Client
}

func newOrderService(sling *sling.Sling, httpClient *http.Client) *OrderService {
	return &OrderService{
		sling:      sling.Path("orders/"),
		httpClient: httpClient,
	}
}

// OrderListParams are the parameters for OrderService.List
type OrderListParams struct {
	Page          int     `url:"page,omitempty"`
	Limit         int     `url:"limit,omitempty"`
	Sort          string  `url:"sort,omitempty"`
	MinID         int     `url:"min_id,omitempty"`
	MaxID         int     `url:"max_id,omitempty"`
	MinTotal      float64 `url:"min_total,omitempty"`
	MaxTotal      float64 `url:"max_total,omitempty"`
	CustomerID    *int    `url:"customer_id,omitempty"`
	Email         string  `url:"email,omitempty"`
	StatusID      *int    `url:"status_id,omitempty"`
	PaymentMethod string  `url:"payment_method,omitempty"`
	//TODO: add date and boolean based params.
}

// List returns a list of Orders matching the given OrderListParams.
func (s *OrderService) List(ctx context.Context, params *OrderListParams) ([]Order, *http.Response, error) {
	var orders []Order
	apiError := new(APIError)

	resp, err := performRequest(ctx, s.sling.New().QueryStruct(params), s.httpClient, &orders, apiError)
	return orders, resp, relevantError(err, *apiError)
}

// Count returns an OrderCount for Orders that matches the given OrderListParams.
func (s *OrderService) Count(ctx context.Context, params *OrderListParams) (int, *http.Response, error) {
	var count count
	apiError := new(APIError)

	resp, err := performRequest(ctx, s.sling.Get("count").QueryStruct(params), s.httpClient, &count, apiError)
	return count.Count, resp, relevantError(err, *apiError)
}

// Show returns the requested Order.
func (s *OrderService) Show(ctx context.Context, id int32) (*Order, *http.Response, error) {
	order := new(Order)
	apiError := new(APIError)

	resp, err := performRequest(ctx, s.sling.New().Get(fmt.Sprintf("%d", id)), s.httpClient, order, apiError)
	return order, resp, relevantError(err, *apiError)
}

// OrderProduct defines a product to be included in the OrderBody.
// Regular Products require: ProductID and Quantity
// Custom Products require: Name, Quantity and PriceIncTax / PriceExTax
type OrderProduct struct {
	ProductID   int     `json:"product_id,omitempty"`
	ProductName string  `json:"name,omitempty"`
	Quantity    int     `json:"quantity"`
	PriceIncTax float64 `json:"price_inc_tax,omitempty"`
	PriceExTax  float64 `json:"price_ex_tax,omitempty"`
}

// OrderBody describes the order information given when creating a new Order.
type OrderBody struct {
	ExternalSource     string          `json:"external_source"`
	CustomerID         *int            `json:"customer_id"`
	StatusID           *int            `json:"status_id"`
	BillingAddress     AddressEntity   `json:"billing_address"`
	Products           []OrderProduct  `json:"products"`
	ShippingCostIncTax float64         `json:"shipping_cost_inc_tax,omitempty"`
	ShippingCostExTax  float64         `json:"shipping_cost_ex_tax,omitempty"`
	HandlingCostIncTax float64         `json:"handling_cost_inc_tax,omitempty"`
	HandlingCostExTax  float64         `json:"handling_cost_ex_tax,omitempty"`
	DiscountAmount     float64         `json:"discount_amount"`
	ShippingAddresses  AddressEntities `json:"shipping_addresses,omitempty"`
	CustomerMessage    string          `json:"customer_message"`
	StaffNotes         string          `json:"staff_notes"`
	PaymentMethod      string          `json:"payment_method"`
}

// New creates a new Order with the specified information and returns the new order.
func (s *OrderService) New(ctx context.Context, body *OrderBody) (*Order, *http.Response, error) {
	order := new(Order)
	apiError := new(APIError)

	resp, err := performRequest(ctx, s.sling.New().Post("").BodyJSON(body), s.httpClient, order, apiError)
	return order, resp, relevantError(err, *apiError)
}

// OrderEditParams describes the fields that are editable on an Order.
type OrderEditParams struct {
	CustomerID      *int           `json:"customer_id,omitempty"`
	StatusID        *int           `json:"status_id,omitempty"`
	IPAddress       string         `json:"ip_address,omitempty"`
	StaffNotes      string         `json:"staff_notes,omitempty"`
	CustomerMessage string         `json:"customer_message,omitempty"`
	BillingAddress  *AddressEntity `json:"billing_address,omitempty"`
}

// Edit updates the given OrderEditParams of the given Order.
func (s *OrderService) Edit(ctx context.Context, id int, params *OrderEditParams) (*Order, *http.Response, error) {
	order := new(Order)
	apiError := new(APIError)

	resp, err := performRequest(ctx, s.sling.New().Put(fmt.Sprintf("%d", id)).BodyJSON(params), s.httpClient, order, apiError)
	return order, resp, relevantError(err, *apiError)
}
<commit_msg>Remove duplicate field<commit_after>package bigcommerce

import (
	"context"
	"fmt"
	"net/http"

	"github.com/dghubble/sling"
)

// Order describes the product resource
type Order struct {
	ID                   int           `json:"id"`
	CustomerID           int           `json:"customer_id"`
	DateCreated          BCTime        `json:"date_created"`
	DateModified         BCTime        `json:"date_modified"`
	DateShipped          BCTime        `json:"date_shipped"`
	StatusID             int           `json:"status_id"`
	Status               string        `json:"status"`
	HandlingCostExTax    float64       `json:"handling_cost_ex_tax,string"`
	HandlingCostIncTax   float64       `json:"handling_cost_inc_tax,string"`
	HandlingCostTax      float64       `json:"handling_cost_tax,string"`
	ShippingCostExTax    float64       `json:"shipping_cost_ex_tax,string"`
	ShippingCostIncTax   float64       `json:"shipping_cost_inc_tax,string"`
	ShippingCostTax      float64       `json:"shipping_cost_tax,string"`
	SubTotalExTax        float64       `json:"subtotal_ex_tax,string"`
	SubTotalIncTax       float64       `json:"subtotal_inc_tax,string"`
	SubTotalTax          float64       `json:"subtotal_tax,string"`
	TotalExTax           float64       `json:"total_ex_tax,string"`
	TotalIncTax          float64       `json:"total_inc_tax,string"`
	TotalTax             float64       `json:"total_tax,string"`
	BaseShippingCost     float64       `json:"base_shipping_cost,string"`
	ItemsTotal           int           `json:"items_total"`
	PaymentMethod        string        `json:"payment_method"`
	PaymentStatus        string        `json:"payment_status"`
	IPAddress            string        `json:"ip_address"`
	CurrencyID           int           `json:"currency_id"`
	CurrencyCode         string        `json:"currency_code"`
	StaffNotes           string        `json:"staff_notes"`
	CustomerMessage      string        `json:"customer_message"`
	DiscountAmount       string        `json:"discount_amount"`
	CouponDiscount       string        `json:"counpon_discount"`
	ShippingAddressCount int           `json:"shipping_address_count"`
	BillingAddress       AddressEntity `json:"billing_address"`
}

// OrderService adds the APIs for the Order resource.
type OrderService struct {
	sling      *sling.Sling
	httpClient *http.Client
}

func newOrderService(sling *sling.Sling, httpClient *http.Client) *OrderService {
	return &OrderService{
		sling:      sling.Path("orders/"),
		httpClient: httpClient,
	}
}

// OrderListParams are the parameters for OrderService.List
type OrderListParams struct {
	Page          int     `url:"page,omitempty"`
	Limit         int     `url:"limit,omitempty"`
	Sort          string  `url:"sort,omitempty"`
	MinID         int     `url:"min_id,omitempty"`
	MaxID         int     `url:"max_id,omitempty"`
	MinTotal      float64 `url:"min_total,omitempty"`
	MaxTotal      float64 `url:"max_total,omitempty"`
	CustomerID    *int    `url:"customer_id,omitempty"`
	Email         string  `url:"email,omitempty"`
	StatusID      *int    `url:"status_id,omitempty"`
	PaymentMethod string  `url:"payment_method,omitempty"`
	//TODO: add date and boolean based params.
}

// List returns a list of Orders matching the given OrderListParams.
func (s *OrderService) List(ctx context.Context, params *OrderListParams) ([]Order, *http.Response, error) {
	var orders []Order
	apiError := new(APIError)

	resp, err := performRequest(ctx, s.sling.New().QueryStruct(params), s.httpClient, &orders, apiError)
	return orders, resp, relevantError(err, *apiError)
}

// Count returns an OrderCount for Orders that matches the given OrderListParams.
func (s *OrderService) Count(ctx context.Context, params *OrderListParams) (int, *http.Response, error) {
	var count count
	apiError := new(APIError)

	resp, err := performRequest(ctx, s.sling.Get("count").QueryStruct(params), s.httpClient, &count, apiError)
	return count.Count, resp, relevantError(err, *apiError)
}

// Show returns the requested Order.
func (s *OrderService) Show(ctx context.Context, id int32) (*Order, *http.Response, error) {
	order := new(Order)
	apiError := new(APIError)

	resp, err := performRequest(ctx, s.sling.New().Get(fmt.Sprintf("%d", id)), s.httpClient, order, apiError)
	return order, resp, relevantError(err, *apiError)
}

// OrderProduct defines a product to be included in the OrderBody.
// Regular Products require: ProductID and Quantity
// Custom Products require: Name, Quantity and PriceIncTax / PriceExTax
type OrderProduct struct {
	ProductID   int     `json:"product_id,omitempty"`
	ProductName string  `json:"name,omitempty"`
	Quantity    int     `json:"quantity"`
	PriceIncTax float64 `json:"price_inc_tax,omitempty"`
	PriceExTax  float64 `json:"price_ex_tax,omitempty"`
}

// OrderBody describes the order information given when creating a new Order.
type OrderBody struct {
	ExternalSource     string          `json:"external_source"`
	CustomerID         *int            `json:"customer_id"`
	StatusID           *int            `json:"status_id"`
	BillingAddress     AddressEntity   `json:"billing_address"`
	Products           []OrderProduct  `json:"products"`
	ShippingCostIncTax float64         `json:"shipping_cost_inc_tax,omitempty"`
	ShippingCostExTax  float64         `json:"shipping_cost_ex_tax,omitempty"`
	HandlingCostIncTax float64         `json:"handling_cost_inc_tax,omitempty"`
	HandlingCostExTax  float64         `json:"handling_cost_ex_tax,omitempty"`
	DiscountAmount     float64         `json:"discount_amount"`
	ShippingAddresses  AddressEntities `json:"shipping_addresses,omitempty"`
	CustomerMessage    string          `json:"customer_message"`
	StaffNotes         string          `json:"staff_notes"`
	PaymentMethod      string          `json:"payment_method"`
}

// New creates a new Order with the specified information and returns the new order.
func (s *OrderService) New(ctx context.Context, body *OrderBody) (*Order, *http.Response, error) {
	order := new(Order)
	apiError := new(APIError)

	resp, err := performRequest(ctx, s.sling.New().Post("").BodyJSON(body), s.httpClient, order, apiError)
	return order, resp, relevantError(err, *apiError)
}

// OrderEditParams describes the fields that are editable on an Order.
type OrderEditParams struct {
	CustomerID      *int           `json:"customer_id,omitempty"`
	StatusID        *int           `json:"status_id,omitempty"`
	IPAddress       string         `json:"ip_address,omitempty"`
	StaffNotes      string         `json:"staff_notes,omitempty"`
	CustomerMessage string         `json:"customer_message,omitempty"`
	BillingAddress  *AddressEntity `json:"billing_address,omitempty"`
}

// Edit updates the given OrderEditParams of the given Order.
func (s *OrderService) Edit(ctx context.Context, id int, params *OrderEditParams) (*Order, *http.Response, error) {
	order := new(Order)
	apiError := new(APIError)

	resp, err := performRequest(ctx, s.sling.New().Put(fmt.Sprintf("%d", id)).BodyJSON(params), s.httpClient, order, apiError)
	return order, resp, relevantError(err, *apiError)
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Vitess 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 topotools

import (
	"fmt"
	"sync"

	"context"

	"vitess.io/vitess/go/vt/concurrency"
	"vitess.io/vitess/go/vt/logutil"
	"vitess.io/vitess/go/vt/topo"
	"vitess.io/vitess/go/vt/topo/topoproto"

	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
)

// RebuildKeyspace rebuilds the serving graph data while locking out other changes.
func RebuildKeyspace(ctx context.Context, log logutil.Logger, ts *topo.Server, keyspace string, cells []string, allowPartial bool) (err error) {
	ctx, unlock, lockErr := ts.LockKeyspace(ctx, keyspace, "RebuildKeyspace")
	if lockErr != nil {
		return lockErr
	}
	defer unlock(&err)

	return RebuildKeyspaceLocked(ctx, log, ts, keyspace, cells, allowPartial)
}

// RebuildKeyspaceLocked should only be used with an action lock on the keyspace
// - otherwise the consistency of the serving graph data can't be
// guaranteed.
//
// Take data from the global keyspace and rebuild the local serving
// copies in each cell.
func RebuildKeyspaceLocked(ctx context.Context, log logutil.Logger, ts *topo.Server, keyspace string, cells []string, allowPartial bool) error {
	if err := topo.CheckKeyspaceLocked(ctx, keyspace); err != nil {
		return err
	}

	ki, err := ts.GetKeyspace(ctx, keyspace)
	if err != nil {
		return err
	}

	// The caller intents to update all cells in this case
	if len(cells) == 0 {
		cells, err = ts.GetCellInfoNames(ctx)
		if err != nil {
			return err
		}
	}

	shards, err := ts.FindAllShardsInKeyspace(ctx, keyspace)
	if err != nil {
		return err
	}

	// This is safe to rebuild as long there are not srvKeyspaces with tablet controls set.
	// Build the list of cells to work on: we get the union
	// of all the Cells of all the Shards, limited to the provided cells.
	//
	// srvKeyspaceMap is a map:
	//   key: cell
	//   value: topo.SrvKeyspace object being built
	srvKeyspaceMap := make(map[string]*topodatapb.SrvKeyspace)
	for _, cell := range cells {
		srvKeyspace, err := ts.GetSrvKeyspace(ctx, cell, keyspace)
		switch {
		case err == nil:
			for _, partition := range srvKeyspace.GetPartitions() {
				if partition.GetShardTabletControls() != nil {
					return fmt.Errorf("can't rebuild serving keyspace while a migration is on going. TabletControls is set for partition %v", partition)
				}

			}
		case topo.IsErrType(err, topo.NoNode):
			// NOOP
		default:
			return err
		}
		srvKeyspaceMap[cell] = &topodatapb.SrvKeyspace{
			ShardingColumnName: ki.ShardingColumnName,
			ShardingColumnType: ki.ShardingColumnType,
			ServedFrom:         ki.ComputeCellServedFrom(cell),
		}
	}

	servedTypes := []topodatapb.TabletType{topodatapb.TabletType_MASTER, topodatapb.TabletType_REPLICA, topodatapb.TabletType_RDONLY}

	// for each entry in the srvKeyspaceMap map, we do the following:
	// - get the Shard structures for each shard / cell
	// - if not present, build an empty one from global Shard
	// - sort the shards in the list by range
	// - check the ranges are compatible (no hole, covers everything)
	for cell, srvKeyspace := range srvKeyspaceMap {
		for _, si := range shards {
			// We rebuild keyspace iff:
			// 1) shard master is in a serving state.
			// 2) shard has served type for master (this is for backwards compatibility).
			if !(si.IsMasterServing || si.GetServedType(topodatapb.TabletType_MASTER) != nil) {
				continue
			}
			// for each type this shard is supposed to serve,
			// add it to srvKeyspace.Partitions
			for _, tabletType := range servedTypes {
				partition := topoproto.SrvKeyspaceGetPartition(srvKeyspace, tabletType)
				if partition == nil {
					partition = &topodatapb.SrvKeyspace_KeyspacePartition{
						ServedType: tabletType,
					}
					srvKeyspace.Partitions = append(srvKeyspace.Partitions, partition)
				}
				partition.ShardReferences = append(partition.ShardReferences, &topodatapb.ShardReference{
					Name:     si.ShardName(),
					KeyRange: si.KeyRange,
				})
			}
		}

		if !(ki.KeyspaceType == topodatapb.KeyspaceType_SNAPSHOT && allowPartial) {
			// skip this check for SNAPSHOT keyspaces so that incomplete keyspaces can still serve
			if err := topo.OrderAndCheckPartitions(cell, srvKeyspace); err != nil {
				return err
			}
		}

	}
	// And then finally save the keyspace objects, in parallel.
	rec := concurrency.AllErrorRecorder{}
	wg := sync.WaitGroup{}
	for cell, srvKeyspace := range srvKeyspaceMap {
		wg.Add(1)
		go func(cell string, srvKeyspace *topodatapb.SrvKeyspace) {
			defer wg.Done()
			if err := ts.UpdateSrvKeyspace(ctx, cell, keyspace, srvKeyspace); err != nil {
				rec.RecordError(fmt.Errorf("writing serving data failed: %v", err))
			}
		}(cell, srvKeyspace)
	}
	wg.Wait()
	return rec.Error()
}
<commit_msg>more resilient rebuild keyspace graph check, tablet controls not in effect if there has no actual disabled query service<commit_after>/*
Copyright 2019 The Vitess 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 topotools

import (
	"fmt"
	"sync"

	"context"

	"vitess.io/vitess/go/vt/concurrency"
	"vitess.io/vitess/go/vt/logutil"
	"vitess.io/vitess/go/vt/topo"
	"vitess.io/vitess/go/vt/topo/topoproto"

	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
)

// RebuildKeyspace rebuilds the serving graph data while locking out other changes.
func RebuildKeyspace(ctx context.Context, log logutil.Logger, ts *topo.Server, keyspace string, cells []string, allowPartial bool) (err error) {
	ctx, unlock, lockErr := ts.LockKeyspace(ctx, keyspace, "RebuildKeyspace")
	if lockErr != nil {
		return lockErr
	}
	defer unlock(&err)

	return RebuildKeyspaceLocked(ctx, log, ts, keyspace, cells, allowPartial)
}

// RebuildKeyspaceLocked should only be used with an action lock on the keyspace
// - otherwise the consistency of the serving graph data can't be
// guaranteed.
//
// Take data from the global keyspace and rebuild the local serving
// copies in each cell.
func RebuildKeyspaceLocked(ctx context.Context, log logutil.Logger, ts *topo.Server, keyspace string, cells []string, allowPartial bool) error {
	if err := topo.CheckKeyspaceLocked(ctx, keyspace); err != nil {
		return err
	}

	ki, err := ts.GetKeyspace(ctx, keyspace)
	if err != nil {
		return err
	}

	// The caller intents to update all cells in this case
	if len(cells) == 0 {
		cells, err = ts.GetCellInfoNames(ctx)
		if err != nil {
			return err
		}
	}

	shards, err := ts.FindAllShardsInKeyspace(ctx, keyspace)
	if err != nil {
		return err
	}

	// This is safe to rebuild as long there are not srvKeyspaces with tablet controls set.
	// Build the list of cells to work on: we get the union
	// of all the Cells of all the Shards, limited to the provided cells.
	//
	// srvKeyspaceMap is a map:
	//   key: cell
	//   value: topo.SrvKeyspace object being built
	srvKeyspaceMap := make(map[string]*topodatapb.SrvKeyspace)
	for _, cell := range cells {
		srvKeyspace, err := ts.GetSrvKeyspace(ctx, cell, keyspace)
		switch {
		case err == nil:
			for _, partition := range srvKeyspace.GetPartitions() {
				for _, shardTabletControl := range partition.GetShardTabletControls() {
					if shardTabletControl.QueryServiceDisabled {
						return fmt.Errorf("can't rebuild serving keyspace while a migration is on going. TabletControls is set for partition %v", partition)
					}
				}
			}
		case topo.IsErrType(err, topo.NoNode):
			// NOOP
		default:
			return err
		}
		srvKeyspaceMap[cell] = &topodatapb.SrvKeyspace{
			ShardingColumnName: ki.ShardingColumnName,
			ShardingColumnType: ki.ShardingColumnType,
			ServedFrom:         ki.ComputeCellServedFrom(cell),
		}
	}

	servedTypes := []topodatapb.TabletType{topodatapb.TabletType_MASTER, topodatapb.TabletType_REPLICA, topodatapb.TabletType_RDONLY}

	// for each entry in the srvKeyspaceMap map, we do the following:
	// - get the Shard structures for each shard / cell
	// - if not present, build an empty one from global Shard
	// - sort the shards in the list by range
	// - check the ranges are compatible (no hole, covers everything)
	for cell, srvKeyspace := range srvKeyspaceMap {
		for _, si := range shards {
			// We rebuild keyspace iff:
			// 1) shard master is in a serving state.
			// 2) shard has served type for master (this is for backwards compatibility).
			if !(si.IsMasterServing || si.GetServedType(topodatapb.TabletType_MASTER) != nil) {
				continue
			}
			// for each type this shard is supposed to serve,
			// add it to srvKeyspace.Partitions
			for _, tabletType := range servedTypes {
				partition := topoproto.SrvKeyspaceGetPartition(srvKeyspace, tabletType)
				if partition == nil {
					partition = &topodatapb.SrvKeyspace_KeyspacePartition{
						ServedType: tabletType,
					}
					srvKeyspace.Partitions = append(srvKeyspace.Partitions, partition)
				}
				partition.ShardReferences = append(partition.ShardReferences, &topodatapb.ShardReference{
					Name:     si.ShardName(),
					KeyRange: si.KeyRange,
				})
			}
		}

		if !(ki.KeyspaceType == topodatapb.KeyspaceType_SNAPSHOT && allowPartial) {
			// skip this check for SNAPSHOT keyspaces so that incomplete keyspaces can still serve
			if err := topo.OrderAndCheckPartitions(cell, srvKeyspace); err != nil {
				return err
			}
		}

	}
	// And then finally save the keyspace objects, in parallel.
	rec := concurrency.AllErrorRecorder{}
	wg := sync.WaitGroup{}
	for cell, srvKeyspace := range srvKeyspaceMap {
		wg.Add(1)
		go func(cell string, srvKeyspace *topodatapb.SrvKeyspace) {
			defer wg.Done()
			if err := ts.UpdateSrvKeyspace(ctx, cell, keyspace, srvKeyspace); err != nil {
				rec.RecordError(fmt.Errorf("writing serving data failed: %v", err))
			}
		}(cell, srvKeyspace)
	}
	wg.Wait()
	return rec.Error()
}
<|endoftext|>


<commit_before>package kite

import (
	"archive/tar"
	"compress/gzip"
	"errors"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"koding/newKite/kd/util"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
)

type Install struct{}

func NewInstall() *Install {
	return &Install{}
}

func (*Install) Definition() string {
	return "Install kite from Koding repository"
}

const S3URL = "http://koding-kites.s3.amazonaws.com/"

func (*Install) Exec() error {
	// Parse kite name
	flag.Parse()
	if flag.NArg() != 1 {
		return errors.New("You should give a kite name")
	}

	kiteFullName := flag.Arg(0)
	kiteName, kiteVersion, err := splitVersion(kiteFullName, true)
	if err != nil {
		kiteName, kiteVersion = kiteFullName, "latest"
	}

	// Make download request
	fmt.Println("Downloading...")
	targz, err := requestPackage(kiteName, kiteVersion)
	if err != nil {
		return err
	}
	defer targz.Close()

	// Extract gzip
	gz, err := gzip.NewReader(targz)
	if err != nil {
		return err
	}
	defer gz.Close()

	// Extract tar
	tempKitePath, err := ioutil.TempDir("", "kd-kite-install-")
	if err != nil {
		return err
	}
	defer os.RemoveAll(tempKitePath)

	err = extractTar(gz, tempKitePath)
	if err != nil {
		return err
	}

	foundName, _, bundlePath, err := validateBundle(tempKitePath)
	if err != nil {
		return err
	}

	if foundName != kiteName {
		return fmt.Errorf("Invalid package: Bundle name does not match with package name: %s != %s",
			foundName, kiteName)
	}

	err = installBundle(bundlePath)
	if err != nil {
		return err
	}

	fmt.Println("Installed successfully:", kiteFullName)
	return nil
}

// requestPackage makes a request to the kite repository and returns
// a io.ReadCloser. The caller must close the returned io.ReadCloser.
func requestPackage(kiteName, kiteVersion string) (io.ReadCloser, error) {
	kiteURL := S3URL + kiteName + "-" + kiteVersion + ".kite.tar.gz"

	res, err := http.Get(kiteURL)
	if err != nil {
		return nil, err
	}

	if res.StatusCode == 404 {
		res.Body.Close()
		return nil, errors.New("Package is not found on the server.")
	}

	if res.StatusCode != 200 {
		res.Body.Close()
		return nil, fmt.Errorf("Unexpected response from server: %d", res.StatusCode)
	}

	return res.Body, nil
}

// extractTar reads from the io.Reader and writes the files into the directory.
func extractTar(r io.Reader, dir string) error {
	tr := tar.NewReader(r)
	for {
		hdr, err := tr.Next()
		if err == io.EOF {
			// end of tar archive
			break
		}
		if err != nil {
			return err
		}

		path := filepath.Join(dir, hdr.Name)

		// TODO make the binary under /bin executable

		if hdr.FileInfo().IsDir() {
			os.MkdirAll(path, 0700)
		} else {
			f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
			if err != nil {
				return err
			}

			if _, err := io.Copy(f, tr); err != nil {
				return err
			}
		}
	}
	return nil
}

// validateBundle returns the package name, version and bundle path.
func validateBundle(tempKitePath string) (string, string, string, error) {
	dirs, err := ioutil.ReadDir(tempKitePath)
	if err != nil {
		return "", "", "", err
	}

	if len(dirs) != 1 {
		return "", "", "", errors.New("Invalid package: Package must contain only one directory.")
	}

	bundleName := dirs[0].Name() // Example: asdf-1.2.3.kite
	if !strings.HasSuffix(bundleName, ".kite") {
		return "", "", "", errors.New("Invalid package: Direcory name must end with \".kite\".")
	}

	fullName := strings.TrimSuffix(bundleName, ".kite") // Example: asdf-1.2.3
	kiteName, version, err := splitVersion(fullName, false)
	if err != nil {
		return "", "", "", errors.New("Invalid package: No version number in Kite bundle")
	}

	return kiteName, version, filepath.Join(tempKitePath, bundleName), nil
}

// installBundle moves the .kite bundle into ~/kd/kites.
func installBundle(bundlePath string) error {
	kitesPath := filepath.Join(util.GetKdPath(), "kites")
	os.MkdirAll(kitesPath, 0700)

	bundleName := filepath.Base(bundlePath)
	kitePath := filepath.Join(kitesPath, bundleName)
	return os.Rename(bundlePath, kitePath)
}

// splitVersion takes a name like "asdf-1.2.3" and
// returns the name "asdf" and version "1.2.3" seperately.
// If allowLatest is true, then the version must not be numeric and can be "latest".
func splitVersion(fullname string, allowLatest bool) (name, version string, err error) {
	notFound := errors.New("name does not contain a version number")

	parts := strings.Split(fullname, "-")
	n := len(parts)
	if n < 2 {
		return "", "", notFound
	}

	name = strings.Join(parts[:n-1], "-")
	version = parts[n-1]

	if allowLatest && version == "latest" {
		return name, version, nil
	}

	versionParts := strings.Split(version, ".")
	for _, v := range versionParts {
		if _, err := strconv.Atoi(v); err != nil {
			return "", "", notFound
		}
	}

	return name, version, nil
}
<commit_msg>rename<commit_after>package kite

import (
	"archive/tar"
	"compress/gzip"
	"errors"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"koding/newKite/kd/util"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
)

type Install struct{}

func NewInstall() *Install {
	return &Install{}
}

func (*Install) Definition() string {
	return "Install kite from Koding repository"
}

const S3URL = "http://koding-kites.s3.amazonaws.com/"

func (*Install) Exec() error {
	// Parse kite name
	flag.Parse()
	if flag.NArg() != 1 {
		return errors.New("You should give a kite name")
	}

	kiteFullName := flag.Arg(0)
	kiteName, kiteVersion, err := splitVersion(kiteFullName, true)
	if err != nil {
		kiteName, kiteVersion = kiteFullName, "latest"
	}

	// Make download request
	fmt.Println("Downloading...")
	targz, err := requestPackage(kiteName, kiteVersion)
	if err != nil {
		return err
	}
	defer targz.Close()

	// Extract gzip
	gz, err := gzip.NewReader(targz)
	if err != nil {
		return err
	}
	defer gz.Close()

	// Extract tar
	tempKitePath, err := ioutil.TempDir("", "kd-kite-install-")
	if err != nil {
		return err
	}
	defer os.RemoveAll(tempKitePath)

	err = extractTar(gz, tempKitePath)
	if err != nil {
		return err
	}

	foundName, _, bundlePath, err := validatePackage(tempKitePath)
	if err != nil {
		return err
	}

	if foundName != kiteName {
		return fmt.Errorf("Invalid package: Bundle name does not match with package name: %s != %s",
			foundName, kiteName)
	}

	err = installBundle(bundlePath)
	if err != nil {
		return err
	}

	fmt.Println("Installed successfully:", kiteFullName)
	return nil
}

// requestPackage makes a request to the kite repository and returns
// a io.ReadCloser. The caller must close the returned io.ReadCloser.
func requestPackage(kiteName, kiteVersion string) (io.ReadCloser, error) {
	kiteURL := S3URL + kiteName + "-" + kiteVersion + ".kite.tar.gz"

	res, err := http.Get(kiteURL)
	if err != nil {
		return nil, err
	}

	if res.StatusCode == 404 {
		res.Body.Close()
		return nil, errors.New("Package is not found on the server.")
	}

	if res.StatusCode != 200 {
		res.Body.Close()
		return nil, fmt.Errorf("Unexpected response from server: %d", res.StatusCode)
	}

	return res.Body, nil
}

// extractTar reads from the io.Reader and writes the files into the directory.
func extractTar(r io.Reader, dir string) error {
	tr := tar.NewReader(r)
	for {
		hdr, err := tr.Next()
		if err == io.EOF {
			// end of tar archive
			break
		}
		if err != nil {
			return err
		}

		path := filepath.Join(dir, hdr.Name)

		// TODO make the binary under /bin executable

		if hdr.FileInfo().IsDir() {
			os.MkdirAll(path, 0700)
		} else {
			f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
			if err != nil {
				return err
			}

			if _, err := io.Copy(f, tr); err != nil {
				return err
			}
		}
	}
	return nil
}

// validatePackage returns the package name, version and bundle path.
func validatePackage(tempKitePath string) (string, string, string, error) {
	dirs, err := ioutil.ReadDir(tempKitePath)
	if err != nil {
		return "", "", "", err
	}

	if len(dirs) != 1 {
		return "", "", "", errors.New("Invalid package: Package must contain only one directory.")
	}

	bundleName := dirs[0].Name() // Example: asdf-1.2.3.kite
	if !strings.HasSuffix(bundleName, ".kite") {
		return "", "", "", errors.New("Invalid package: Direcory name must end with \".kite\".")
	}

	fullName := strings.TrimSuffix(bundleName, ".kite") // Example: asdf-1.2.3
	kiteName, version, err := splitVersion(fullName, false)
	if err != nil {
		return "", "", "", errors.New("Invalid package: No version number in Kite bundle")
	}

	return kiteName, version, filepath.Join(tempKitePath, bundleName), nil
}

// installBundle moves the .kite bundle into ~/kd/kites.
func installBundle(bundlePath string) error {
	kitesPath := filepath.Join(util.GetKdPath(), "kites")
	os.MkdirAll(kitesPath, 0700)

	bundleName := filepath.Base(bundlePath)
	kitePath := filepath.Join(kitesPath, bundleName)
	return os.Rename(bundlePath, kitePath)
}

// splitVersion takes a name like "asdf-1.2.3" and
// returns the name "asdf" and version "1.2.3" seperately.
// If allowLatest is true, then the version must not be numeric and can be "latest".
func splitVersion(fullname string, allowLatest bool) (name, version string, err error) {
	notFound := errors.New("name does not contain a version number")

	parts := strings.Split(fullname, "-")
	n := len(parts)
	if n < 2 {
		return "", "", notFound
	}

	name = strings.Join(parts[:n-1], "-")
	version = parts[n-1]

	if allowLatest && version == "latest" {
		return name, version, nil
	}

	versionParts := strings.Split(version, ".")
	for _, v := range versionParts {
		if _, err := strconv.Atoi(v); err != nil {
			return "", "", notFound
		}
	}

	return name, version, nil
}
<|endoftext|>


<commit_before>// Copyright 2017-2019 Authors of Cilium
//
// 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 launch

import (
	"context"
	"fmt"
	"net"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"github.com/cilium/cilium/api/v1/models"
	"github.com/cilium/cilium/pkg/datapath/linux/route"
	"github.com/cilium/cilium/pkg/defaults"
	"github.com/cilium/cilium/pkg/endpoint"
	"github.com/cilium/cilium/pkg/endpoint/connector"
	"github.com/cilium/cilium/pkg/endpointmanager"
	healthPkg "github.com/cilium/cilium/pkg/health/client"
	healthDefaults "github.com/cilium/cilium/pkg/health/defaults"
	"github.com/cilium/cilium/pkg/labels"
	"github.com/cilium/cilium/pkg/launcher"
	"github.com/cilium/cilium/pkg/logging/logfields"
	"github.com/cilium/cilium/pkg/metrics"
	"github.com/cilium/cilium/pkg/mtu"
	"github.com/cilium/cilium/pkg/netns"
	"github.com/cilium/cilium/pkg/node"
	"github.com/cilium/cilium/pkg/option"
	"github.com/cilium/cilium/pkg/pidfile"

	"github.com/vishvananda/netlink"
	"golang.org/x/sys/unix"
)

const (
	ciliumHealth = "cilium-health"
	netNSName    = "cilium-health"
)

var (
	// vethName is the host-side veth link device name for cilium-health EP
	// (veth mode only).
	vethName = "cilium_health"

	// epIfaceName is the endpoint-side link device name for cilium-health.
	epIfaceName = "cilium"

	// PidfilePath
	PidfilePath = "health-endpoint.pid"
)

func configureHealthRouting(netns, dev string, addressing *models.NodeAddressing, mtuConfig mtu.Configuration) error {
	routes := []route.Route{}

	if option.Config.EnableIPv4 {
		v4Routes, err := connector.IPv4Routes(addressing, mtuConfig.GetRouteMTU())
		if err == nil {
			routes = append(routes, v4Routes...)
		} else {
			log.Debugf("Couldn't get IPv4 routes for health routing")
		}
	}

	if option.Config.EnableIPv6 {
		v6Routes, err := connector.IPv6Routes(addressing, mtuConfig.GetRouteMTU())
		if err != nil {
			return fmt.Errorf("Failed to get IPv6 routes")
		}
		routes = append(routes, v6Routes...)
	}

	prog := "ip"
	args := []string{"netns", "exec", netns, "bash", "-c"}
	routeCmds := []string{}
	for _, rt := range routes {
		cmd := strings.Join(rt.ToIPCommand(dev), " ")
		log.WithField("netns", netns).WithField("command", cmd).Debug("Adding route")
		routeCmds = append(routeCmds, cmd)
	}
	cmd := strings.Join(routeCmds, " && ")
	args = append(args, cmd)

	log.Debugf("Running \"%s %+v\"", prog, args)
	out, err := exec.Command(prog, args...).CombinedOutput()
	if err == nil && len(out) > 0 {
		log.Warn(out)
	}

	return err
}

// Client wraps a client to a specific cilium-health endpoint instance, to
// provide convenience methods such as PingEndpoint().
type Client struct {
	*healthPkg.Client
}

// PingEndpoint attempts to make an API ping request to the local cilium-health
// endpoint, and returns whether this was successful.
func (c *Client) PingEndpoint() error {
	_, err := c.Restapi.GetHello(nil)
	return err
}

// KillEndpoint attempts to kill any existing cilium-health endpoint if it
// exists.
//
// This is intended to be invoked in multiple situations:
// * The health endpoint has never been run before
// * The health endpoint was run during a previous run of the Cilium agent
// * The health endpoint crashed during the current run of the Cilium agent
//   and needs to be cleaned up before it is restarted.
func KillEndpoint() {
	path := filepath.Join(option.Config.StateDir, PidfilePath)
	if err := pidfile.Kill(path); err != nil {
		log.WithField(logfields.Path, path).WithError(err).
			Warning("Failed to kill cilium-health instance")
	}
}

// CleanupEndpoint cleans up remaining resources associated with the health
// endpoint.
//
// This is expected to be called after the process is killed and the endpoint
// is removed from the endpointmanager.
func CleanupEndpoint() {
	// In the case of ipvlan, the ipvlan slave device is removed by removal
	// of the endpoint netns in "cleanup" of spawn_netns.sh
	if option.Config.DatapathMode == option.DatapathModeVeth {
		scopedLog := log.WithField(logfields.Veth, vethName)
		if link, err := netlink.LinkByName(vethName); err == nil {
			err = netlink.LinkDel(link)
			if err != nil {
				scopedLog.WithError(err).Warning("Couldn't delete cilium-health device")
			}
		} else {
			scopedLog.WithError(err).Debug("Didn't find existing device")
		}
	}
}

// LaunchAsEndpoint launches the cilium-health agent in a nested network
// namespace and attaches it to Cilium the same way as any other endpoint,
// but with special reserved labels.
//
// CleanupEndpoint() must be called before calling LaunchAsEndpoint() to ensure
// cleanup of prior cilium-health endpoint instances.
func LaunchAsEndpoint(owner endpoint.Owner, n *node.Node, mtuConfig mtu.Configuration) (*Client, error) {
	var (
		cmd  = launcher.Launcher{}
		info = &models.EndpointChangeRequest{
			ContainerName: ciliumHealth,
			State:         models.EndpointStateWaitingForIdentity,
			Addressing:    &models.AddressPair{},
		}
		ip4Address, ip6Address string
		healthIP               net.IP
		hostIfaceName          string
	)

	if n.IPv4HealthIP != nil {
		healthIP = n.IPv4HealthIP
		info.Addressing.IPV4 = healthIP.String()
		ip4WithMask := net.IPNet{IP: healthIP, Mask: defaults.ContainerIPv4Mask}
		ip4Address = ip4WithMask.String()
	}

	if n.IPv6HealthIP != nil {
		healthIP = n.IPv6HealthIP
		info.Addressing.IPV6 = healthIP.String()
		ip6WithMask := net.IPNet{IP: healthIP, Mask: defaults.ContainerIPv6Mask}
		ip6Address = ip6WithMask.String()
	}

	switch option.Config.DatapathMode {
	case option.DatapathModeVeth:
		if _, _, err := connector.SetupVethWithNames(vethName, epIfaceName, mtuConfig.GetDeviceMTU(), info); err != nil {
			return nil, fmt.Errorf("Error while creating veth: %s", err)
		}
		hostIfaceName = vethName

	case option.DatapathModeIpvlan:
		netNS, err := netns.ReplaceNetNSWithName(netNSName)
		if err != nil {
			return nil, err
		}

		mapFD, err := connector.CreateAndSetupIpvlanSlave("",
			epIfaceName, netNS, mtuConfig.GetDeviceMTU(),
			option.Config.Ipvlan.MasterDeviceIndex,
			option.Config.Ipvlan.OperationMode, info)
		if err != nil {
			if errDel := netns.RemoveNetNSWithName(netNSName); errDel != nil {
				log.WithError(errDel).WithField(logfields.NetNSName, netNSName).
					Warning("Unable to remove network namespace")
			}
			return nil, err
		}
		defer unix.Close(mapFD)

		hostIfaceName = option.Config.Device
	}

	pidfile := filepath.Join(option.Config.StateDir, PidfilePath)
	healthArgs := fmt.Sprintf("-d --admin=unix --passive --pidfile %s", pidfile)
	args := []string{info.ContainerName, hostIfaceName, epIfaceName,
		ip6Address, ip4Address, ciliumHealth, healthArgs}
	if option.Config.DatapathMode == option.DatapathModeIpvlan {
		// Do not initialize netns (i.e. create and move the slave to it), as
		// otherwise qdisc of the slave will get reset
		args = append(args, "--skip-netns-init")
	}
	prog := filepath.Join(option.Config.BpfDir, "spawn_netns.sh")
	cmd.SetTarget(prog)
	cmd.SetArgs(args)
	log.Infof("Spawning health endpoint with arguments %#v", args)
	if err := cmd.Run(); err != nil {
		return nil, err
	}

	// Create the endpoint
	ep, err := endpoint.NewEndpointFromChangeModel(info)
	if err != nil {
		return nil, fmt.Errorf("Error while creating endpoint model: %s", err)
	}

	// Give the endpoint a security identity
	ep.UpdateLabels(context.Background(), owner, labels.LabelHealth, nil, true)

	// Wait until the cilium-health endpoint is running before setting up routes
	deadline := time.Now().Add(1 * time.Minute)
	for {
		if _, err := os.Stat(pidfile); err == nil {
			log.WithField("pidfile", pidfile).Debug("cilium-health agent running")
			break
		} else if time.Now().After(deadline) {
			return nil, fmt.Errorf("Endpoint failed to run: %s", err)
		} else {
			time.Sleep(1 * time.Second)
		}
	}

	// Set up the endpoint routes
	hostAddressing := node.GetNodeAddressing()
	if err = configureHealthRouting(info.ContainerName, epIfaceName, hostAddressing, mtuConfig); err != nil {
		return nil, fmt.Errorf("Error while configuring routes: %s", err)
	}

	if err := endpointmanager.AddEndpoint(owner, ep, "Create cilium-health endpoint"); err != nil {
		return nil, fmt.Errorf("Error while adding endpoint: %s", err)
	}

	if err := ep.LockAlive(); err != nil {
		return nil, err
	}
	if !ep.SetStateLocked(endpoint.StateWaitingToRegenerate, "initial build of health endpoint") {
		endpointmanager.Remove(ep, owner)
		ep.Unlock()
		return nil, fmt.Errorf("unable to transition health endpoint to WaitingToRegenerate state")
	}
	ep.PinDatapathMap()
	ep.Unlock()

	buildSuccessful := <-ep.Regenerate(owner, &endpoint.ExternalRegenerationMetadata{
		Reason: "health daemon bootstrap",
	})
	if !buildSuccessful {
		endpointmanager.Remove(ep, owner)
		return nil, fmt.Errorf("unable to build health endpoint")
	}

	// Initialize the health client to talk to this instance. This is why
	// the caller must limit usage of this package to a single goroutine.
	client, err := healthPkg.NewClient("tcp://" + net.JoinHostPort(healthIP.String(), fmt.Sprintf("%d", healthDefaults.HTTPPathPort)))
	if err != nil {
		return nil, fmt.Errorf("Cannot establish connection to health endpoint: %s", err)
	}
	metrics.SubprocessStart.WithLabelValues(ciliumHealth).Inc()

	return &Client{Client: client}, nil
}
<commit_msg>cilium-health: Rebuild health-ep via identity set<commit_after>// Copyright 2017-2019 Authors of Cilium
//
// 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 launch

import (
	"context"
	"fmt"
	"net"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"github.com/cilium/cilium/api/v1/models"
	"github.com/cilium/cilium/pkg/datapath/linux/route"
	"github.com/cilium/cilium/pkg/defaults"
	"github.com/cilium/cilium/pkg/endpoint"
	"github.com/cilium/cilium/pkg/endpoint/connector"
	"github.com/cilium/cilium/pkg/endpointmanager"
	healthPkg "github.com/cilium/cilium/pkg/health/client"
	healthDefaults "github.com/cilium/cilium/pkg/health/defaults"
	"github.com/cilium/cilium/pkg/labels"
	"github.com/cilium/cilium/pkg/launcher"
	"github.com/cilium/cilium/pkg/logging/logfields"
	"github.com/cilium/cilium/pkg/metrics"
	"github.com/cilium/cilium/pkg/mtu"
	"github.com/cilium/cilium/pkg/netns"
	"github.com/cilium/cilium/pkg/node"
	"github.com/cilium/cilium/pkg/option"
	"github.com/cilium/cilium/pkg/pidfile"

	"github.com/vishvananda/netlink"
	"golang.org/x/sys/unix"
)

const (
	ciliumHealth = "cilium-health"
	netNSName    = "cilium-health"
)

var (
	// vethName is the host-side veth link device name for cilium-health EP
	// (veth mode only).
	vethName = "cilium_health"

	// epIfaceName is the endpoint-side link device name for cilium-health.
	epIfaceName = "cilium"

	// PidfilePath
	PidfilePath = "health-endpoint.pid"
)

func configureHealthRouting(netns, dev string, addressing *models.NodeAddressing, mtuConfig mtu.Configuration) error {
	routes := []route.Route{}

	if option.Config.EnableIPv4 {
		v4Routes, err := connector.IPv4Routes(addressing, mtuConfig.GetRouteMTU())
		if err == nil {
			routes = append(routes, v4Routes...)
		} else {
			log.Debugf("Couldn't get IPv4 routes for health routing")
		}
	}

	if option.Config.EnableIPv6 {
		v6Routes, err := connector.IPv6Routes(addressing, mtuConfig.GetRouteMTU())
		if err != nil {
			return fmt.Errorf("Failed to get IPv6 routes")
		}
		routes = append(routes, v6Routes...)
	}

	prog := "ip"
	args := []string{"netns", "exec", netns, "bash", "-c"}
	routeCmds := []string{}
	for _, rt := range routes {
		cmd := strings.Join(rt.ToIPCommand(dev), " ")
		log.WithField("netns", netns).WithField("command", cmd).Debug("Adding route")
		routeCmds = append(routeCmds, cmd)
	}
	cmd := strings.Join(routeCmds, " && ")
	args = append(args, cmd)

	log.Debugf("Running \"%s %+v\"", prog, args)
	out, err := exec.Command(prog, args...).CombinedOutput()
	if err == nil && len(out) > 0 {
		log.Warn(out)
	}

	return err
}

// Client wraps a client to a specific cilium-health endpoint instance, to
// provide convenience methods such as PingEndpoint().
type Client struct {
	*healthPkg.Client
}

// PingEndpoint attempts to make an API ping request to the local cilium-health
// endpoint, and returns whether this was successful.
func (c *Client) PingEndpoint() error {
	_, err := c.Restapi.GetHello(nil)
	return err
}

// KillEndpoint attempts to kill any existing cilium-health endpoint if it
// exists.
//
// This is intended to be invoked in multiple situations:
// * The health endpoint has never been run before
// * The health endpoint was run during a previous run of the Cilium agent
// * The health endpoint crashed during the current run of the Cilium agent
//   and needs to be cleaned up before it is restarted.
func KillEndpoint() {
	path := filepath.Join(option.Config.StateDir, PidfilePath)
	if err := pidfile.Kill(path); err != nil {
		log.WithField(logfields.Path, path).WithError(err).
			Warning("Failed to kill cilium-health instance")
	}
}

// CleanupEndpoint cleans up remaining resources associated with the health
// endpoint.
//
// This is expected to be called after the process is killed and the endpoint
// is removed from the endpointmanager.
func CleanupEndpoint() {
	// In the case of ipvlan, the ipvlan slave device is removed by removal
	// of the endpoint netns in "cleanup" of spawn_netns.sh
	if option.Config.DatapathMode == option.DatapathModeVeth {
		scopedLog := log.WithField(logfields.Veth, vethName)
		if link, err := netlink.LinkByName(vethName); err == nil {
			err = netlink.LinkDel(link)
			if err != nil {
				scopedLog.WithError(err).Warning("Couldn't delete cilium-health device")
			}
		} else {
			scopedLog.WithError(err).Debug("Didn't find existing device")
		}
	}
}

// LaunchAsEndpoint launches the cilium-health agent in a nested network
// namespace and attaches it to Cilium the same way as any other endpoint,
// but with special reserved labels.
//
// CleanupEndpoint() must be called before calling LaunchAsEndpoint() to ensure
// cleanup of prior cilium-health endpoint instances.
func LaunchAsEndpoint(owner endpoint.Owner, n *node.Node, mtuConfig mtu.Configuration) (*Client, error) {
	var (
		cmd  = launcher.Launcher{}
		info = &models.EndpointChangeRequest{
			ContainerName: ciliumHealth,
			State:         models.EndpointStateWaitingForIdentity,
			Addressing:    &models.AddressPair{},
		}
		ip4Address, ip6Address string
		healthIP               net.IP
		hostIfaceName          string
	)

	if n.IPv4HealthIP != nil {
		healthIP = n.IPv4HealthIP
		info.Addressing.IPV4 = healthIP.String()
		ip4WithMask := net.IPNet{IP: healthIP, Mask: defaults.ContainerIPv4Mask}
		ip4Address = ip4WithMask.String()
	}

	if n.IPv6HealthIP != nil {
		healthIP = n.IPv6HealthIP
		info.Addressing.IPV6 = healthIP.String()
		ip6WithMask := net.IPNet{IP: healthIP, Mask: defaults.ContainerIPv6Mask}
		ip6Address = ip6WithMask.String()
	}

	switch option.Config.DatapathMode {
	case option.DatapathModeVeth:
		if _, _, err := connector.SetupVethWithNames(vethName, epIfaceName, mtuConfig.GetDeviceMTU(), info); err != nil {
			return nil, fmt.Errorf("Error while creating veth: %s", err)
		}
		hostIfaceName = vethName

	case option.DatapathModeIpvlan:
		netNS, err := netns.ReplaceNetNSWithName(netNSName)
		if err != nil {
			return nil, err
		}

		mapFD, err := connector.CreateAndSetupIpvlanSlave("",
			epIfaceName, netNS, mtuConfig.GetDeviceMTU(),
			option.Config.Ipvlan.MasterDeviceIndex,
			option.Config.Ipvlan.OperationMode, info)
		if err != nil {
			if errDel := netns.RemoveNetNSWithName(netNSName); errDel != nil {
				log.WithError(errDel).WithField(logfields.NetNSName, netNSName).
					Warning("Unable to remove network namespace")
			}
			return nil, err
		}
		defer unix.Close(mapFD)

		hostIfaceName = option.Config.Device
	}

	pidfile := filepath.Join(option.Config.StateDir, PidfilePath)
	healthArgs := fmt.Sprintf("-d --admin=unix --passive --pidfile %s", pidfile)
	args := []string{info.ContainerName, hostIfaceName, epIfaceName,
		ip6Address, ip4Address, ciliumHealth, healthArgs}
	if option.Config.DatapathMode == option.DatapathModeIpvlan {
		// Do not initialize netns (i.e. create and move the slave to it), as
		// otherwise qdisc of the slave will get reset
		args = append(args, "--skip-netns-init")
	}
	prog := filepath.Join(option.Config.BpfDir, "spawn_netns.sh")
	cmd.SetTarget(prog)
	cmd.SetArgs(args)
	log.Infof("Spawning health endpoint with arguments %#v", args)
	if err := cmd.Run(); err != nil {
		return nil, err
	}

	// Create the endpoint
	ep, err := endpoint.NewEndpointFromChangeModel(info)
	if err != nil {
		return nil, fmt.Errorf("Error while creating endpoint model: %s", err)
	}

	// Wait until the cilium-health endpoint is running before setting up routes
	deadline := time.Now().Add(1 * time.Minute)
	for {
		if _, err := os.Stat(pidfile); err == nil {
			log.WithField("pidfile", pidfile).Debug("cilium-health agent running")
			break
		} else if time.Now().After(deadline) {
			return nil, fmt.Errorf("Endpoint failed to run: %s", err)
		} else {
			time.Sleep(1 * time.Second)
		}
	}

	// Set up the endpoint routes
	hostAddressing := node.GetNodeAddressing()
	if err = configureHealthRouting(info.ContainerName, epIfaceName, hostAddressing, mtuConfig); err != nil {
		return nil, fmt.Errorf("Error while configuring routes: %s", err)
	}

	if err := endpointmanager.AddEndpoint(owner, ep, "Create cilium-health endpoint"); err != nil {
		return nil, fmt.Errorf("Error while adding endpoint: %s", err)
	}

	if err := ep.LockAlive(); err != nil {
		return nil, err
	}
	ep.PinDatapathMap()
	ep.Unlock()

	// Give the endpoint a security identity
	ep.UpdateLabels(context.Background(), owner, labels.LabelHealth, nil, true)

	// Initialize the health client to talk to this instance. This is why
	// the caller must limit usage of this package to a single goroutine.
	client, err := healthPkg.NewClient("tcp://" + net.JoinHostPort(healthIP.String(), fmt.Sprintf("%d", healthDefaults.HTTPPathPort)))
	if err != nil {
		return nil, fmt.Errorf("Cannot establish connection to health endpoint: %s", err)
	}
	metrics.SubprocessStart.WithLabelValues(ciliumHealth).Inc()

	return &Client{Client: client}, nil
}
<|endoftext|>