<commit_before>/*
This package is designed to support better logging for Go. Specifically, this
project aims to support different levels of logging and the ability to
customize log output via custom implementations of the interfaces provided in
the package. In addition, all logged messages are wrapped in closures and are
only evaluated and rendered if they will be outputed.

The easiest way to start using this package is to use the Global
PackageLogger and the exported global namespace wrapper functions. For
example:

	package mypackage

	import "github.com/awreece/golog"

	func Foo() {
		golog.Info("Hello, world")
		golog.Warningf("Error %d", 4)
		golog.Errorc(func() { return verySlowStringFunction() })
		golog.Fatal("Error opening file:", err)
	}

The Global PackageLogger output to default files set by flags. For example,
to log to stderr and to temp.log, invoke the binary with the additional
flags --golog.logfile=/dev/stderr --golog.logfile=temp.log.

This package also makes it easy to log to a testing harness in addition to
files. To do this, invoke StartTestLogging(t) at the start of every test
and StopTestLogging() at the end. For example:

	package mypackage

	import (
		"github.com/awreece/golog"
		"testing"
	)

	func TestFoo(t *testing.T) {
		golog.StartTestLogging(t); defer golog.StopTestLogging()

		// Test the Foo() function.
		Foo()
	}

While in test logging mode, calls to Fatal() (and DefaultLogger.FailNow())
will call testing.(*T).FailNow() rather than
exiting the program abruptly.

Another common way to use this pacakge is to create a local PackageLogger.
This can either be declared on the package level or passed in by value.

Advanced usage
This package is highly modular and configurable; different components can be
plugged in to modify the behavior. For example, to speed up logging an advanced
user could try creating a LocationLogger using the NoLocation function, or
even create a custom location function.

Advanced users can further take advantage of the modularity of the package to
implement and control individual parts. For example, logging in XML format
should be done by writing a proper LogOuter.

This package was designed to be highly modular, with different interfaces for
each logical component. The important types are:

-	A LogMessage is a logged message with associated metadata.

-	A LogOuter controls outputing a LogMessage.

-	A MultiLogOuter multiplexes an outputted message to a set of keyed
LogOuters. The associated MultiLogOuterFlag automatically add
logfiles to the associated set of LogOuters.

-	A Logger decides whether or not to log a message, and if so renders
the message and outputs it.

-	A LocationLogger is a wrapper for a Logger that generates a closure
to return a LogMessage with the associate metadata and is the first
easily usable entrypoint into this package.

-	A PackageLogger has a set of functions designed be quickly useful
and is the expected entry point into this package.
*/
package golog
<commit_msg>Add TODO<commit_after>/*
This package is designed to support better logging for Go. Specifically, this
project aims to support different levels of logging and the ability to
customize log output via custom implementations of the interfaces provided in
the package. In addition, all logged messages are wrapped in closures and are
only evaluated and rendered if they will be outputed.

The easiest way to start using this package is to use the Global
PackageLogger and the exported global namespace wrapper functions. For
example:

	package mypackage

	import "github.com/awreece/golog"

	func Foo() {
		golog.Info("Hello, world")
		golog.Warningf("Error %d", 4)
		golog.Errorc(func() { return verySlowStringFunction() })
		golog.Fatal("Error opening file:", err)
	}

The Global PackageLogger output to default files set by flags. For example,
to log to stderr and to temp.log, invoke the binary with the additional
flags --golog.logfile=/dev/stderr --golog.logfile=temp.log.

This package also makes it easy to log to a testing harness in addition to
files. To do this, invoke StartTestLogging(t) at the start of every test
and StopTestLogging() at the end. For example:

	package mypackage

	import (
		"github.com/awreece/golog"
		"testing"
	)

	func TestFoo(t *testing.T) {
		golog.StartTestLogging(t); defer golog.StopTestLogging()

		// Test the Foo() function.
		Foo()
	}

While in test logging mode, calls to Fatal() (and DefaultLogger.FailNow())
will call testing.(*T).FailNow() rather than
exiting the program abruptly.

Another common way to use this pacakge is to create a local PackageLogger.
This can either be declared on the package level or passed in by value.

Advanced usage
This package is highly modular and configurable; different components can be
plugged in to modify the behavior. For example, to speed up logging an advanced
user could try creating a LocationLogger using the NoLocation function, or
even create a custom location function.

Advanced users can further take advantage of the modularity of the package to
implement and control individual parts. For example, logging in XML format
should be done by writing a proper LogOuter.

This package was designed to be highly modular, with different interfaces for
each logical component. The important types are:

-	A LogMessage is a logged message with associated metadata.

-	A LogOuter controls outputing a LogMessage.

-	A MultiLogOuter multiplexes an outputted message to a set of keyed
LogOuters. The associated MultiLogOuterFlag automatically add
logfiles to the associated set of LogOuters.

-	A Logger decides whether or not to log a message, and if so renders
the message and outputs it.

-	A LocationLogger is a wrapper for a Logger that generates a closure
to return a LogMessage with the associate metadata and is the first
easily usable entrypoint into this package.

-	A PackageLogger has a set of functions designed be quickly useful
and is the expected entry point into this package.
*/
package golog

// TODO(awreece) Can README.md or doc.go cite the other?
<|endoftext|>


<commit_before>// Simple webserver
//
// A small webserver for testing various technologies, techniques and concepts.
//
// This project enables you to learn new technologies by try them in
// a small and simple environment to get a first idea how things are working.
//
// This part is responsible to show the standard way of documentation
// of golang. With the idea to inline the documentation, code and docs are one.
// This minimize the risk that documentation get out of date quickly.
//
// Notice that this is only a a small and not production ready demo.
// If you want to deep dive into the topic of golang + documentation
// i suggest to checkout:
//
// https://blog.golang.org/godoc-documenting-go-code
//
// https://godoc.org/golang.org/x/tools/cmd/godoc
//
// https://github.com/fluhus/godoc-tricks
//
// https://github.com/golang/gddo
//
// If you have any suggestion or comment, please feel free to open an issue on
// this the GitHub page of this project!
//
// More information and details can be found there as well.
// Checkout https://github.com/andygrunwald/simple-webserver
//
package main
<commit_msg>Removed "simple-webserver" headline from godoc<commit_after>// A small webserver for testing various technologies, techniques and concepts.
//
// This project enables you to learn new technologies by try them in
// a small and simple environment to get a first idea how things are working.
//
// This part is responsible to show the standard way of documentation
// of golang. With the idea to inline the documentation, code and docs are one.
// This minimize the risk that documentation get out of date quickly.
//
// Notice that this is only a a small and not production ready demo.
// If you want to deep dive into the topic of golang + documentation
// i suggest to checkout:
//
// https://blog.golang.org/godoc-documenting-go-code
//
// https://godoc.org/golang.org/x/tools/cmd/godoc
//
// https://github.com/fluhus/godoc-tricks
//
// https://github.com/golang/gddo
//
// If you have any suggestion or comment, please feel free to open an issue on
// this the GitHub page of this project!
//
// More information and details can be found there as well.
// Checkout https://github.com/andygrunwald/simple-webserver
//
package main
<|endoftext|>


<commit_before>// The gopaths server responds to partial path requests with full package
// import or directory paths, depending on the request type.
//
// Usage: gopaths [-http [HOST]:PORT] [-root DIRS] [-exclude FILE]
//
//   -http=":6118"
// 	Listen on HOST on PORT.
//
//   -root=""
//      Directories to look for Go packages in, separated by ‘:’ in Unix
//      and ‘;’ in Windows. By default, the packages are looked for
//      in GOROOT and GOPATH.
//
//   -exclude=""
//      FILE containing a list of whitespace separated directory names
//      in which gopaths won't be looking into when searching for packages.
//
//
// Paths are matched against the base path (deepest sitting directory):
//
// If there are many matches, all matches are returned; each on a separate
// line. If there are no package matches, paths leading to the base path
// are returned; again, if there are any.
//
// For example, if the requested path is “io”, this path is matched:
//
//   io
//
// but these are not:
//
//   bufio
//   testing/iotest
//   cmd/internal/rsc.io/x86/x86asm
//
// On the other hand, if the requested path is “go.net”, and there are no
// indexed packages with “go.net” at the end, this path is returned:
//
//   code.google.com/p/go.net
//
// It's a parent path to many other packages.
//
//
// The paths are queried using a Web browser, preferably console one:
// curl(1) or wget(1), because gopaths is a CLI server.
//
// There are three request types, specified by path prefixes:
//
//   GET /dirs/{PATH}
//     Return directory paths matching PATH.
//
//   GET /imports/{PATH}
//     Return import paths matching PATH.
//
//   GET /update
//     Update the directory index. The directory index updates itself
//     every 45 minutes. Occasionally, a faster update might be needed.
//
// Examples:
//
//   $ curl :6118/imports/log
//   log
//   google.golang.org/appengine/internal/log
//   google.golang.org/appengine/log
//
//   $ curl :6118/dirs/rand
//   /Users/peter/go/src/crypto/rand
//   /Users/peter/go/src/math/rand
//
package main
<commit_msg>Wording<commit_after>// The gopaths server responds to partial path requests with full package
// import or directory paths, depending on the request type.
//
// Usage: gopaths [-http [HOST]:PORT] [-root DIRS] [-exclude FILE]
//
//   -http=":6118"
// 	Listen on HOST on PORT.
//
//   -root=""
//      Directories to look for Go packages in, separated by ‘:’ in Unix
//      and ‘;’ in Windows. By default, the packages are looked for
//      in GOROOT and GOPATH.
//
//   -exclude=""
//      FILE containing a list of whitespace separated directory names
//      in which gopaths won't be looking into when searching for packages.
//
//
// Paths are matched against the base path (deepest sitting directory):
//
// If there are many matches, all matches are returned; each on a separate
// line. If there are no package matches, paths leading to the base path
// are returned; again, if there are any.
//
// For example, if the requested path is “io”, this path will be matched:
//
//   io
//
// but these will be not:
//
//   bufio
//   testing/iotest
//   cmd/internal/rsc.io/x86/x86asm
//
// On the other hand, if the requested path is “go.net”, and there are no
// indexed packages with “go.net” at the end, this path will be returned:
//
//   code.google.com/p/go.net
//
// It's a parent path to many other packages.
//
//
// The paths are queried using a Web browser, preferably a console one like
// curl(1) or wget(1), because gopaths is intended to be a CLI server.
//
// There are three request types, specified by path prefixes:
//
//   GET /dirs/{PATH}
//     Return directory paths matching PATH.
//
//   GET /imports/{PATH}
//     Return import paths matching PATH.
//
//   GET /update
//     Update the directory index. The directory index updates itself
//     every 45 minutes. Occasionally, a faster update might be needed.
//
// Examples:
//
//   $ curl :6118/imports/log
//   log
//   google.golang.org/appengine/internal/log
//   google.golang.org/appengine/log
//
//   $ curl :6118/dirs/rand
//   /Users/peter/go/src/crypto/rand
//   /Users/peter/go/src/math/rand
//
package main
<|endoftext|>


<commit_before>// Copyright Istio 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 controller

import (
	"fmt"
	"net"
	"sort"
	"strings"

	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	klabels "k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/client-go/tools/cache"

	"istio.io/istio/pilot/pkg/features"
	"istio.io/istio/pilot/pkg/model"
	"istio.io/istio/pilot/pkg/networking/util"
	"istio.io/istio/pilot/pkg/serviceregistry/kube"
	"istio.io/istio/pkg/cluster"
	"istio.io/istio/pkg/config/constants"
	"istio.io/istio/pkg/config/host"
	"istio.io/istio/pkg/config/schema/gvk"
	"istio.io/istio/pkg/kube/mcs"
)

const (
	mcsDomainSuffix = "." + constants.DefaultClusterSetLocalDomain
)

type importedService struct {
	namespacedName types.NamespacedName
	clusterSetVIP  string
}

// serviceImportCache reads and processes Kubernetes Multi-Cluster Services (MCS) ServiceImport
// resources.
//
// An MCS controller is responsible for reading ServiceExport resources in one cluster and generating
// ServiceImport in all clusters of the ClusterSet (i.e. mesh). While the serviceExportCache reads
// ServiceExport to control the discoverability policy for individual endpoints, this controller
// reads ServiceImport in the cluster in order to extract the ClusterSet VIP and generate a
// synthetic service for the MCS host (i.e. clusterset.local). The aggregate.Controller will then
// merge together the MCS services from all the clusters, filling out the full map of Cluster IPs.
//
// The synthetic MCS service is a copy of the real k8s Service (e.g. cluster.local) with the same
// namespaced name, but with the hostname and VIPs changed to the appropriate ClusterSet values.
// The real k8s Service can live anywhere in the mesh and does not have to reside in the same
// cluster as the ServiceImport.
type serviceImportCache interface {
	HasSynced() bool
	ImportedServices() []importedService
}

// newServiceImportCache creates a new cache of ServiceImport resources in the cluster.
func newServiceImportCache(c *Controller) serviceImportCache {
	if features.EnableMCSHost {
		dInformer := c.client.DynamicInformer().ForResource(mcs.ServiceImportGVR)
		sic := &serviceImportCacheImpl{
			Controller: c,
			informer:   dInformer.Informer(),
			lister:     dInformer.Lister(),
		}

		// Register callbacks for Service events anywhere in the mesh.
		c.opts.MeshServiceController.AppendServiceHandlerForCluster(c.Cluster(), sic.onServiceEvent)

		// Register callbacks for ServiceImport events in this cluster only.
		c.registerHandlers(sic.informer, "ServiceImports", sic.onServiceImportEvent, nil)
		return sic
	}

	// MCS Service discovery is disabled. Use a placeholder cache.
	return disabledServiceImportCache{}
}

// serviceImportCacheImpl reads ServiceImport resources for a single cluster.
type serviceImportCacheImpl struct {
	*Controller
	informer cache.SharedIndexInformer
	lister   cache.GenericLister
}

// onServiceEvent is called when the controller receives an event for the kube Service (i.e. cluster.local).
// When this happens, we need to update the state of the associated synthetic MCS service.
func (ic *serviceImportCacheImpl) onServiceEvent(svc *model.Service, event model.Event) {
	if strings.HasSuffix(svc.Hostname.String(), mcsDomainSuffix) {
		// Ignore events for MCS services that were triggered by this controller.
		return
	}

	namespacedName := namespacedNameForService(svc)

	// Lookup the previous MCS service if there was one.
	mcsHost := serviceClusterSetLocalHostname(namespacedName)
	prevMcsService := ic.GetService(mcsHost)

	// Get the ClusterSet VIPs for this service in this cluster. Will only be populated if the
	// service has a ServiceImport in this cluster.
	vips := ic.getClusterSetIPs(namespacedName)
	name := namespacedName.Name
	ns := namespacedName.Namespace

	if len(vips) == 0 || (event == model.EventDelete &&
		ic.opts.MeshServiceController.GetService(kube.ServiceHostname(name, ns, ic.opts.DomainSuffix)) == nil) {
		if prevMcsService != nil {
			// There are no vips in this cluster. Just delete the MCS service now.
			ic.deleteService(prevMcsService)
		}
		return
	}

	if prevMcsService != nil {
		event = model.EventUpdate
	} else {
		event = model.EventAdd
	}

	mcsService := ic.genMCSService(svc, mcsHost, vips)
	ic.addOrUpdateService(nil, mcsService, event, false)
}

func (ic *serviceImportCacheImpl) onServiceImportEvent(obj interface{}, event model.Event) error {
	si, ok := obj.(*unstructured.Unstructured)
	if !ok {
		tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
		if !ok {
			return fmt.Errorf("couldn't get object from tombstone %#v", obj)
		}
		si, ok = tombstone.Obj.(*unstructured.Unstructured)
		if !ok {
			return fmt.Errorf("tombstone contained object that is not a ServiceImport %#v", obj)
		}
	}

	// We need a full push if the cluster VIP changes.
	needsFullPush := false

	// Get the updated MCS service.
	mcsHost := serviceClusterSetLocalHostnameForKR(si)
	mcsService := ic.GetService(mcsHost)

	ips := GetServiceImportIPs(si)
	if mcsService == nil {
		if event == model.EventDelete || len(ips) == 0 {
			// We never created the service. Nothing to delete.
			return nil
		}

		// The service didn't exist prior. Treat it as an add.
		event = model.EventAdd

		// Create the MCS service, based on the cluster.local service. We get the merged, mesh-wide service
		// from the aggregate controller so that we don't rely on the service existing in this cluster.
		realService := ic.opts.MeshServiceController.GetService(kube.ServiceHostnameForKR(si, ic.opts.DomainSuffix))
		if realService == nil {
			log.Warnf("failed processing %s event for ServiceImport %s/%s in cluster %s. No matching service found in cluster",
				event, si.GetNamespace(), si.GetName(), ic.Cluster())
			return nil
		}

		// Create the MCS service from the cluster.local service.
		mcsService = ic.genMCSService(realService, mcsHost, ips)
	} else {
		if event == model.EventDelete || len(ips) == 0 {
			ic.deleteService(mcsService)
			return nil
		}

		// The service already existed. Treat it as an update.
		event = model.EventUpdate

		if ic.updateIPs(mcsService, ips) {
			needsFullPush = true
		}
	}

	// Always force a rebuild of the endpoint cache in case this import caused
	// a change to the discoverability policy.
	ic.addOrUpdateService(nil, mcsService, event, true)

	if needsFullPush {
		ic.doFullPush(mcsHost, si.GetNamespace())
	}

	return nil
}

func (ic *serviceImportCacheImpl) updateIPs(mcsService *model.Service, ips []string) (updated bool) {
	prevIPs := mcsService.ClusterVIPs.GetAddressesFor(ic.Cluster())
	if !util.StringSliceEqual(prevIPs, ips) {
		// Update the VIPs
		mcsService.ClusterVIPs.SetAddressesFor(ic.Cluster(), ips)
		updated = true
	}
	return
}

func (ic *serviceImportCacheImpl) doFullPush(mcsHost host.Name, ns string) {
	pushReq := &model.PushRequest{
		Full: true,
		ConfigsUpdated: map[model.ConfigKey]struct{}{{
			Kind:      gvk.ServiceEntry,
			Name:      mcsHost.String(),
			Namespace: ns,
		}: {}},
		Reason: []model.TriggerReason{model.ServiceUpdate},
	}
	ic.opts.XDSUpdater.ConfigUpdate(pushReq)
}

// GetServiceImportIPs returns the list of ClusterSet IPs for the ServiceImport.
// Exported for testing only.
func GetServiceImportIPs(si *unstructured.Unstructured) []string {
	var ips []string
	if spec, ok := si.Object["spec"].(map[string]interface{}); ok {
		if rawIPs, ok := spec["ips"].([]interface{}); ok {
			for _, rawIP := range rawIPs {
				ip := rawIP.(string)
				if net.ParseIP(ip) != nil {
					ips = append(ips, ip)
				}
			}
		}
	}
	sort.Strings(ips)
	return ips
}

// genMCSService generates an MCS service based on the given real k8s service. The list of vips must be non-empty.
func (ic *serviceImportCacheImpl) genMCSService(realService *model.Service, mcsHost host.Name, vips []string) *model.Service {
	mcsService := realService.DeepCopy()
	mcsService.Hostname = mcsHost
	mcsService.DefaultAddress = vips[0]
	mcsService.ClusterVIPs.Addresses = map[cluster.ID][]string{
		ic.Cluster(): vips,
	}

	return mcsService
}

func (ic *serviceImportCacheImpl) getClusterSetIPs(name types.NamespacedName) []string {
	if si, err := ic.lister.ByNamespace(name.Namespace).Get(name.Name); err == nil {
		return GetServiceImportIPs(si.(*unstructured.Unstructured))
	}
	return nil
}

func (ic *serviceImportCacheImpl) ImportedServices() []importedService {
	sis, err := ic.lister.List(klabels.Everything())
	if err != nil {
		return make([]importedService, 0)
	}

	// Iterate over the ServiceImport resources in this cluster.
	out := make([]importedService, 0, len(sis))

	ic.RLock()
	for _, si := range sis {
		usi := si.(*unstructured.Unstructured)
		info := importedService{
			namespacedName: kube.NamespacedNameForK8sObject(usi),
		}

		// Lookup the synthetic MCS service.
		hostName := serviceClusterSetLocalHostnameForKR(usi)
		svc := ic.servicesMap[hostName]
		if svc != nil {
			if vips := svc.ClusterVIPs.GetAddressesFor(ic.Cluster()); len(vips) > 0 {
				info.clusterSetVIP = vips[0]
			}
		}

		out = append(out, info)
	}
	ic.RUnlock()

	return out
}

func (ic *serviceImportCacheImpl) HasSynced() bool {
	return ic.informer.HasSynced()
}

type disabledServiceImportCache struct{}

var _ serviceImportCache = disabledServiceImportCache{}

func (c disabledServiceImportCache) HasSynced() bool {
	return true
}

func (c disabledServiceImportCache) ImportedServices() []importedService {
	// MCS is disabled - returning `nil`, which is semantically different here than an empty list.
	return nil
}
<commit_msg>Synchronize MCS even handling (#37839)<commit_after>// Copyright Istio 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 controller

import (
	"fmt"
	"net"
	"sort"
	"strings"

	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	klabels "k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/client-go/tools/cache"

	"istio.io/istio/pilot/pkg/features"
	"istio.io/istio/pilot/pkg/model"
	"istio.io/istio/pilot/pkg/networking/util"
	"istio.io/istio/pilot/pkg/serviceregistry/kube"
	"istio.io/istio/pkg/cluster"
	"istio.io/istio/pkg/config/constants"
	"istio.io/istio/pkg/config/host"
	"istio.io/istio/pkg/config/schema/gvk"
	"istio.io/istio/pkg/kube/mcs"
)

const (
	mcsDomainSuffix = "." + constants.DefaultClusterSetLocalDomain
)

type importedService struct {
	namespacedName types.NamespacedName
	clusterSetVIP  string
}

// serviceImportCache reads and processes Kubernetes Multi-Cluster Services (MCS) ServiceImport
// resources.
//
// An MCS controller is responsible for reading ServiceExport resources in one cluster and generating
// ServiceImport in all clusters of the ClusterSet (i.e. mesh). While the serviceExportCache reads
// ServiceExport to control the discoverability policy for individual endpoints, this controller
// reads ServiceImport in the cluster in order to extract the ClusterSet VIP and generate a
// synthetic service for the MCS host (i.e. clusterset.local). The aggregate.Controller will then
// merge together the MCS services from all the clusters, filling out the full map of Cluster IPs.
//
// The synthetic MCS service is a copy of the real k8s Service (e.g. cluster.local) with the same
// namespaced name, but with the hostname and VIPs changed to the appropriate ClusterSet values.
// The real k8s Service can live anywhere in the mesh and does not have to reside in the same
// cluster as the ServiceImport.
type serviceImportCache interface {
	HasSynced() bool
	ImportedServices() []importedService
}

// newServiceImportCache creates a new cache of ServiceImport resources in the cluster.
func newServiceImportCache(c *Controller) serviceImportCache {
	if features.EnableMCSHost {
		dInformer := c.client.DynamicInformer().ForResource(mcs.ServiceImportGVR)
		sic := &serviceImportCacheImpl{
			Controller: c,
			informer:   dInformer.Informer(),
			lister:     dInformer.Lister(),
		}

		// Register callbacks for Service events anywhere in the mesh.
		c.opts.MeshServiceController.AppendServiceHandlerForCluster(c.Cluster(), sic.onServiceEvent)

		// Register callbacks for ServiceImport events in this cluster only.
		c.registerHandlers(sic.informer, "ServiceImports", sic.onServiceImportEvent, nil)
		return sic
	}

	// MCS Service discovery is disabled. Use a placeholder cache.
	return disabledServiceImportCache{}
}

// serviceImportCacheImpl reads ServiceImport resources for a single cluster.
type serviceImportCacheImpl struct {
	*Controller
	informer cache.SharedIndexInformer
	lister   cache.GenericLister
}

// onServiceEvent is called when the controller receives an event for the kube Service (i.e. cluster.local).
// When this happens, we need to update the state of the associated synthetic MCS service.
func (ic *serviceImportCacheImpl) onServiceEvent(svc *model.Service, event model.Event) {
	if strings.HasSuffix(svc.Hostname.String(), mcsDomainSuffix) {
		// Ignore events for MCS services that were triggered by this controller.
		return
	}

	// This method is called concurrently from each cluster's queue. Process it in `this` cluster's queue
	// in order to synchronize event processing.
	ic.queue.Push(func() error {
		namespacedName := namespacedNameForService(svc)

		// Lookup the previous MCS service if there was one.
		mcsHost := serviceClusterSetLocalHostname(namespacedName)
		prevMcsService := ic.GetService(mcsHost)

		// Get the ClusterSet VIPs for this service in this cluster. Will only be populated if the
		// service has a ServiceImport in this cluster.
		vips := ic.getClusterSetIPs(namespacedName)
		name := namespacedName.Name
		ns := namespacedName.Namespace

		if len(vips) == 0 || (event == model.EventDelete &&
			ic.opts.MeshServiceController.GetService(kube.ServiceHostname(name, ns, ic.opts.DomainSuffix)) == nil) {
			if prevMcsService != nil {
				// There are no vips in this cluster. Just delete the MCS service now.
				ic.deleteService(prevMcsService)
			}
			return nil
		}

		if prevMcsService != nil {
			event = model.EventUpdate
		} else {
			event = model.EventAdd
		}

		mcsService := ic.genMCSService(svc, mcsHost, vips)
		ic.addOrUpdateService(nil, mcsService, event, false)
		return nil
	})
}

func (ic *serviceImportCacheImpl) onServiceImportEvent(obj interface{}, event model.Event) error {
	si, ok := obj.(*unstructured.Unstructured)
	if !ok {
		tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
		if !ok {
			return fmt.Errorf("couldn't get object from tombstone %#v", obj)
		}
		si, ok = tombstone.Obj.(*unstructured.Unstructured)
		if !ok {
			return fmt.Errorf("tombstone contained object that is not a ServiceImport %#v", obj)
		}
	}

	// We need a full push if the cluster VIP changes.
	needsFullPush := false

	// Get the updated MCS service.
	mcsHost := serviceClusterSetLocalHostnameForKR(si)
	mcsService := ic.GetService(mcsHost)

	ips := GetServiceImportIPs(si)
	if mcsService == nil {
		if event == model.EventDelete || len(ips) == 0 {
			// We never created the service. Nothing to delete.
			return nil
		}

		// The service didn't exist prior. Treat it as an add.
		event = model.EventAdd

		// Create the MCS service, based on the cluster.local service. We get the merged, mesh-wide service
		// from the aggregate controller so that we don't rely on the service existing in this cluster.
		realService := ic.opts.MeshServiceController.GetService(kube.ServiceHostnameForKR(si, ic.opts.DomainSuffix))
		if realService == nil {
			log.Warnf("failed processing %s event for ServiceImport %s/%s in cluster %s. No matching service found in cluster",
				event, si.GetNamespace(), si.GetName(), ic.Cluster())
			return nil
		}

		// Create the MCS service from the cluster.local service.
		mcsService = ic.genMCSService(realService, mcsHost, ips)
	} else {
		if event == model.EventDelete || len(ips) == 0 {
			ic.deleteService(mcsService)
			return nil
		}

		// The service already existed. Treat it as an update.
		event = model.EventUpdate

		if ic.updateIPs(mcsService, ips) {
			needsFullPush = true
		}
	}

	// Always force a rebuild of the endpoint cache in case this import caused
	// a change to the discoverability policy.
	ic.addOrUpdateService(nil, mcsService, event, true)

	if needsFullPush {
		ic.doFullPush(mcsHost, si.GetNamespace())
	}

	return nil
}

func (ic *serviceImportCacheImpl) updateIPs(mcsService *model.Service, ips []string) (updated bool) {
	prevIPs := mcsService.ClusterVIPs.GetAddressesFor(ic.Cluster())
	if !util.StringSliceEqual(prevIPs, ips) {
		// Update the VIPs
		mcsService.ClusterVIPs.SetAddressesFor(ic.Cluster(), ips)
		updated = true
	}
	return
}

func (ic *serviceImportCacheImpl) doFullPush(mcsHost host.Name, ns string) {
	pushReq := &model.PushRequest{
		Full: true,
		ConfigsUpdated: map[model.ConfigKey]struct{}{{
			Kind:      gvk.ServiceEntry,
			Name:      mcsHost.String(),
			Namespace: ns,
		}: {}},
		Reason: []model.TriggerReason{model.ServiceUpdate},
	}
	ic.opts.XDSUpdater.ConfigUpdate(pushReq)
}

// GetServiceImportIPs returns the list of ClusterSet IPs for the ServiceImport.
// Exported for testing only.
func GetServiceImportIPs(si *unstructured.Unstructured) []string {
	var ips []string
	if spec, ok := si.Object["spec"].(map[string]interface{}); ok {
		if rawIPs, ok := spec["ips"].([]interface{}); ok {
			for _, rawIP := range rawIPs {
				ip := rawIP.(string)
				if net.ParseIP(ip) != nil {
					ips = append(ips, ip)
				}
			}
		}
	}
	sort.Strings(ips)
	return ips
}

// genMCSService generates an MCS service based on the given real k8s service. The list of vips must be non-empty.
func (ic *serviceImportCacheImpl) genMCSService(realService *model.Service, mcsHost host.Name, vips []string) *model.Service {
	mcsService := realService.DeepCopy()
	mcsService.Hostname = mcsHost
	mcsService.DefaultAddress = vips[0]
	mcsService.ClusterVIPs.Addresses = map[cluster.ID][]string{
		ic.Cluster(): vips,
	}

	return mcsService
}

func (ic *serviceImportCacheImpl) getClusterSetIPs(name types.NamespacedName) []string {
	if si, err := ic.lister.ByNamespace(name.Namespace).Get(name.Name); err == nil {
		return GetServiceImportIPs(si.(*unstructured.Unstructured))
	}
	return nil
}

func (ic *serviceImportCacheImpl) ImportedServices() []importedService {
	sis, err := ic.lister.List(klabels.Everything())
	if err != nil {
		return make([]importedService, 0)
	}

	// Iterate over the ServiceImport resources in this cluster.
	out := make([]importedService, 0, len(sis))

	ic.RLock()
	for _, si := range sis {
		usi := si.(*unstructured.Unstructured)
		info := importedService{
			namespacedName: kube.NamespacedNameForK8sObject(usi),
		}

		// Lookup the synthetic MCS service.
		hostName := serviceClusterSetLocalHostnameForKR(usi)
		svc := ic.servicesMap[hostName]
		if svc != nil {
			if vips := svc.ClusterVIPs.GetAddressesFor(ic.Cluster()); len(vips) > 0 {
				info.clusterSetVIP = vips[0]
			}
		}

		out = append(out, info)
	}
	ic.RUnlock()

	return out
}

func (ic *serviceImportCacheImpl) HasSynced() bool {
	return ic.informer.HasSynced()
}

type disabledServiceImportCache struct{}

var _ serviceImportCache = disabledServiceImportCache{}

func (c disabledServiceImportCache) HasSynced() bool {
	return true
}

func (c disabledServiceImportCache) ImportedServices() []importedService {
	// MCS is disabled - returning `nil`, which is semantically different here than an empty list.
	return nil
}
<|endoftext|>


<commit_before>// Parse Exif4film xml.
// And output the result
//
// See LICENSE

package main

import (
	"fmt"
	"os"
	"strconv"

	"gopkg.in/lucsky/go-exml.v3"
)

type E4fObject interface {
	Id() int
	Type() string
}

type E4fDb struct {
	Version      string
	Cameras      []*Camera
	Makes        []*Make
	GpsLocations []*GpsLocation
	ExposedRolls []*ExposedRoll
	Exposures    []*Exposure
	Films        []*Film
	Lenses       []*Lens
	Artists      []*Artist

	GpsMap     map[int]*GpsLocation
	LensMap    map[int]*Lens
	ObjectsMap map[int]E4fObject
}

// Build the id -> data maps for the various elements
func (db *E4fDb) buildMaps() {
	db.ObjectsMap = make(map[int]E4fObject)

	for _, cam := range db.Cameras {
		o, present := db.ObjectsMap[cam.id]
		if present {
			fmt.Printf("Object %d present already of type %s\n",
				cam.id, o.Type())
		}
		db.ObjectsMap[cam.id] = cam
	}

	for _, mk := range db.Makes {
		o, present := db.ObjectsMap[mk.id]
		if present {
			fmt.Printf("Object %d present already of type %s\n",
				mk.id, o.Type())
		}
		db.ObjectsMap[mk.id] = mk
	}

	db.GpsMap = make(map[int]*GpsLocation)
	for _, gps := range db.GpsLocations {
		db.GpsMap[gps.id] = gps
	}

	for _, roll := range db.ExposedRolls {
		o, present := db.ObjectsMap[roll.id]
		if present {
			fmt.Printf("Object %d present already of type %s\n",
				roll.id, o.Type())
		}
		db.ObjectsMap[roll.id] = roll
	}

	for _, exp := range db.Exposures {
		o, present := db.ObjectsMap[exp.id]
		if present {
			fmt.Printf("Object %d present already of type %s\n",
				exp.id, o.Type())
		}
		db.ObjectsMap[exp.id] = exp
	}

	for _, film := range db.Films {
		o, present := db.ObjectsMap[film.id]
		if present {
			fmt.Printf("Object %d present already of type %s\n",
				film.id, o.Type())
		}
		db.ObjectsMap[film.id] = film
	}

	db.LensMap = make(map[int]*Lens)
	for _, lens := range db.Lenses {
		db.LensMap[lens.id] = lens
	}
}

func (db *E4fDb) exposuresForRoll(id int) (exposures []*Exposure) {
	obj := db.ObjectsMap[id]
	if obj.Type() != "ExposedRoll" {
		fmt.Printf("Found type %s\n", obj.Type())
		return nil
	}

	for _, exp := range db.Exposures {
		if exp.RollId == id {
			exposures = append(exposures, exp)
		}
	}
	return
}

type Camera struct {
	id                int
	DefaultFrameCount int
	MakeId            int
	SerialNumber      string
	DefaultFilmType   string
	Title             string
}

func (o *Camera) Id() int {
	return o.id
}
func (o *Camera) Type() string {
	return "Camera"
}

type Make struct {
	id   int
	Name string
}

func (o *Make) Id() int {
	return o.id
}
func (o *Make) Type() string {
	return "Make"
}

type GpsLocation struct {
	id             int
	Long, Lat, Alt float64
}

func (o *GpsLocation) Id() int {
	return o.id
}
func (o *GpsLocation) Type() string {
	return "GpsLocation"
}

type ExposedRoll struct {
	id           int
	FilmType     string
	CameraId     int
	Iso          int
	FrameCount   int
	TimeUnloaded string
	TimeLoaded   string
	FilmId       int
}

func (o *ExposedRoll) Id() int {
	return o.id
}
func (o *ExposedRoll) Type() string {
	return "ExposedRoll"
}

type Exposure struct {
	id           int
	FlashOn      bool
	Desc         string
	Number       int
	GpsLocId     int
	ExpComp      int
	RollId       int
	FocalLength  int
	LightSource  string
	TimeTaken    string
	ShutterSpeed string
	LensId       int
	Aperture     string
	MeteringMode string
}

func (o *Exposure) Id() int {
	return o.id
}
func (o *Exposure) Type() string {
	return "Exposure"
}

type Film struct {
	id        int
	Process   string
	Title     string
	ColorType string
	Iso       int
	MakeId    int
}

func (o *Film) Id() int {
	return o.id
}
func (o *Film) Type() string {
	return "Film"
}

type Lens struct {
	id             int
	Title          string
	SerialNumber   string
	MakeId         int
	ApertureMin    string
	ApertureMax    string
	FocalLengthMin int
	FocalLengthMax int
}

func (o *Lens) Id() int {
	return o.id
}
func (o *Lens) Type() string {
	return "Lens"
}

type Artist struct {
	Name string
}

func toInt(dst *int) exml.TextCallback {
	return func(c exml.CharData) {
		n, err := strconv.ParseInt(string(c), 0, 32)
		if err == nil {
			*dst = int(n)
		}
	}
}

func toFloat(dst *float64) exml.TextCallback {
	return func(c exml.CharData) {
		f, err := strconv.ParseFloat(string(c), 64)
		if err == nil {
			*dst = float64(f)
		}
	}
}

func toBool(dst *bool) exml.TextCallback {
	return func(c exml.CharData) {
		if string(c) == "true" {
			*dst = true
		} else {
			*dst = false
		}
	}
}

func parse(file string) *E4fDb {

	reader, _ := os.Open(file)
	defer reader.Close()

	e4fDb := &E4fDb{}
	decoder := exml.NewDecoder(reader)

	decoder.On("Exif4Film", func(attrs exml.Attrs) {

		e4fDb.Version, _ = attrs.Get("version")

		decoder.On("Camera/dk.codeunited.exif4film.model.Camera",
			func(attrs exml.Attrs) {
				camera := &Camera{}
				e4fDb.Cameras = append(e4fDb.Cameras, camera)
				decoder.OnTextOf("camera_default_frame_count",
					toInt(&camera.DefaultFrameCount))
				decoder.OnTextOf("id", toInt(&camera.id))
				decoder.OnTextOf("camera_make_id",
					toInt(&camera.MakeId))
				decoder.OnTextOf("camera_serial_number",
					exml.Assign(&camera.SerialNumber))
				decoder.OnTextOf("camera_default_film_type",
					exml.Assign(&camera.DefaultFilmType))
				decoder.OnTextOf("camera_title",
					exml.Assign(&camera.Title))
			})
		decoder.On("Make/dk.codeunited.exif4film.model.Make",
			func(attrs exml.Attrs) {
				m := &Make{}
				e4fDb.Makes = append(e4fDb.Makes, m)
				decoder.OnTextOf("id", toInt(&m.id))
				decoder.OnTextOf("make_name",
					exml.Assign(&m.Name))
			})
		decoder.On(
			"GpsLocation/dk.codeunited.exif4film.model.GpsLocation",
			func(attrs exml.Attrs) {
				gps := &GpsLocation{}
				e4fDb.GpsLocations = append(e4fDb.GpsLocations,
					gps)
				decoder.OnTextOf("id", toInt(&gps.id))
				decoder.OnTextOf("gps_latitude",
					toFloat(&gps.Lat))
				decoder.OnTextOf("gps_longitude",
					toFloat(&gps.Long))
				decoder.OnTextOf("gps_altitude",
					toFloat(&gps.Alt))
			})
		decoder.On(
			"ExposedRoll/dk.codeunited.exif4film.model.ExposedRoll",
			func(attrs exml.Attrs) {
				roll := &ExposedRoll{}
				e4fDb.ExposedRolls = append(e4fDb.ExposedRolls,
					roll)
				decoder.OnTextOf("id", toInt(&roll.id))
				decoder.OnTextOf("exposedroll_film_type",
					exml.Assign(&roll.FilmType))
				decoder.OnTextOf("exposedroll_camera_id",
					toInt(&roll.CameraId))
				decoder.OnTextOf("exposedroll_film_id",
					toInt(&roll.FilmId))
				decoder.OnTextOf("exposedroll_iso",
					toInt(&roll.Iso))
				decoder.OnTextOf("exposedroll_frame_count",
					toInt(&roll.FrameCount))
				decoder.OnTextOf("exposedroll_time_unloaded",
					exml.Assign(&roll.TimeUnloaded))
				decoder.OnTextOf("exposedroll_time_loaded",
					exml.Assign(&roll.TimeLoaded))
			})
		decoder.On("Exposure/dk.codeunited.exif4film.model.Exposure",
			func(attrs exml.Attrs) {
				exp := &Exposure{}
				e4fDb.Exposures = append(e4fDb.Exposures, exp)
				decoder.OnTextOf("id", toInt(&exp.id))
				decoder.OnTextOf("exposure_flash_on",
					toBool(&exp.FlashOn))
				decoder.OnTextOf("exposure_description",
					exml.Assign(&exp.Desc))
				decoder.OnTextOf("exposure_number",
					toInt(&exp.Number))
				decoder.OnTextOf("exposure_gps_location",
					toInt(&exp.GpsLocId))
				decoder.OnTextOf("exposure_compensation",
					toInt(&exp.ExpComp))
				decoder.OnTextOf("exposure_roll_id",
					toInt(&exp.RollId))
				decoder.OnTextOf("exposure_focal_length",
					toInt(&exp.FocalLength))
				decoder.OnTextOf("exposure_light_source",
					exml.Assign(&exp.LightSource))
				decoder.OnTextOf("exposure_time_taken",
					exml.Assign(&exp.TimeTaken))
				decoder.OnTextOf("exposure_shutter_speed",
					exml.Assign(&exp.ShutterSpeed))
				decoder.OnTextOf("exposure_lens_id",
					toInt(&exp.LensId))
				decoder.OnTextOf("exposure_aperture",
					exml.Assign(&exp.Aperture))
				decoder.OnTextOf("exposure_metering_mode",
					exml.Assign(&exp.MeteringMode))
			})
		decoder.On("Film/dk.codeunited.exif4film.model.Film",
			func(attrs exml.Attrs) {
				film := &Film{}
				e4fDb.Films = append(e4fDb.Films, film)
				decoder.OnTextOf("id", toInt(&film.id))
				decoder.OnTextOf("film_title",
					exml.Assign(&film.Title))
				decoder.OnTextOf("film_make_process",
					exml.Assign(&film.Process))
				decoder.OnTextOf("film_color_type",
					exml.Assign(&film.ColorType))
				decoder.OnTextOf("film_iso", toInt(&film.Iso))
				decoder.OnTextOf("film_make_id",
					toInt(&film.MakeId))
			})
		decoder.On("Lens/dk.codeunited.exif4film.model.Lens",
			func(attrs exml.Attrs) {
				lens := &Lens{}
				e4fDb.Lenses = append(e4fDb.Lenses, lens)
				decoder.OnTextOf("id", toInt(&lens.id))
				decoder.OnTextOf("lens_title",
					exml.Assign(&lens.Title))
				decoder.OnTextOf("lens_serial_number",
					exml.Assign(&lens.SerialNumber))
				decoder.OnTextOf("lens_make_id",
					toInt(&lens.MakeId))
				decoder.OnTextOf("lens_aperture_min",
					exml.Assign(&lens.ApertureMin))
				decoder.OnTextOf("lens_aperture_max",
					exml.Assign(&lens.ApertureMax))
				decoder.OnTextOf("lens_focal_length_min",
					toInt(&lens.FocalLengthMin))
				decoder.OnTextOf("lens_focal_length_max",
					toInt(&lens.FocalLengthMax))
			})
		decoder.On("Artist/dk.codeunited.exif4film.model.Artist",
			func(attrs exml.Attrs) {
				artist := &Artist{}
				e4fDb.Artists = append(e4fDb.Artists, artist)
				decoder.OnTextOf("artist_name",
					exml.Assign(&artist.Name))
			})
	})
	decoder.Run()

	return e4fDb
}

func main() {
	e4fDb := parse("samples/export-Roll-20130630_203650.xml")

	e4fDb.buildMaps()

	for _, roll := range e4fDb.ExposedRolls {
		id := roll.Id()
		fmt.Println("Roll:")
		fmt.Println(roll)
		exps := e4fDb.exposuresForRoll(id)
		for _, exp := range exps {
			fmt.Printf("Exposure %d: ", exp.Number)
			fmt.Println(exp)
		}
	}
}
<commit_msg>Each kind of object has its own id namespace<commit_after>// Parse Exif4film xml.
// And output the result
//
// See LICENSE

package main

import (
	"fmt"
	"os"
	"strconv"

	"gopkg.in/lucsky/go-exml.v3"
)

type E4fDb struct {
	Version      string
	Cameras      []*Camera
	Makes        []*Make
	GpsLocations []*GpsLocation
	ExposedRolls []*ExposedRoll
	Exposures    []*Exposure
	Films        []*Film
	Lenses       []*Lens
	Artists      []*Artist

	RollMap    map[int]*ExposedRoll
	MakeMap    map[int]*Make
	CameraMap  map[int]*Camera
	GpsMap     map[int]*GpsLocation
	LensMap    map[int]*Lens
	FilmMap    map[int]*Film
}

// Build the id -> data maps for the various elements
func (db *E4fDb) buildMaps() {
	db.CameraMap = make(map[int]*Camera)
	for _, cam := range db.Cameras {
		db.CameraMap[cam.Id] = cam
	}

	db.MakeMap = make(map[int]*Make)
	for _, mk := range db.Makes {
		db.MakeMap[mk.Id] = mk
	}

	db.GpsMap = make(map[int]*GpsLocation)
	for _, gps := range db.GpsLocations {
		db.GpsMap[gps.Id] = gps
	}

	db.RollMap = make(map[int]*ExposedRoll)
	for _, roll := range db.ExposedRolls {
		db.RollMap[roll.Id] = roll
	}

	db.FilmMap = make(map[int]*Film)
	for _, film := range db.Films {
		db.FilmMap[film.Id] = film
	}

	db.LensMap = make(map[int]*Lens)
	for _, lens := range db.Lenses {
		db.LensMap[lens.Id] = lens
	}
}

func (db *E4fDb) exposuresForRoll(id int) (exposures []*Exposure) {
	for _, exp := range db.Exposures {
		if exp.RollId == id {
			exposures = append(exposures, exp)
		}
	}
	return
}

type Camera struct {
	Id                int
	DefaultFrameCount int
	MakeId            int
	SerialNumber      string
	DefaultFilmType   string
	Title             string
}

type Make struct {
	Id   int
	Name string
}

type GpsLocation struct {
	Id             int
	Long, Lat, Alt float64
}

type ExposedRoll struct {
	Id           int
	FilmType     string
	CameraId     int
	Iso          int
	FrameCount   int
	TimeUnloaded string
	TimeLoaded   string
	FilmId       int
}

type Exposure struct {
	Id           int
	FlashOn      bool
	Desc         string
	Number       int
	GpsLocId     int
	ExpComp      int
	RollId       int
	FocalLength  int
	LightSource  string
	TimeTaken    string
	ShutterSpeed string
	LensId       int
	Aperture     string
	MeteringMode string
}

type Film struct {
	Id        int
	Process   string
	Title     string
	ColorType string
	Iso       int
	MakeId    int
}

type Lens struct {
	Id             int
	Title          string
	SerialNumber   string
	MakeId         int
	ApertureMin    string
	ApertureMax    string
	FocalLengthMin int
	FocalLengthMax int
}

type Artist struct {
	Name string
}

func toInt(dst *int) exml.TextCallback {
	return func(c exml.CharData) {
		n, err := strconv.ParseInt(string(c), 0, 32)
		if err == nil {
			*dst = int(n)
		}
	}
}

func toFloat(dst *float64) exml.TextCallback {
	return func(c exml.CharData) {
		f, err := strconv.ParseFloat(string(c), 64)
		if err == nil {
			*dst = float64(f)
		}
	}
}

func toBool(dst *bool) exml.TextCallback {
	return func(c exml.CharData) {
		if string(c) == "true" {
			*dst = true
		} else {
			*dst = false
		}
	}
}

func parse(file string) *E4fDb {

	reader, _ := os.Open(file)
	defer reader.Close()

	e4fDb := &E4fDb{}
	decoder := exml.NewDecoder(reader)

	decoder.On("Exif4Film", func(attrs exml.Attrs) {

		e4fDb.Version, _ = attrs.Get("version")

		decoder.On("Camera/dk.codeunited.exif4film.model.Camera",
			func(attrs exml.Attrs) {
				camera := &Camera{}
				e4fDb.Cameras = append(e4fDb.Cameras, camera)
				decoder.OnTextOf("camera_default_frame_count",
					toInt(&camera.DefaultFrameCount))
				decoder.OnTextOf("id", toInt(&camera.Id))
				decoder.OnTextOf("camera_make_id",
					toInt(&camera.MakeId))
				decoder.OnTextOf("camera_serial_number",
					exml.Assign(&camera.SerialNumber))
				decoder.OnTextOf("camera_default_film_type",
					exml.Assign(&camera.DefaultFilmType))
				decoder.OnTextOf("camera_title",
					exml.Assign(&camera.Title))
			})
		decoder.On("Make/dk.codeunited.exif4film.model.Make",
			func(attrs exml.Attrs) {
				m := &Make{}
				e4fDb.Makes = append(e4fDb.Makes, m)
				decoder.OnTextOf("id", toInt(&m.Id))
				decoder.OnTextOf("make_name",
					exml.Assign(&m.Name))
			})
		decoder.On(
			"GpsLocation/dk.codeunited.exif4film.model.GpsLocation",
			func(attrs exml.Attrs) {
				gps := &GpsLocation{}
				e4fDb.GpsLocations = append(e4fDb.GpsLocations,
					gps)
				decoder.OnTextOf("id", toInt(&gps.Id))
				decoder.OnTextOf("gps_latitude",
					toFloat(&gps.Lat))
				decoder.OnTextOf("gps_longitude",
					toFloat(&gps.Long))
				decoder.OnTextOf("gps_altitude",
					toFloat(&gps.Alt))
			})
		decoder.On(
			"ExposedRoll/dk.codeunited.exif4film.model.ExposedRoll",
			func(attrs exml.Attrs) {
				roll := &ExposedRoll{}
				e4fDb.ExposedRolls = append(e4fDb.ExposedRolls,
					roll)
				decoder.OnTextOf("id", toInt(&roll.Id))
				decoder.OnTextOf("exposedroll_film_type",
					exml.Assign(&roll.FilmType))
				decoder.OnTextOf("exposedroll_camera_id",
					toInt(&roll.CameraId))
				decoder.OnTextOf("exposedroll_film_id",
					toInt(&roll.FilmId))
				decoder.OnTextOf("exposedroll_iso",
					toInt(&roll.Iso))
				decoder.OnTextOf("exposedroll_frame_count",
					toInt(&roll.FrameCount))
				decoder.OnTextOf("exposedroll_time_unloaded",
					exml.Assign(&roll.TimeUnloaded))
				decoder.OnTextOf("exposedroll_time_loaded",
					exml.Assign(&roll.TimeLoaded))
			})
		decoder.On("Exposure/dk.codeunited.exif4film.model.Exposure",
			func(attrs exml.Attrs) {
				exp := &Exposure{}
				e4fDb.Exposures = append(e4fDb.Exposures, exp)
				decoder.OnTextOf("id", toInt(&exp.Id))
				decoder.OnTextOf("exposure_flash_on",
					toBool(&exp.FlashOn))
				decoder.OnTextOf("exposure_description",
					exml.Assign(&exp.Desc))
				decoder.OnTextOf("exposure_number",
					toInt(&exp.Number))
				decoder.OnTextOf("exposure_gps_location",
					toInt(&exp.GpsLocId))
				decoder.OnTextOf("exposure_compensation",
					toInt(&exp.ExpComp))
				decoder.OnTextOf("exposure_roll_id",
					toInt(&exp.RollId))
				decoder.OnTextOf("exposure_focal_length",
					toInt(&exp.FocalLength))
				decoder.OnTextOf("exposure_light_source",
					exml.Assign(&exp.LightSource))
				decoder.OnTextOf("exposure_time_taken",
					exml.Assign(&exp.TimeTaken))
				decoder.OnTextOf("exposure_shutter_speed",
					exml.Assign(&exp.ShutterSpeed))
				decoder.OnTextOf("exposure_lens_id",
					toInt(&exp.LensId))
				decoder.OnTextOf("exposure_aperture",
					exml.Assign(&exp.Aperture))
				decoder.OnTextOf("exposure_metering_mode",
					exml.Assign(&exp.MeteringMode))
			})
		decoder.On("Film/dk.codeunited.exif4film.model.Film",
			func(attrs exml.Attrs) {
				film := &Film{}
				e4fDb.Films = append(e4fDb.Films, film)
				decoder.OnTextOf("id", toInt(&film.Id))
				decoder.OnTextOf("film_title",
					exml.Assign(&film.Title))
				decoder.OnTextOf("film_make_process",
					exml.Assign(&film.Process))
				decoder.OnTextOf("film_color_type",
					exml.Assign(&film.ColorType))
				decoder.OnTextOf("film_iso", toInt(&film.Iso))
				decoder.OnTextOf("film_make_id",
					toInt(&film.MakeId))
			})
		decoder.On("Lens/dk.codeunited.exif4film.model.Lens",
			func(attrs exml.Attrs) {
				lens := &Lens{}
				e4fDb.Lenses = append(e4fDb.Lenses, lens)
				decoder.OnTextOf("id", toInt(&lens.Id))
				decoder.OnTextOf("lens_title",
					exml.Assign(&lens.Title))
				decoder.OnTextOf("lens_serial_number",
					exml.Assign(&lens.SerialNumber))
				decoder.OnTextOf("lens_make_id",
					toInt(&lens.MakeId))
				decoder.OnTextOf("lens_aperture_min",
					exml.Assign(&lens.ApertureMin))
				decoder.OnTextOf("lens_aperture_max",
					exml.Assign(&lens.ApertureMax))
				decoder.OnTextOf("lens_focal_length_min",
					toInt(&lens.FocalLengthMin))
				decoder.OnTextOf("lens_focal_length_max",
					toInt(&lens.FocalLengthMax))
			})
		decoder.On("Artist/dk.codeunited.exif4film.model.Artist",
			func(attrs exml.Attrs) {
				artist := &Artist{}
				e4fDb.Artists = append(e4fDb.Artists, artist)
				decoder.OnTextOf("artist_name",
					exml.Assign(&artist.Name))
			})
	})
	decoder.Run()

	return e4fDb
}

func main() {
	e4fDb := parse("samples/export-Roll-20130630_203650.xml")

	e4fDb.buildMaps()

	for _, roll := range e4fDb.ExposedRolls {
		id := roll.Id()
		fmt.Println("Roll:")
		fmt.Println(roll)
		exps := e4fDb.exposuresForRoll(id)
		for _, exp := range exps {
			fmt.Printf("Exposure %d: ", exp.Number)
			fmt.Println(exp)
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"io"
	"os"
	"os/exec"
	"regexp"
	"runtime"
	"strings"
	"text/template"
)

var (
	aliases []string = []string{
		"go",
		"godep",
	}
	git_http_re *regexp.Regexp = regexp.MustCompile("^https?://(.+).git$")
	git_ssh_re  *regexp.Regexp = regexp.MustCompile("^.+@([^:]+):(.+).git$")
)

type Environment struct {
	Package string
	Root    string
}

type envWrap struct {
	Env     *Environment
	Aliases []string
}

func LoadEnvfile() (*Environment, error) {
	root, e := getRoot()
	if e != nil {
		return nil, e
	}

	env_path := strings.Join([]string{root, ".env"}, "/")
	fi, e := os.OpenFile(env_path, os.O_RDONLY, os.ModePerm)
	if e != nil {
		return nil, e
	}

	env := new(Environment)

	r := bufio.NewReader(fi)
	for true {
		line, e := r.ReadString('\n')
		if e != nil {
			break
		}
		switch {
		case strings.HasPrefix(line, "ENV_DIR"):
			env.Root = strings.SplitN(line, "=", 1)[1]
			break
		case strings.HasPrefix(line, "GOPACKAGE"):
			env.Package = strings.SplitN(line, "=", 1)[1]
			break
		}
	}

	if env.Package == "" {
		return env, errors.New("Package name is empty. it looks like broken .env file")
	}

	return env, nil
}

func getPackageNameGit() (string, error) {
	cmd := exec.Command("git", "config", "--get", "remote.origin.url")
	out, e := cmd.StdoutPipe()
	if e != nil {
		return "", e
	}
	cmd.Start()
	url := make([]byte, 512)
	length, e := out.Read(url)
	if e != nil {
		return "", e
	}
	e = cmd.Wait()
	if e != nil {
		return "", e
	}
	buf := bytes.NewBuffer(url)
	buf.Truncate(length)
	package_url := strings.TrimSpace(buf.String())
	if strs := git_http_re.FindStringSubmatch(package_url); len(strs) != 0 {
		return strs[1], nil
	} else if strs := git_ssh_re.FindStringSubmatch(package_url); len(strs) != 0 {
		return fmt.Sprintf("%s/%s", strs[1], strs[2]), nil
	} else {
		return "", errors.New("not matched")
	}
}

func getPackage() (string, error) {
	if name, _ := getPackageNameGit(); len(name) != 0 {
		return name, nil
	}
	return "", nil
}

func getRoot() (string, error) {
	cmd := exec.Command("git", "rev-parse", "--show-toplevel")
	out, e := cmd.StdoutPipe()
	e = cmd.Start()
	if e != nil {
		return "", e
	}
	path := make([]byte, 512)
	length, e := out.Read(path)
	if e != nil {
		return "", e
	}
	e = cmd.Wait()
	if e != nil {
		return "", e
	}
	buf := bytes.NewBuffer(path)
	buf.Truncate(length)
	root := strings.TrimSpace(buf.String())
	switch runtime.GOOS {
	case "windows":
		root = strings.Replace(root, "/", "\\", -1)
	}
	return root, nil
}

func writeEnvFile(wrap envWrap, writer io.Writer, templateStr string) error {
	t, e := template.New("env_script").Parse(templateStr)
	if e != nil {
		return e
	}
	return t.Execute(writer, wrap)
}

func WriteEnvUnixFile(env *Environment, writer io.Writer) error {
	return writeEnvFile(envWrap{env, aliases}, writer, envTemplateUnix)
}

func WriteEnvPSFile(env *Environment, writer io.Writer) error {
	return writeEnvFile(envWrap{env, aliases}, writer, envTemplatePS)
}
<commit_msg>Add support for hg<commit_after>package main

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"io"
	"os"
	"os/exec"
	"regexp"
	"runtime"
	"strings"
	"text/template"
)

var (
	aliases []string = []string{
		"go",
		"godep",
	}
	git_http_re *regexp.Regexp = regexp.MustCompile("^https?://(.+).git$")
	git_ssh_re  *regexp.Regexp = regexp.MustCompile("^.+@([^:]+):(.+).git$")
	hg_http_re  *regexp.Regexp = regexp.MustCompile("^https?://(.+)$")
	hg_ssh_re   *regexp.Regexp = regexp.MustCompile("^ssh://[^@]+@(.+)$")
)

type Environment struct {
	Package string
	Root    string
}

type envWrap struct {
	Env     *Environment
	Aliases []string
}

func LoadEnvfile() (*Environment, error) {
	root, e := getRoot()
	if e != nil {
		return nil, e
	}

	env_path := strings.Join([]string{root, ".env"}, "/")
	fi, e := os.OpenFile(env_path, os.O_RDONLY, os.ModePerm)
	if e != nil {
		return nil, e
	}

	env := new(Environment)

	r := bufio.NewReader(fi)
	for true {
		line, e := r.ReadString('\n')
		if e != nil {
			break
		}
		switch {
		case strings.HasPrefix(line, "ENV_DIR"):
			env.Root = strings.SplitN(line, "=", 1)[1]
			break
		case strings.HasPrefix(line, "GOPACKAGE"):
			env.Package = strings.SplitN(line, "=", 1)[1]
			break
		}
	}

	if env.Package == "" {
		return env, errors.New("Package name is empty. it looks like broken .env file")
	}

	return env, nil
}

func getPackageNameGit() (string, error) {
	cmd := exec.Command("git", "config", "--get", "remote.origin.url")
	out, e := cmd.StdoutPipe()
	if e != nil {
		return "", e
	}
	cmd.Start()
	url := make([]byte, 512)
	length, e := out.Read(url)
	if e != nil {
		return "", e
	}
	e = cmd.Wait()
	if e != nil {
		return "", e
	}
	buf := bytes.NewBuffer(url)
	buf.Truncate(length)
	package_url := strings.TrimSpace(buf.String())
	if strs := git_http_re.FindStringSubmatch(package_url); len(strs) != 0 {
		return strs[1], nil
	} else if strs := git_ssh_re.FindStringSubmatch(package_url); len(strs) != 0 {
		return fmt.Sprintf("%s/%s", strs[1], strs[2]), nil
	} else {
		return "", errors.New("not matched")
	}
}

func getPackageNameHg() (string, error) {
	cmd := exec.Command("hg", "paths", "default")
	out, e := cmd.StdoutPipe()
	if e != nil {
		return "", e
	}
	cmd.Start()
	url := make([]byte, 512)
	length, e := out.Read(url)
	if e != nil {
		return "", e
	}
	e = cmd.Wait()
	if e != nil {
		return "", e
	}
	buf := bytes.NewBuffer(url)
	buf.Truncate(length)
	package_url := strings.TrimSpace(buf.String())
	if strs := hg_http_re.FindStringSubmatch(package_url); len(strs) != 0 {
		return strs[1], nil
	} else if strs := hg_ssh_re.FindStringSubmatch(package_url); len(strs) != 0 {
		return strs[1], nil
	} else {
		return "", errors.New("not matched")
	}
}

func getPackage() (string, error) {
	var (
		name string
		e    error
	)
	name, e = getPackageNameGit()
	if len(name) == 0 {
		name, e = getPackageNameHg()
	}
	if len(name) == 0 {
		return name, e
	}
	return name, e
}

func getRootGit() (string, error) {
	cmd := exec.Command("git", "rev-parse", "--show-toplevel")
	out, e := cmd.StdoutPipe()
	e = cmd.Start()
	if e != nil {
		return "", e
	}
	path := make([]byte, 512)
	length, e := out.Read(path)
	if e != nil {
		return "", e
	}
	e = cmd.Wait()
	if e != nil {
		return "", e
	}
	buf := bytes.NewBuffer(path)
	buf.Truncate(length)
	return strings.TrimSpace(buf.String()), nil
}

func getRootHg() (string, error) {
	cmd := exec.Command("hg", "root")
	out, e := cmd.StdoutPipe()
	e = cmd.Start()
	if e != nil {
		return "", e
	}
	path := make([]byte, 512)
	length, e := out.Read(path)
	if e != nil {
		return "", e
	}
	e = cmd.Wait()
	if e != nil {
		return "", e
	}
	buf := bytes.NewBuffer(path)
	buf.Truncate(length)
	return strings.TrimSpace(buf.String()), nil
}

func getRoot() (string, error) {
	var root string
	root, _ = getRootGit()
	if len(root) == 0 {
		root, _ = getRootHg()
	}
	if len(root) == 0 {
		return "", errors.New("Can't find root of local repository for working directory")
	}
	switch runtime.GOOS {
	case "windows":
		root = strings.Replace(root, "/", "\\", -1)
	}
	return root, nil
}

func writeEnvFile(wrap envWrap, writer io.Writer, templateStr string) error {
	t, e := template.New("env_script").Parse(templateStr)
	if e != nil {
		return e
	}
	return t.Execute(writer, wrap)
}

func WriteEnvUnixFile(env *Environment, writer io.Writer) error {
	return writeEnvFile(envWrap{env, aliases}, writer, envTemplateUnix)
}

func WriteEnvPSFile(env *Environment, writer io.Writer) error {
	return writeEnvFile(envWrap{env, aliases}, writer, envTemplatePS)
}
<|endoftext|>


<commit_before>// Copyright © 2017 Matt Tyler <me@matthewtyler.io>
// 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 run

import (
	"context"
	"fmt"
	"github.com/matt-tyler/elasticsearch-operator/e2e/pkg/gke"
	"os"
	"os/exec"
)

type Config struct {
	Build   bool   `mapstructure:"build"`
	Up      bool   `mapstructure:"up"`
	Down    bool   `mapstructure:"down"`
	Test    bool   `mapstructure:"test"`
	Project string `mapstructure:"project"`
	Zone    string `mapstructure:"zone"`
}

func Run(config Config, args []string) error {
	clusterId := "e2e-test-cluster"
	ctx := context.Background()
	client := gke.GkeClient{}

	if config.Up || config.Down {
		if err := gke.NewGkeClient(&client, ctx, config.Project, config.Zone); err != nil {
			return err
		}
		fmt.Println("Created GKE client")
	}

	// spin the cluster up
	if config.Up {
		fmt.Printf("Creating cluster: %v\n", clusterId)
		op, err := client.CreateCluster(clusterId)
		if err != nil {
			return err
		}
		client.Done(op)
		fmt.Printf("Cluster %v created\n", clusterId)
	}

	//cluster, _ := client.GetCluster(clusterId)

	//kubeClient, err := cluster.Client()
	//if err != nil {
	//    fmt.Println(err.Error())
	//}

	if config.Build {
		// build the e2e test binary
	}

	if config.Test {
		// run the tests
		ginkgo, err := exec.LookPath("ginkgo")
		if err != nil {
			return err
		}

		testbin, err := exec.LookPath("e2e.test")
		if err != nil {
			return err
		}

		args := []string{testbin}
		cmd := exec.Command(ginkgo, args...)

		cmd.Stdout = os.Stdin
		cmd.Stderr = os.Stderr

		if err := cmd.Run(); err != nil {
			return err
		}
	}

	// spin the cluster down
	if config.Down {
		fmt.Printf("Deleting Cluster: %v\n", clusterId)
		op, err := client.DeleteCluster(clusterId)
		if err != nil {
			return err
		}

		client.Done(op)
		fmt.Printf("Cluster %v deleted\n", clusterId)
	}

	return nil
}
<commit_msg>e2e: Add build command<commit_after>// Copyright © 2017 Matt Tyler <me@matthewtyler.io>
// 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 run

import (
	"context"
	"fmt"
	"github.com/matt-tyler/elasticsearch-operator/e2e/pkg/gke"
	"os"
	"os/exec"
)

type Config struct {
	Build   bool   `mapstructure:"build"`
	Up      bool   `mapstructure:"up"`
	Down    bool   `mapstructure:"down"`
	Test    bool   `mapstructure:"test"`
	Project string `mapstructure:"project"`
	Zone    string `mapstructure:"zone"`
}

func Run(config Config, args []string) error {
	clusterId := "e2e-test-cluster"
	ctx := context.Background()
	client := gke.GkeClient{}

	if config.Up || config.Down {
		if err := gke.NewGkeClient(&client, ctx, config.Project, config.Zone); err != nil {
			return err
		}
		fmt.Println("Created GKE client")
	}

	// spin the cluster up
	if config.Up {
		fmt.Printf("Creating cluster: %v\n", clusterId)
		op, err := client.CreateCluster(clusterId)
		if err != nil {
			return err
		}
		client.Done(op)
		fmt.Printf("Cluster %v created\n", clusterId)
	}

	//cluster, _ := client.GetCluster(clusterId)

	//kubeClient, err := cluster.Client()
	//if err != nil {
	//    fmt.Println(err.Error())
	//}

	if config.Build {
		// build the e2e test binary
		gopath := os.Getenv("GOPATH")

		wd, err := os.Getwd()
		if err != nil {
			return err
		}

		gocmd, err := exec.LookPath("go")
		if err != nil {
			return err
		}

		os.Chdir(gopath + "/src/github.com/matt-tyler/elasticsearch-operator/e2e/pkg/e2e")
		args := []string{"test", "-c", "-o", gopath + "/bin/e2e.test"}
		cmd := exec.Command(gocmd, args...)
		fmt.Println(cmd)

		if err := cmd.Run(); err != nil {
			return err
		}

		os.Chdir(wd)
	}

	if config.Test {
		// run the tests
		ginkgo, err := exec.LookPath("ginkgo")
		if err != nil {
			return err
		}

		testbin, err := exec.LookPath("e2e.test")
		if err != nil {
			return err
		}

		args := []string{testbin}
		cmd := exec.Command(ginkgo, args...)

		cmd.Stdout = os.Stdin
		cmd.Stderr = os.Stderr

		if err := cmd.Run(); err != nil {
			return err
		}
	}

	// spin the cluster down
	if config.Down {
		fmt.Printf("Deleting Cluster: %v\n", clusterId)
		op, err := client.DeleteCluster(clusterId)
		if err != nil {
			return err
		}

		client.Done(op)
		fmt.Printf("Cluster %v deleted\n", clusterId)
	}

	return nil
}
<|endoftext|>


<commit_before>// Copyright 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 RuntimeTest

import (
	"fmt"
	"path/filepath"

	. "github.com/cilium/cilium/test/ginkgo-ext"
	"github.com/cilium/cilium/test/helpers"

	. "github.com/onsi/gomega"
)

var _ = Describe("RuntimePrivilegedUnitTests", func() {

	var vm *helpers.SSHMeta

	BeforeAll(func() {
		vm = helpers.InitRuntimeHelper(helpers.Runtime, logger)
		res := vm.ExecWithSudo("systemctl stop cilium")
		res.ExpectSuccess("Failed trying to stop cilium via systemctl")
		ExpectCiliumNotRunning(vm)
	})

	AfterAll(func() {
		err := vm.RestartCilium()
		Expect(err).Should(BeNil(), "Failed to restart Cilium")
		vm.CloseSSHClient()
	})

	It("Run Tests", func() {
		path, _ := filepath.Split(vm.BasePath())
		res := vm.ExecWithSudo(fmt.Sprintf("make -C %s tests-privileged", path))
		res.ExpectSuccess("Failed to run privileged unit tests")
	})
})
<commit_msg>test: Increase timeout for privileged unit tests<commit_after>// Copyright 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 RuntimeTest

import (
	"context"
	"fmt"
	"path/filepath"
	"time"

	. "github.com/cilium/cilium/test/ginkgo-ext"
	"github.com/cilium/cilium/test/helpers"

	. "github.com/onsi/gomega"
)

const (
	// The privileged unit tests can take more than 4 minutes, the default
	// timeout for helper commands.
	privilegedUnitTestTimeout = 8 * time.Minute
)

var _ = Describe("RuntimePrivilegedUnitTests", func() {

	var vm *helpers.SSHMeta

	BeforeAll(func() {
		vm = helpers.InitRuntimeHelper(helpers.Runtime, logger)
		res := vm.ExecWithSudo("systemctl stop cilium")
		res.ExpectSuccess("Failed trying to stop cilium via systemctl")
		ExpectCiliumNotRunning(vm)
	})

	AfterAll(func() {
		err := vm.RestartCilium()
		Expect(err).Should(BeNil(), "Failed to restart Cilium")
		vm.CloseSSHClient()
	})

	It("Run Tests", func() {
		path, _ := filepath.Split(vm.BasePath())
		ctx, cancel := context.WithTimeout(context.Background(), privilegedUnitTestTimeout)
		defer cancel()
		res := vm.ExecContext(ctx, fmt.Sprintf("sudo make -C %s tests-privileged", path))
		res.ExpectSuccess("Failed to run privileged unit tests")
	})
})
<|endoftext|>


<commit_before>package empire

import (
	"fmt"
	"time"

	"github.com/jinzhu/gorm"
	"github.com/remind101/empire/empire/pkg/service"
	"github.com/remind101/pkg/timex"
	"golang.org/x/net/context"
)

// Release is a combination of a Config and a Slug, which form a deployable
// release.
type Release struct {
	ID      string
	Version int

	AppID string
	App   *App

	ConfigID string
	Config   *Config

	SlugID string
	Slug   *Slug

	Processes []*Process

	Description string
	CreatedAt   *time.Time
}

func (r *Release) Formation() Formation {
	f := make(Formation)
	for _, p := range r.Processes {
		f[p.Type] = p
	}
	return f
}

// Set created_at before inserting.
func (r *Release) BeforeCreate() error {
	t := timex.Now()
	r.CreatedAt = &t
	return nil
}

// ReleasesQuery is a Scope implementation for common things to filter releases
// by.
type ReleasesQuery struct {
	// If Provided, an app to filter by.
	App *App

	// If provided, a version to filter by.
	Version *int
}

// Scope implements the Scope interface.
func (q ReleasesQuery) Scope(db *gorm.DB) *gorm.DB {
	var scope ComposedScope

	if app := q.App; app != nil {
		scope = append(scope, FieldEquals("app_id", app.ID))
	}

	if version := q.Version; version != nil {
		scope = append(scope, FieldEquals("version", *version))
	}

	// Preload all the things.
	scope = append(scope, Preload("App", "Config", "Slug", "Processes"))
	scope = append(scope, Order("version desc"))

	return scope.Scope(db)
}

// ReleasesFirst returns the first matching release.
func (s *store) ReleasesFirst(scope Scope) (*Release, error) {
	var release Release
	// TODO: Wrap the store with this. Gorm blows up when preloading
	// App.Certificates on a collection of releases.
	scope = ComposedScope{scope, Preload("App.Certificates")}
	return &release, s.First(scope, &release)
}

// Releases returns all releases matching the scope.
func (s *store) Releases(scope Scope) ([]*Release, error) {
	var releases []*Release
	return releases, s.Find(scope, &releases)
}

// ReleasesCreate persists a release.
func (s *store) ReleasesCreate(r *Release) (*Release, error) {
	return releasesCreate(s.db, r)
}

// releasesService is a service for creating and rolling back a Release.
type releasesService struct {
	store    *store
	releaser *releaser
}

// ReleasesCreate creates the release, then sets the current process formation on the release.
func (s *releasesService) ReleasesCreate(ctx context.Context, r *Release) (*Release, error) {
	// Create a new formation for this release.
	if err := s.createFormation(r); err != nil {
		return nil, err
	}

	r, err := s.store.ReleasesCreate(r)
	if err != nil {
		return r, err
	}

	// Create port mappings for formation.
	if err := s.newProcessPorts(r); err != nil {
		return nil, err
	}

	// Schedule the new release onto the cluster.
	return r, s.releaser.Release(ctx, r)
}

func (s *releasesService) createFormation(release *Release) error {
	var existing Formation

	// Get the old release, so we can copy the Formation.
	last, err := s.store.ReleasesFirst(ReleasesQuery{App: release.App})
	if err != nil {
		if err != gorm.RecordNotFound {
			return err
		}
	} else {
		existing = last.Formation()
	}

	f := NewFormation(existing, release.Slug.ProcessTypes)
	release.Processes = f.Processes()

	return nil
}

// newProcessPorts returns a map of ports for a release. It will allocate new ports to an app if need be.
func (s *releasesService) newProcessPorts(r *Release) error {
	for _, p := range r.Processes {
		if p.Type == WebProcessType {
			// TODO: Support a port per process, allowing more than one process to expose a port.
			port, err := s.store.PortsFindOrCreateByApp(r.App)
			if err != nil {
				return err
			}
			p.Port = port.Port
		}
	}
	return nil
}

// Rolls back to a specific release version.
func (s *releasesService) ReleasesRollback(ctx context.Context, app *App, version int) (*Release, error) {
	r, err := s.store.ReleasesFirst(ReleasesQuery{App: app, Version: &version})
	if err != nil {
		return nil, err
	}

	desc := fmt.Sprintf("Rollback to v%d", version)
	return s.ReleasesCreate(ctx, &Release{
		App:         app,
		Config:      r.Config,
		Slug:        r.Slug,
		Description: desc,
	})
}

// ReleasesLastVersion returns the last ReleaseVersion for the given App. This
// function also ensures that the last release is locked until the transaction
// is commited, so the release version can be incremented atomically.
func releasesLastVersion(db *gorm.DB, appID string) (int, error) {
	var version int

	rows, err := db.Raw(`select version from releases where app_id = ? order by version desc for update`, appID).Rows()
	if err != nil {
		return version, err
	}
	defer rows.Close()

	for rows.Next() {
		err := rows.Scan(&version)
		return version, err
	}

	return version, nil
}

// releasesCreate creates a new Release and inserts it into the database.
func releasesCreate(db *gorm.DB, release *Release) (*Release, error) {
	t := db.Begin()

	// Get the last release version for this app.
	v, err := releasesLastVersion(t, release.App.ID)
	if err != nil {
		t.Rollback()
		return release, err
	}

	// Increment the release version.
	release.Version = v + 1

	if err := t.Create(release).Error; err != nil {
		t.Rollback()
		return release, err
	}

	if err := t.Commit().Error; err != nil {
		t.Rollback()
		return release, err
	}

	return release, nil
}

type releaser struct {
	manager service.Manager
}

// ScheduleRelease creates jobs for every process and instance count and
// schedules them onto the cluster.
func (r *releaser) Release(ctx context.Context, release *Release) error {
	a := newServiceApp(release)
	return r.manager.Submit(ctx, a)
}

func newServiceApp(release *Release) *service.App {
	var processes []*service.Process

	for _, p := range release.Processes {
		processes = append(processes, newServiceProcess(release, p))
	}

	return &service.App{
		ID:        release.App.ID,
		Name:      release.App.Name,
		Processes: processes,
	}
}

func newServiceProcess(release *Release, p *Process) *service.Process {
	var procExp service.Exposure
	ports := newServicePorts(int64(p.Port))

	env := environment(release.Config.Vars)
	env["EMPIRE_APPNAME"] = release.App.Name
	env["EMPIRE_PROCESS"] = string(p.Type)
	env["EMPIRE_RELEASE"] = fmt.Sprintf("%d", release.Version)
	env["SOURCE"] = fmt.Sprintf("%s.%s", release.App.Name, p.Type)

	if len(ports) > 0 {
		env["PORT"] = fmt.Sprintf("%d", *ports[0].Container)

		// If we have exposed ports, set process exposure to apps exposure
		procExp = serviceExposure(release.App.Exposure)
	}

	cert := serviceSSLCertName(release.App.Certificates)

	return &service.Process{
		Type:        string(p.Type),
		Env:         env,
		Command:     string(p.Command),
		Image:       release.Slug.Image.String(),
		Instances:   uint(p.Quantity),
		MemoryLimit: MemoryLimit,
		CPUShares:   CPUShare,
		Ports:       ports,
		Exposure:    procExp,
		SSLCert:     cert,
	}
}

func newServicePorts(hostPort int64) []service.PortMap {
	var ports []service.PortMap
	if hostPort != 0 {
		// TODO: We can just map the same host port as the container port, as we make it
		// available as $PORT in the env vars.
		port := int64(WebPort)
		ports = append(ports, service.PortMap{
			Host:      &hostPort,
			Container: &port,
		})
	}
	return ports
}

// environment coerces a Vars into a map[string]string.
func environment(vars Vars) map[string]string {
	env := make(map[string]string)

	for k, v := range vars {
		env[string(k)] = string(v)
	}

	return env
}

func serviceExposure(appExp string) (exp service.Exposure) {
	switch appExp {
	case ExposePrivate:
		exp = service.ExposePrivate
	case ExposePublic:
		exp = service.ExposePublic
	default:
		exp = service.ExposeNone
	}

	return exp
}

func serviceSSLCertName(certs []*Certificate) (name string) {
	if len(certs) > 0 {
		name = certs[0].Name
	}
	return name
}
<commit_msg>Add release version to SOURCE.<commit_after>package empire

import (
	"fmt"
	"time"

	"github.com/jinzhu/gorm"
	"github.com/remind101/empire/empire/pkg/service"
	"github.com/remind101/pkg/timex"
	"golang.org/x/net/context"
)

// Release is a combination of a Config and a Slug, which form a deployable
// release.
type Release struct {
	ID      string
	Version int

	AppID string
	App   *App

	ConfigID string
	Config   *Config

	SlugID string
	Slug   *Slug

	Processes []*Process

	Description string
	CreatedAt   *time.Time
}

func (r *Release) Formation() Formation {
	f := make(Formation)
	for _, p := range r.Processes {
		f[p.Type] = p
	}
	return f
}

// Set created_at before inserting.
func (r *Release) BeforeCreate() error {
	t := timex.Now()
	r.CreatedAt = &t
	return nil
}

// ReleasesQuery is a Scope implementation for common things to filter releases
// by.
type ReleasesQuery struct {
	// If Provided, an app to filter by.
	App *App

	// If provided, a version to filter by.
	Version *int
}

// Scope implements the Scope interface.
func (q ReleasesQuery) Scope(db *gorm.DB) *gorm.DB {
	var scope ComposedScope

	if app := q.App; app != nil {
		scope = append(scope, FieldEquals("app_id", app.ID))
	}

	if version := q.Version; version != nil {
		scope = append(scope, FieldEquals("version", *version))
	}

	// Preload all the things.
	scope = append(scope, Preload("App", "Config", "Slug", "Processes"))
	scope = append(scope, Order("version desc"))

	return scope.Scope(db)
}

// ReleasesFirst returns the first matching release.
func (s *store) ReleasesFirst(scope Scope) (*Release, error) {
	var release Release
	// TODO: Wrap the store with this. Gorm blows up when preloading
	// App.Certificates on a collection of releases.
	scope = ComposedScope{scope, Preload("App.Certificates")}
	return &release, s.First(scope, &release)
}

// Releases returns all releases matching the scope.
func (s *store) Releases(scope Scope) ([]*Release, error) {
	var releases []*Release
	return releases, s.Find(scope, &releases)
}

// ReleasesCreate persists a release.
func (s *store) ReleasesCreate(r *Release) (*Release, error) {
	return releasesCreate(s.db, r)
}

// releasesService is a service for creating and rolling back a Release.
type releasesService struct {
	store    *store
	releaser *releaser
}

// ReleasesCreate creates the release, then sets the current process formation on the release.
func (s *releasesService) ReleasesCreate(ctx context.Context, r *Release) (*Release, error) {
	// Create a new formation for this release.
	if err := s.createFormation(r); err != nil {
		return nil, err
	}

	r, err := s.store.ReleasesCreate(r)
	if err != nil {
		return r, err
	}

	// Create port mappings for formation.
	if err := s.newProcessPorts(r); err != nil {
		return nil, err
	}

	// Schedule the new release onto the cluster.
	return r, s.releaser.Release(ctx, r)
}

func (s *releasesService) createFormation(release *Release) error {
	var existing Formation

	// Get the old release, so we can copy the Formation.
	last, err := s.store.ReleasesFirst(ReleasesQuery{App: release.App})
	if err != nil {
		if err != gorm.RecordNotFound {
			return err
		}
	} else {
		existing = last.Formation()
	}

	f := NewFormation(existing, release.Slug.ProcessTypes)
	release.Processes = f.Processes()

	return nil
}

// newProcessPorts returns a map of ports for a release. It will allocate new ports to an app if need be.
func (s *releasesService) newProcessPorts(r *Release) error {
	for _, p := range r.Processes {
		if p.Type == WebProcessType {
			// TODO: Support a port per process, allowing more than one process to expose a port.
			port, err := s.store.PortsFindOrCreateByApp(r.App)
			if err != nil {
				return err
			}
			p.Port = port.Port
		}
	}
	return nil
}

// Rolls back to a specific release version.
func (s *releasesService) ReleasesRollback(ctx context.Context, app *App, version int) (*Release, error) {
	r, err := s.store.ReleasesFirst(ReleasesQuery{App: app, Version: &version})
	if err != nil {
		return nil, err
	}

	desc := fmt.Sprintf("Rollback to v%d", version)
	return s.ReleasesCreate(ctx, &Release{
		App:         app,
		Config:      r.Config,
		Slug:        r.Slug,
		Description: desc,
	})
}

// ReleasesLastVersion returns the last ReleaseVersion for the given App. This
// function also ensures that the last release is locked until the transaction
// is commited, so the release version can be incremented atomically.
func releasesLastVersion(db *gorm.DB, appID string) (int, error) {
	var version int

	rows, err := db.Raw(`select version from releases where app_id = ? order by version desc for update`, appID).Rows()
	if err != nil {
		return version, err
	}
	defer rows.Close()

	for rows.Next() {
		err := rows.Scan(&version)
		return version, err
	}

	return version, nil
}

// releasesCreate creates a new Release and inserts it into the database.
func releasesCreate(db *gorm.DB, release *Release) (*Release, error) {
	t := db.Begin()

	// Get the last release version for this app.
	v, err := releasesLastVersion(t, release.App.ID)
	if err != nil {
		t.Rollback()
		return release, err
	}

	// Increment the release version.
	release.Version = v + 1

	if err := t.Create(release).Error; err != nil {
		t.Rollback()
		return release, err
	}

	if err := t.Commit().Error; err != nil {
		t.Rollback()
		return release, err
	}

	return release, nil
}

type releaser struct {
	manager service.Manager
}

// ScheduleRelease creates jobs for every process and instance count and
// schedules them onto the cluster.
func (r *releaser) Release(ctx context.Context, release *Release) error {
	a := newServiceApp(release)
	return r.manager.Submit(ctx, a)
}

func newServiceApp(release *Release) *service.App {
	var processes []*service.Process

	for _, p := range release.Processes {
		processes = append(processes, newServiceProcess(release, p))
	}

	return &service.App{
		ID:        release.App.ID,
		Name:      release.App.Name,
		Processes: processes,
	}
}

func newServiceProcess(release *Release, p *Process) *service.Process {
	var procExp service.Exposure
	ports := newServicePorts(int64(p.Port))

	env := environment(release.Config.Vars)
	env["EMPIRE_APPNAME"] = release.App.Name
	env["EMPIRE_PROCESS"] = string(p.Type)
	env["EMPIRE_RELEASE"] = fmt.Sprintf("%d", release.Version)
	env["SOURCE"] = fmt.Sprintf("%s.%s.%d", release.App.Name, p.Type, release.Version)

	if len(ports) > 0 {
		env["PORT"] = fmt.Sprintf("%d", *ports[0].Container)

		// If we have exposed ports, set process exposure to apps exposure
		procExp = serviceExposure(release.App.Exposure)
	}

	cert := serviceSSLCertName(release.App.Certificates)

	return &service.Process{
		Type:        string(p.Type),
		Env:         env,
		Command:     string(p.Command),
		Image:       release.Slug.Image.String(),
		Instances:   uint(p.Quantity),
		MemoryLimit: MemoryLimit,
		CPUShares:   CPUShare,
		Ports:       ports,
		Exposure:    procExp,
		SSLCert:     cert,
	}
}

func newServicePorts(hostPort int64) []service.PortMap {
	var ports []service.PortMap
	if hostPort != 0 {
		// TODO: We can just map the same host port as the container port, as we make it
		// available as $PORT in the env vars.
		port := int64(WebPort)
		ports = append(ports, service.PortMap{
			Host:      &hostPort,
			Container: &port,
		})
	}
	return ports
}

// environment coerces a Vars into a map[string]string.
func environment(vars Vars) map[string]string {
	env := make(map[string]string)

	for k, v := range vars {
		env[string(k)] = string(v)
	}

	return env
}

func serviceExposure(appExp string) (exp service.Exposure) {
	switch appExp {
	case ExposePrivate:
		exp = service.ExposePrivate
	case ExposePublic:
		exp = service.ExposePublic
	default:
		exp = service.ExposeNone
	}

	return exp
}

func serviceSSLCertName(certs []*Certificate) (name string) {
	if len(certs) > 0 {
		name = certs[0].Name
	}
	return name
}
<|endoftext|>