<commit_before>/*
Copyright 2014 The Kubernetes Authors.

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

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

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

package cloudprovider

import (
	"errors"
	"fmt"
	"strings"

	"k8s.io/kubernetes/pkg/api"
)

// Interface is an abstract, pluggable interface for cloud providers.
type Interface interface {
	// LoadBalancer returns a balancer interface. Also returns true if the interface is supported, false otherwise.
	LoadBalancer() (LoadBalancer, bool)
	// Instances returns an instances interface. Also returns true if the interface is supported, false otherwise.
	Instances() (Instances, bool)
	// Zones returns a zones interface. Also returns true if the interface is supported, false otherwise.
	Zones() (Zones, bool)
	// Clusters returns a clusters interface.  Also returns true if the interface is supported, false otherwise.
	Clusters() (Clusters, bool)
	// Routes returns a routes interface along with whether the interface is supported.
	Routes() (Routes, bool)
	// ProviderName returns the cloud provider ID.
	ProviderName() string
	// ScrubDNS provides an opportunity for cloud-provider-specific code to process DNS settings for pods.
	ScrubDNS(nameservers, searches []string) (nsOut, srchOut []string)
}

// Clusters is an abstract, pluggable interface for clusters of containers.
type Clusters interface {
	// ListClusters lists the names of the available clusters.
	ListClusters() ([]string, error)
	// Master gets back the address (either DNS name or IP address) of the master node for the cluster.
	Master(clusterName string) (string, error)
}

// TODO(#6812): Use a shorter name that's less likely to be longer than cloud
// providers' name length limits.
func GetLoadBalancerName(service *api.Service) string {
	//GCE requires that the name of a load balancer starts with a lower case letter.
	ret := "a" + string(service.UID)
	ret = strings.Replace(ret, "-", "", -1)
	//AWS requires that the name of a load balancer is shorter than 32 bytes.
	if len(ret) > 32 {
		ret = ret[:32]
	}
	return ret
}

func GetInstanceProviderID(cloud Interface, nodeName string) (string, error) {
	instances, ok := cloud.Instances()
	if !ok {
		return "", fmt.Errorf("failed to get instances from cloud provider")
	}
	instanceID, err := instances.InstanceID(nodeName)
	if err != nil {
		return "", fmt.Errorf("failed to get instance ID from cloud provider: %v", err)
	}
	return cloud.ProviderName() + "://" + instanceID, nil
}

// LoadBalancer is an abstract, pluggable interface for load balancers.
type LoadBalancer interface {
	// TODO: Break this up into different interfaces (LB, etc) when we have more than one type of service
	// GetLoadBalancer returns whether the specified load balancer exists, and
	// if so, what its status is.
	// Implementations must treat the *api.Service parameter as read-only and not modify it.
	GetLoadBalancer(service *api.Service) (status *api.LoadBalancerStatus, exists bool, err error)
	// EnsureLoadBalancer creates a new load balancer 'name', or updates the existing one. Returns the status of the balancer
	// Implementations must treat the *api.Service parameter as read-only and not modify it.
	EnsureLoadBalancer(service *api.Service, hosts []string) (*api.LoadBalancerStatus, error)
	// UpdateLoadBalancer updates hosts under the specified load balancer.
	// Implementations must treat the *api.Service parameter as read-only and not modify it.
	UpdateLoadBalancer(service *api.Service, hosts []string) error
	// EnsureLoadBalancerDeleted deletes the specified load balancer if it
	// exists, returning nil if the load balancer specified either didn't exist or
	// was successfully deleted.
	// This construction is useful because many cloud providers' load balancers
	// have multiple underlying components, meaning a Get could say that the LB
	// doesn't exist even if some part of it is still laying around.
	// Implementations must treat the *api.Service parameter as read-only and not modify it.
	EnsureLoadBalancerDeleted(service *api.Service) error
}

// Instances is an abstract, pluggable interface for sets of instances.
type Instances interface {
	// NodeAddresses returns the addresses of the specified instance.
	// TODO(roberthbailey): This currently is only used in such a way that it
	// returns the address of the calling instance. We should do a rename to
	// make this clearer.
	NodeAddresses(name string) ([]api.NodeAddress, error)
	// ExternalID returns the cloud provider ID of the specified instance (deprecated).
	ExternalID(name string) (string, error)
	// InstanceID returns the cloud provider ID of the specified instance.
	// Note that if the instance does not exist or is no longer running, we must return ("", cloudprovider.InstanceNotFound)
	InstanceID(name string) (string, error)
	// InstanceType returns the type of the specified instance.
	// Note that if the instance does not exist or is no longer running, we must return ("", cloudprovider.InstanceNotFound)
	InstanceType(name string) (string, error)
	// List lists instances that match 'filter' which is a regular expression which must match the entire instance name (fqdn)
	List(filter string) ([]string, error)
	// AddSSHKeyToAllInstances adds an SSH public key as a legal identity for all instances
	// expected format for the key is standard ssh-keygen format: <protocol> <blob>
	AddSSHKeyToAllInstances(user string, keyData []byte) error
	// CurrentNodeName returns the name of the node we are currently running on
	// On most clouds (e.g. GCE) this is the hostname, so we provide the hostname
	CurrentNodeName(hostname string) (string, error)
}

// Route is a representation of an advanced routing rule.
type Route struct {
	// Name is the name of the routing rule in the cloud-provider.
	// It will be ignored in a Create (although nameHint may influence it)
	Name string
	// TargetInstance is the name of the instance as specified in routing rules
	// for the cloud-provider (in gce: the Instance Name).
	TargetInstance string
	// DestinationCIDR is the CIDR format IP range that this routing rule
	// applies to.
	DestinationCIDR string
}

// Routes is an abstract, pluggable interface for advanced routing rules.
type Routes interface {
	// ListRoutes lists all managed routes that belong to the specified clusterName
	ListRoutes(clusterName string) ([]*Route, error)
	// CreateRoute creates the described managed route
	// route.Name will be ignored, although the cloud-provider may use nameHint
	// to create a more user-meaningful name.
	CreateRoute(clusterName string, nameHint string, route *Route) error
	// DeleteRoute deletes the specified managed route
	// Route should be as returned by ListRoutes
	DeleteRoute(clusterName string, route *Route) error
}

var InstanceNotFound = errors.New("instance not found")

// Zone represents the location of a particular machine.
type Zone struct {
	FailureDomain string
	Region        string
}

// Zones is an abstract, pluggable interface for zone enumeration.
type Zones interface {
	// GetZone returns the Zone containing the current failure zone and locality region that the program is running in
	GetZone() (Zone, error)
}
<commit_msg>Correctly document cloudprovider Instances contract<commit_after>/*
Copyright 2014 The Kubernetes Authors.

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

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

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

package cloudprovider

import (
	"errors"
	"fmt"
	"strings"

	"k8s.io/kubernetes/pkg/api"
)

// Interface is an abstract, pluggable interface for cloud providers.
type Interface interface {
	// LoadBalancer returns a balancer interface. Also returns true if the interface is supported, false otherwise.
	LoadBalancer() (LoadBalancer, bool)
	// Instances returns an instances interface. Also returns true if the interface is supported, false otherwise.
	Instances() (Instances, bool)
	// Zones returns a zones interface. Also returns true if the interface is supported, false otherwise.
	Zones() (Zones, bool)
	// Clusters returns a clusters interface.  Also returns true if the interface is supported, false otherwise.
	Clusters() (Clusters, bool)
	// Routes returns a routes interface along with whether the interface is supported.
	Routes() (Routes, bool)
	// ProviderName returns the cloud provider ID.
	ProviderName() string
	// ScrubDNS provides an opportunity for cloud-provider-specific code to process DNS settings for pods.
	ScrubDNS(nameservers, searches []string) (nsOut, srchOut []string)
}

// Clusters is an abstract, pluggable interface for clusters of containers.
type Clusters interface {
	// ListClusters lists the names of the available clusters.
	ListClusters() ([]string, error)
	// Master gets back the address (either DNS name or IP address) of the master node for the cluster.
	Master(clusterName string) (string, error)
}

// TODO(#6812): Use a shorter name that's less likely to be longer than cloud
// providers' name length limits.
func GetLoadBalancerName(service *api.Service) string {
	//GCE requires that the name of a load balancer starts with a lower case letter.
	ret := "a" + string(service.UID)
	ret = strings.Replace(ret, "-", "", -1)
	//AWS requires that the name of a load balancer is shorter than 32 bytes.
	if len(ret) > 32 {
		ret = ret[:32]
	}
	return ret
}

func GetInstanceProviderID(cloud Interface, nodeName string) (string, error) {
	instances, ok := cloud.Instances()
	if !ok {
		return "", fmt.Errorf("failed to get instances from cloud provider")
	}
	instanceID, err := instances.InstanceID(nodeName)
	if err != nil {
		return "", fmt.Errorf("failed to get instance ID from cloud provider: %v", err)
	}
	return cloud.ProviderName() + "://" + instanceID, nil
}

// LoadBalancer is an abstract, pluggable interface for load balancers.
type LoadBalancer interface {
	// TODO: Break this up into different interfaces (LB, etc) when we have more than one type of service
	// GetLoadBalancer returns whether the specified load balancer exists, and
	// if so, what its status is.
	// Implementations must treat the *api.Service parameter as read-only and not modify it.
	GetLoadBalancer(service *api.Service) (status *api.LoadBalancerStatus, exists bool, err error)
	// EnsureLoadBalancer creates a new load balancer 'name', or updates the existing one. Returns the status of the balancer
	// Implementations must treat the *api.Service parameter as read-only and not modify it.
	EnsureLoadBalancer(service *api.Service, hosts []string) (*api.LoadBalancerStatus, error)
	// UpdateLoadBalancer updates hosts under the specified load balancer.
	// Implementations must treat the *api.Service parameter as read-only and not modify it.
	UpdateLoadBalancer(service *api.Service, hosts []string) error
	// EnsureLoadBalancerDeleted deletes the specified load balancer if it
	// exists, returning nil if the load balancer specified either didn't exist or
	// was successfully deleted.
	// This construction is useful because many cloud providers' load balancers
	// have multiple underlying components, meaning a Get could say that the LB
	// doesn't exist even if some part of it is still laying around.
	// Implementations must treat the *api.Service parameter as read-only and not modify it.
	EnsureLoadBalancerDeleted(service *api.Service) error
}

// Instances is an abstract, pluggable interface for sets of instances.
type Instances interface {
	// NodeAddresses returns the addresses of the specified instance.
	// TODO(roberthbailey): This currently is only used in such a way that it
	// returns the address of the calling instance. We should do a rename to
	// make this clearer.
	NodeAddresses(name string) ([]api.NodeAddress, error)
	// ExternalID returns the cloud provider ID of the specified instance (deprecated).
	// Note that if the instance does not exist or is no longer running, we must return ("", cloudprovider.InstanceNotFound)
	ExternalID(name string) (string, error)
	// InstanceID returns the cloud provider ID of the specified instance.
	InstanceID(name string) (string, error)
	// InstanceType returns the type of the specified instance.
	InstanceType(name string) (string, error)
	// List lists instances that match 'filter' which is a regular expression which must match the entire instance name (fqdn)
	List(filter string) ([]string, error)
	// AddSSHKeyToAllInstances adds an SSH public key as a legal identity for all instances
	// expected format for the key is standard ssh-keygen format: <protocol> <blob>
	AddSSHKeyToAllInstances(user string, keyData []byte) error
	// CurrentNodeName returns the name of the node we are currently running on
	// On most clouds (e.g. GCE) this is the hostname, so we provide the hostname
	CurrentNodeName(hostname string) (string, error)
}

// Route is a representation of an advanced routing rule.
type Route struct {
	// Name is the name of the routing rule in the cloud-provider.
	// It will be ignored in a Create (although nameHint may influence it)
	Name string
	// TargetInstance is the name of the instance as specified in routing rules
	// for the cloud-provider (in gce: the Instance Name).
	TargetInstance string
	// DestinationCIDR is the CIDR format IP range that this routing rule
	// applies to.
	DestinationCIDR string
}

// Routes is an abstract, pluggable interface for advanced routing rules.
type Routes interface {
	// ListRoutes lists all managed routes that belong to the specified clusterName
	ListRoutes(clusterName string) ([]*Route, error)
	// CreateRoute creates the described managed route
	// route.Name will be ignored, although the cloud-provider may use nameHint
	// to create a more user-meaningful name.
	CreateRoute(clusterName string, nameHint string, route *Route) error
	// DeleteRoute deletes the specified managed route
	// Route should be as returned by ListRoutes
	DeleteRoute(clusterName string, route *Route) error
}

var InstanceNotFound = errors.New("instance not found")

// Zone represents the location of a particular machine.
type Zone struct {
	FailureDomain string
	Region        string
}

// Zones is an abstract, pluggable interface for zone enumeration.
type Zones interface {
	// GetZone returns the Zone containing the current failure zone and locality region that the program is running in
	GetZone() (Zone, error)
}
<|endoftext|>


<commit_before>// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package database

import (
	"fmt"
	"strings"
	"time"

	"github.com/jinzhu/gorm"
)

type OSType int

// Display prints the name of the OSType. Note this CANNOT be named String()
// because, if it is, Go's text/template package will automatically call
// String() and cause you to lose hours upon hours of your life debuggin when
// forms are suddenly broken.
func (o OSType) Display() string {
	switch o {
	case OSTypeIOS:
		return "iOS"
	case OSTypeAndroid:
		return "Android"
	default:
		return "Unknown"
	}
}

const (
	OSTypeInvalid OSType = iota
	OSTypeIOS
	OSTypeAndroid
)

var _ Auditable = (*MobileApp)(nil)

type MobileApp struct {
	gorm.Model
	Errorable

	// Name is the name of the app.
	Name string `gorm:"column:name; type:citext;"`

	// RealmID is the id of the mobile app.
	RealmID uint `gorm:"column:realm_id;"`

	// URL is the link to the app in it's appstore.
	URL    string  `gorm:"-"`
	URLPtr *string `gorm:"column:url; type:text"`

	// OS is the type of the application we're using (eg, iOS, Android).
	OS OSType `gorm:"column:os; type:int;"`

	// IOSAppID is a unique string representing the app.
	AppID string `gorm:"column:app_id; type:varchar(512);"`

	// SHA is a unique hash of the app.
	// It is only present for Android devices, and should be of the form:
	//   AA:BB:CC:DD...
	SHA string `gorm:"column:sha; type:text;"`
}

func (a *MobileApp) BeforeSave(tx *gorm.DB) error {
	a.Name = strings.TrimSpace(a.Name)
	if a.Name == "" {
		a.AddError("name", "is required")
	}

	if a.RealmID == 0 {
		a.AddError("realm_id", "is required")
	}

	a.AppID = strings.TrimSpace(a.AppID)
	if a.AppID == "" {
		a.AddError("app_id", "is required")
	}

	a.URL = strings.TrimSpace(a.URL)
	if a.URL != "" {
		a.URLPtr = stringPtr(a.URL)
	}

	// Ensure OS is valid
	if a.OS < OSTypeIOS || a.OS > OSTypeAndroid {
		a.AddError("os", "is invalid")
	}

	// SHA is required for Android
	a.SHA = strings.TrimSpace(a.SHA)
	if a.OS == OSTypeAndroid {
		if a.SHA == "" {
			a.AddError("sha", "is required for Android apps")
		}
	}

	// Process and clean SHAs
	var shas []string
	for _, line := range strings.Split(a.SHA, "\n") {
		for _, entry := range strings.Split(line, ",") {
			entry = strings.ToUpper(strings.TrimSpace(entry))
			if entry == "" {
				continue
			}

			if len(entry) != 95 {
				a.AddError("sha", "is not 95 characters")
				continue
			}

			if entry != "" {
				shas = append(shas, entry)
			}
		}
	}
	a.SHA = strings.Join(shas, "\n")

	if len(a.Errors()) > 0 {
		return fmt.Errorf("validation failed")
	}

	return nil
}

// ListActiveAppsByOS finds mobile apps by their realm and OS.
func (db *Database) ListActiveAppsByOS(realmID uint, os OSType) ([]*MobileApp, error) {
	// Find the apps.
	var apps []*MobileApp
	if err := db.db.
		Model(&MobileApp{}).
		Where("realm_id = ? AND os = ?", realmID, os).
		Find(&apps).
		Error; err != nil {
		if IsNotFound(err) {
			return apps, nil
		}
		return nil, err
	}
	return apps, nil
}

// SaveMobileApp saves the mobile app.
func (db *Database) SaveMobileApp(a *MobileApp, actor Auditable) error {
	if a == nil {
		return fmt.Errorf("provided mobile app is nil")
	}

	if actor == nil {
		return fmt.Errorf("auditing actor is nil")
	}

	return db.db.Transaction(func(tx *gorm.DB) error {
		var audits []*AuditEntry

		var existing MobileApp
		if err := tx.
			Unscoped().
			Model(&MobileApp{}).
			Where("id = ?", a.ID).
			First(&existing).
			Error; err != nil && !IsNotFound(err) {
			return fmt.Errorf("failed to get existing mobile app")
		}

		// Save the app
		if err := tx.Unscoped().Save(a).Error; err != nil {
			return fmt.Errorf("failed to save mobile app: %w", err)
		}

		// Brand new app?
		if existing.ID == 0 {
			audit := BuildAuditEntry(actor, "created mobile app", a, a.RealmID)
			audits = append(audits, audit)
		} else {
			if existing.Name != a.Name {
				audit := BuildAuditEntry(actor, "updated mobile app name", a, a.RealmID)
				audit.Diff = stringDiff(existing.Name, a.Name)
				audits = append(audits, audit)
			}

			if existing.OS != a.OS {
				audit := BuildAuditEntry(actor, "updated mobile app os", a, a.RealmID)
				audit.Diff = stringDiff(existing.OS.Display(), a.OS.Display())
				audits = append(audits, audit)
			}

			if existing.AppID != a.AppID {
				audit := BuildAuditEntry(actor, "updated mobile app appID", a, a.RealmID)
				audit.Diff = stringDiff(existing.AppID, a.AppID)
				audits = append(audits, audit)
			}

			if existing.SHA != a.SHA {
				audit := BuildAuditEntry(actor, "updated mobile app sha", a, a.RealmID)
				audit.Diff = stringDiff(existing.SHA, a.SHA)
				audits = append(audits, audit)
			}

			if existing.DeletedAt != a.DeletedAt {
				audit := BuildAuditEntry(actor, "updated mobile app enabled", a, a.RealmID)
				audit.Diff = boolDiff(existing.DeletedAt == nil, a.DeletedAt == nil)
				audits = append(audits, audit)
			}
		}

		// Save all audits
		for _, audit := range audits {
			if err := tx.Save(audit).Error; err != nil {
				return fmt.Errorf("failed to save audits: %w", err)
			}
		}

		return nil
	})
}

func (a *MobileApp) AuditID() string {
	return fmt.Sprintf("mobile_apps:%d", a.ID)
}

func (a *MobileApp) AuditDisplay() string {
	return fmt.Sprintf("%s (%s)", a.Name, a.OS.Display())
}

func (a *MobileApp) AfterFind(tx *gorm.DB) error {
	a.URL = stringValue(a.URLPtr)
	return nil
}

// PurgeMobileApps will delete mobile apps that have been deleted for more than
// the specified time.
func (db *Database) PurgeMobileApps(maxAge time.Duration) (int64, error) {
	if maxAge > 0 {
		maxAge = -1 * maxAge
	}
	deleteBefore := time.Now().UTC().Add(maxAge)

	result := db.db.
		Unscoped().
		Where("deleted_at IS NOT NULL AND deleted_at < ?", deleteBefore).
		Delete(&MobileApp{})
	return result.RowsAffected, result.Error
}
<commit_msg>Allow blank entry for URL. (#846)<commit_after>// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package database

import (
	"fmt"
	"strings"
	"time"

	"github.com/jinzhu/gorm"
)

type OSType int

// Display prints the name of the OSType. Note this CANNOT be named String()
// because, if it is, Go's text/template package will automatically call
// String() and cause you to lose hours upon hours of your life debuggin when
// forms are suddenly broken.
func (o OSType) Display() string {
	switch o {
	case OSTypeIOS:
		return "iOS"
	case OSTypeAndroid:
		return "Android"
	default:
		return "Unknown"
	}
}

const (
	OSTypeInvalid OSType = iota
	OSTypeIOS
	OSTypeAndroid
)

var _ Auditable = (*MobileApp)(nil)

type MobileApp struct {
	gorm.Model
	Errorable

	// Name is the name of the app.
	Name string `gorm:"column:name; type:citext;"`

	// RealmID is the id of the mobile app.
	RealmID uint `gorm:"column:realm_id;"`

	// URL is the link to the app in it's appstore.
	URL    string  `gorm:"-"`
	URLPtr *string `gorm:"column:url; type:text"`

	// OS is the type of the application we're using (eg, iOS, Android).
	OS OSType `gorm:"column:os; type:int;"`

	// IOSAppID is a unique string representing the app.
	AppID string `gorm:"column:app_id; type:varchar(512);"`

	// SHA is a unique hash of the app.
	// It is only present for Android devices, and should be of the form:
	//   AA:BB:CC:DD...
	SHA string `gorm:"column:sha; type:text;"`
}

func (a *MobileApp) BeforeSave(tx *gorm.DB) error {
	a.Name = strings.TrimSpace(a.Name)
	if a.Name == "" {
		a.AddError("name", "is required")
	}

	if a.RealmID == 0 {
		a.AddError("realm_id", "is required")
	}

	a.AppID = strings.TrimSpace(a.AppID)
	if a.AppID == "" {
		a.AddError("app_id", "is required")
	}

	a.URL = strings.TrimSpace(a.URL)
	a.URLPtr = stringPtr(a.URL)

	// Ensure OS is valid
	if a.OS < OSTypeIOS || a.OS > OSTypeAndroid {
		a.AddError("os", "is invalid")
	}

	// SHA is required for Android
	a.SHA = strings.TrimSpace(a.SHA)
	if a.OS == OSTypeAndroid {
		if a.SHA == "" {
			a.AddError("sha", "is required for Android apps")
		}
	}

	// Process and clean SHAs
	var shas []string
	for _, line := range strings.Split(a.SHA, "\n") {
		for _, entry := range strings.Split(line, ",") {
			entry = strings.ToUpper(strings.TrimSpace(entry))
			if entry == "" {
				continue
			}

			if len(entry) != 95 {
				a.AddError("sha", "is not 95 characters")
				continue
			}

			if entry != "" {
				shas = append(shas, entry)
			}
		}
	}
	a.SHA = strings.Join(shas, "\n")

	if len(a.Errors()) > 0 {
		return fmt.Errorf("validation failed")
	}

	return nil
}

// ListActiveAppsByOS finds mobile apps by their realm and OS.
func (db *Database) ListActiveAppsByOS(realmID uint, os OSType) ([]*MobileApp, error) {
	// Find the apps.
	var apps []*MobileApp
	if err := db.db.
		Model(&MobileApp{}).
		Where("realm_id = ? AND os = ?", realmID, os).
		Find(&apps).
		Error; err != nil {
		if IsNotFound(err) {
			return apps, nil
		}
		return nil, err
	}
	return apps, nil
}

// SaveMobileApp saves the mobile app.
func (db *Database) SaveMobileApp(a *MobileApp, actor Auditable) error {
	if a == nil {
		return fmt.Errorf("provided mobile app is nil")
	}

	if actor == nil {
		return fmt.Errorf("auditing actor is nil")
	}

	return db.db.Transaction(func(tx *gorm.DB) error {
		var audits []*AuditEntry

		var existing MobileApp
		if err := tx.
			Unscoped().
			Model(&MobileApp{}).
			Where("id = ?", a.ID).
			First(&existing).
			Error; err != nil && !IsNotFound(err) {
			return fmt.Errorf("failed to get existing mobile app")
		}

		// Save the app
		if err := tx.Unscoped().Save(a).Error; err != nil {
			return fmt.Errorf("failed to save mobile app: %w", err)
		}

		// Brand new app?
		if existing.ID == 0 {
			audit := BuildAuditEntry(actor, "created mobile app", a, a.RealmID)
			audits = append(audits, audit)
		} else {
			if existing.Name != a.Name {
				audit := BuildAuditEntry(actor, "updated mobile app name", a, a.RealmID)
				audit.Diff = stringDiff(existing.Name, a.Name)
				audits = append(audits, audit)
			}

			if existing.OS != a.OS {
				audit := BuildAuditEntry(actor, "updated mobile app os", a, a.RealmID)
				audit.Diff = stringDiff(existing.OS.Display(), a.OS.Display())
				audits = append(audits, audit)
			}

			if existing.AppID != a.AppID {
				audit := BuildAuditEntry(actor, "updated mobile app appID", a, a.RealmID)
				audit.Diff = stringDiff(existing.AppID, a.AppID)
				audits = append(audits, audit)
			}

			if existing.SHA != a.SHA {
				audit := BuildAuditEntry(actor, "updated mobile app sha", a, a.RealmID)
				audit.Diff = stringDiff(existing.SHA, a.SHA)
				audits = append(audits, audit)
			}

			if existing.DeletedAt != a.DeletedAt {
				audit := BuildAuditEntry(actor, "updated mobile app enabled", a, a.RealmID)
				audit.Diff = boolDiff(existing.DeletedAt == nil, a.DeletedAt == nil)
				audits = append(audits, audit)
			}
		}

		// Save all audits
		for _, audit := range audits {
			if err := tx.Save(audit).Error; err != nil {
				return fmt.Errorf("failed to save audits: %w", err)
			}
		}

		return nil
	})
}

func (a *MobileApp) AuditID() string {
	return fmt.Sprintf("mobile_apps:%d", a.ID)
}

func (a *MobileApp) AuditDisplay() string {
	return fmt.Sprintf("%s (%s)", a.Name, a.OS.Display())
}

func (a *MobileApp) AfterFind(tx *gorm.DB) error {
	a.URL = stringValue(a.URLPtr)
	return nil
}

// PurgeMobileApps will delete mobile apps that have been deleted for more than
// the specified time.
func (db *Database) PurgeMobileApps(maxAge time.Duration) (int64, error) {
	if maxAge > 0 {
		maxAge = -1 * maxAge
	}
	deleteBefore := time.Now().UTC().Add(maxAge)

	result := db.db.
		Unscoped().
		Where("deleted_at IS NOT NULL AND deleted_at < ?", deleteBefore).
		Delete(&MobileApp{})
	return result.RowsAffected, result.Error
}
<|endoftext|>


<commit_before>package datastore

import (
	"database/sql"
	"errors"
	"fmt"
	_ "github.com/go-sql-driver/mysql" // mysql driver
	"github.com/jmoiron/sqlx"
	_ "github.com/lib/pq" // postgresql driver
	"github.com/rubenv/sql-migrate"
	"log"
)

// DB holds the deprecated SQL connection pool. Try to use exported methods in this package instead.
var DB *sql.DB

var dbx *sqlx.DB

// ErrNoResults is returned when a query yielded 0 results
var ErrNoResults = errors.New("query returned 0 results")

// Init creates a database connection pool (using sqlx)
func Init(driver string, host string, name string, user string, password string) *sqlx.DB {
	dbx = New(driver, getDSN(driver, host, name, user, password))

	// store old sql.DB in exported var for backwards compat
	DB = dbx.DB

	// run migrations
	runMigrations(driver)

	return dbx
}

// New creates a new database pool
func New(driver string, dsn string) *sqlx.DB {
	dbx := sqlx.MustConnect(driver, dsn)
	return dbx
}

// Get returns the underlying sqlx.DB instance. Use at your own risk.
func Get() *sqlx.DB {
	return dbx
}

func getDSN(driver string, host string, name string, user string, password string) string {
	var dsn = fmt.Sprintf("%s:%s@%s/%s", user, password, host, name)

	switch driver {
	case "postgres":
		dsn = "postgres://" + dsn
	case "mysql":
		dsn = dsn + "?parseTime=true"
	}

	return dsn
}

func runMigrations(driver string) {
	migrations := migrate.FileMigrationSource{
		Dir: "pkg/datastore/migrations", // TODO: Move to bindata
	}

	migrate.SetTable("migrations")

	n, err := migrate.Exec(dbx.DB, driver, migrations, migrate.Up)

	if err != nil {
		log.Fatal("Database migrations failed: ", err)
	}

	if n > 0 {
		log.Printf("Applied %d database migrations!\n", n)
	}
}
<commit_msg>stop exporting DB instance in datastore pkg. yay<commit_after>package datastore

import (
	"errors"
	"fmt"
	_ "github.com/go-sql-driver/mysql" // mysql driver
	"github.com/jmoiron/sqlx"
	_ "github.com/lib/pq" // postgresql driver
	"github.com/rubenv/sql-migrate"
	"log"
)

var dbx *sqlx.DB

// ErrNoResults is returned when a query yielded 0 results
var ErrNoResults = errors.New("datastore: query returned 0 results")

// Init creates a database connection pool (using sqlx)
func Init(driver string, host string, name string, user string, password string) *sqlx.DB {
	dbx = New(driver, getDSN(driver, host, name, user, password))

	// run migrations
	runMigrations(driver)

	return dbx
}

// New creates a new database pool
func New(driver string, dsn string) *sqlx.DB {
	dbx := sqlx.MustConnect(driver, dsn)
	return dbx
}

func getDSN(driver string, host string, name string, user string, password string) string {
	var dsn = fmt.Sprintf("%s:%s@%s/%s", user, password, host, name)

	switch driver {
	case "postgres":
		dsn = "postgres://" + dsn
	case "mysql":
		dsn = dsn + "?parseTime=true"
	}

	return dsn
}

func runMigrations(driver string) {
	migrations := migrate.FileMigrationSource{
		Dir: "pkg/datastore/migrations", // TODO: Move to bindata
	}

	migrate.SetTable("migrations")

	n, err := migrate.Exec(dbx.DB, driver, migrations, migrate.Up)

	if err != nil {
		log.Fatal("Database migrations failed: ", err)
	}

	if n > 0 {
		log.Printf("Applied %d database migrations!\n", n)
	}
}
<|endoftext|>


<commit_before>package writer

import (
	"archive/tar"
	"io"
	"os"
	"path/filepath"
	"syscall"
	"time"

	"github.com/mgoltzsche/cntnr/pkg/fs"
	"github.com/pkg/errors"
)

var _ fs.Writer = &TarWriter{}

// A mapping file system writer that secures root directory boundaries.
// Derived from umoci's tar_extract.go to allow separate source/dest interfaces
// and filter archive contents on extraction
type TarWriter struct {
	writer  *tar.Writer
	written map[string]*fs.FileAttrs
}

func NewTarWriter(writer io.Writer) (w *TarWriter) {
	return &TarWriter{tar.NewWriter(writer), map[string]*fs.FileAttrs{}}
}

func (w *TarWriter) Close() error {
	return errors.Wrap(w.writer.Close(), "close tar writer")
}

func (w *TarWriter) Parent() error                                        { return nil }
func (w *TarWriter) LowerNode(path, name string, a *fs.NodeAttrs) error   { return nil }
func (w *TarWriter) LowerLink(path, target string, a *fs.NodeAttrs) error { return nil }

func (w *TarWriter) Lazy(path, name string, src fs.LazySource, written map[fs.Source]string) (err error) {
	return errors.Errorf("refused to write lazy source %s into tar writer directly at %s since resulting tar could contain overridden entries. lazy sources must be resolved first.", src, path)
}

func (w *TarWriter) File(path string, src fs.FileSource) (r fs.Source, err error) {
	a := src.Attrs()
	if path, err = normalize(path); err != nil {
		return
	}
	if err = w.writeTarHeader(path, a.FileAttrs); err != nil {
		return
	}

	if a.NodeType != fs.TypeFile {
		return src, nil
	}

	// Copy file
	f, err := src.Reader()
	if err != nil {
		return
	}
	defer func() {
		if e := f.Close(); e != nil && err == nil {
			err = errors.Wrap(e, "write tar")
		}
	}()
	n, err := io.Copy(w.writer, f)
	if err != nil {
		return nil, errors.Wrap(err, "write tar: file entry")
	}
	if n != a.Size {
		return nil, errors.Wrap(io.ErrShortWrite, "write tar: file entry")
	}
	return src, nil
}

func (w *TarWriter) writeTarHeader(path string, a fs.FileAttrs) (err error) {
	hdr, err := w.toTarHeader(path, a)
	if err != nil {
		return
	}
	err = w.writer.WriteHeader(hdr)
	return errors.Wrapf(err, "write tar header for %q", path)
}

func (w *TarWriter) toTarHeader(path string, a fs.FileAttrs) (hdr *tar.Header, err error) {
	a.Mtime = time.Unix(a.Mtime.Unix(), 0) // use floor(mtime) to preserve mtime which otherwise is not guaranteed due to rounding to seconds within tar
	hdr, err = tar.FileInfoHeader(fs.NewFileInfo(path, &a), a.Symlink)
	if err != nil {
		return nil, errors.Wrapf(err, "to tar header: %s", path)
	}
	hdr.AccessTime = a.Atime
	hdr.Xattrs = a.Xattrs
	w.addWritten(path, &a)
	return
}

// Taken from umoci
func normalize(path string) (string, error) {
	path = filepath.Clean(string(os.PathSeparator) + path)
	path, _ = filepath.Rel(string(os.PathSeparator), path)
	path = filepath.Clean(path)
	if !isValidPath(path) {
		return "", errors.Errorf("tar writer: path outside tar root: %s", path)
	}
	return path, nil
}

func isValidPath(path string) bool {
	prfx := string(os.PathSeparator) + "___"
	return filepath.HasPrefix(filepath.Join(prfx, path), prfx)
}

func (w *TarWriter) addWritten(path string, a *fs.FileAttrs) {
	w.written[path] = a
}

func (w *TarWriter) Link(path, target string) (err error) {
	if path, err = normalize(path); err != nil {
		return
	}
	if !filepath.IsAbs(target) {
		target = filepath.Join(filepath.Dir(path), target)
	}
	if target, err = normalize(target); err != nil {
		return errors.Wrap(err, "link")
	}

	a := w.written[target]
	if a == nil {
		return errors.Errorf("write tar: link entry %s: target %s does not exist", path, target)
	}
	hdr, err := w.toTarHeader(path, *a)
	if err != nil {
		return
	}
	hdr.Typeflag = tar.TypeLink
	hdr.Linkname = target
	hdr.Size = 0

	err = w.writer.WriteHeader(hdr)
	return errors.Wrap(err, "tar writer: write link")
}

func (w *TarWriter) Symlink(path string, a fs.FileAttrs) (err error) {
	if path, err = normalize(path); err != nil {
		return
	}

	a.Mode |= os.ModeSymlink | 0777
	a.Symlink, err = normalizeLinkDest(path, a.Symlink)
	if err != nil {
		return
	}
	return w.writeTarHeader(path, a)
}

func (w *TarWriter) Fifo(path string, a fs.DeviceAttrs) (err error) {
	a.Mode |= syscall.S_IFIFO
	return w.device(path, &a)
}

func (w *TarWriter) device(path string, a *fs.DeviceAttrs) (err error) {
	if path, err = normalize(path); err != nil {
		return
	}
	hdr, err := w.toTarHeader(path, a.FileAttrs)
	if err != nil {
		return
	}
	hdr.Size = 0
	hdr.Devmajor = a.Devmajor
	hdr.Devminor = a.Devminor
	err = w.writer.WriteHeader(hdr)
	return errors.Wrap(err, "tar writer: write device")
}

func (w *TarWriter) Device(path string, a fs.DeviceAttrs) (err error) {
	return w.device(path, &a)
}

func (w *TarWriter) Mkdir(path string) (err error) {
	if path, err = normalize(path); err != nil {
		return
	}
	return w.writeTarHeader(path+string(os.PathSeparator), fs.FileAttrs{Mode: os.ModeDir | 0755})
}

func (w *TarWriter) Dir(path, base string, a fs.FileAttrs) (err error) {
	if path, err = normalize(path); err != nil {
		return
	}
	a.Mode |= os.ModeDir
	return w.writeTarHeader(path+string(os.PathSeparator), a)
}

func (w *TarWriter) Remove(path string) (err error) {
	if path, err = normalize(path); err != nil {
		return
	}
	delete(w.written, path)
	dir, file := filepath.Split(path)
	file = fs.WhiteoutPrefix + file
	now := time.Now()
	// Using current time for header values which leads to unreproducable layer
	// TODO: maybe change to fixed time instead of now()
	return w.writeTarHeader(filepath.Join(dir, file), fs.FileAttrs{FileTimes: fs.FileTimes{Atime: now, Mtime: now}})
}
<commit_msg>Fixed tar/user mapping<commit_after>package writer

import (
	"archive/tar"
	"io"
	"os"
	"path/filepath"
	"syscall"
	"time"

	"github.com/mgoltzsche/cntnr/pkg/fs"
	"github.com/pkg/errors"
)

var _ fs.Writer = &TarWriter{}

// A mapping file system writer that secures root directory boundaries.
// Derived from umoci's tar_extract.go to allow separate source/dest interfaces
// and filter archive contents on extraction
type TarWriter struct {
	writer  *tar.Writer
	written map[string]*fs.FileAttrs
}

func NewTarWriter(writer io.Writer) (w *TarWriter) {
	return &TarWriter{tar.NewWriter(writer), map[string]*fs.FileAttrs{}}
}

func (w *TarWriter) Close() error {
	return errors.Wrap(w.writer.Close(), "close tar writer")
}

func (w *TarWriter) Parent() error                                        { return nil }
func (w *TarWriter) LowerNode(path, name string, a *fs.NodeAttrs) error   { return nil }
func (w *TarWriter) LowerLink(path, target string, a *fs.NodeAttrs) error { return nil }

func (w *TarWriter) Lazy(path, name string, src fs.LazySource, written map[fs.Source]string) (err error) {
	return errors.Errorf("refused to write lazy source %s into tar writer directly at %s since resulting tar could contain overridden entries. lazy sources must be resolved first.", src, path)
}

func (w *TarWriter) File(path string, src fs.FileSource) (r fs.Source, err error) {
	a := src.Attrs()
	if path, err = normalize(path); err != nil {
		return
	}
	if err = w.writeTarHeader(path, a.FileAttrs); err != nil {
		return
	}

	if a.NodeType != fs.TypeFile {
		return src, nil
	}

	// Copy file
	f, err := src.Reader()
	if err != nil {
		return
	}
	defer func() {
		if e := f.Close(); e != nil && err == nil {
			err = errors.Wrap(e, "write tar")
		}
	}()
	n, err := io.Copy(w.writer, f)
	if err != nil {
		return nil, errors.Wrap(err, "write tar: file entry")
	}
	if n != a.Size {
		return nil, errors.Wrap(io.ErrShortWrite, "write tar: file entry")
	}
	return src, nil
}

func (w *TarWriter) writeTarHeader(path string, a fs.FileAttrs) (err error) {
	hdr, err := w.toTarHeader(path, a)
	if err != nil {
		return
	}
	err = w.writer.WriteHeader(hdr)
	return errors.Wrapf(err, "write tar header for %q", path)
}

func (w *TarWriter) toTarHeader(path string, a fs.FileAttrs) (hdr *tar.Header, err error) {
	a.Mtime = time.Unix(a.Mtime.Unix(), 0) // use floor(mtime) to preserve mtime which otherwise is not guaranteed due to rounding to seconds within tar
	hdr, err = tar.FileInfoHeader(fs.NewFileInfo(path, &a), a.Symlink)
	if err != nil {
		return nil, errors.Wrapf(err, "to tar header: %s", path)
	}
	hdr.Uid = int(a.Uid)
	hdr.Gid = int(a.Gid)
	hdr.AccessTime = a.Atime
	hdr.Xattrs = a.Xattrs
	w.addWritten(path, &a)
	return
}

// Taken from umoci
func normalize(path string) (string, error) {
	path = filepath.Clean(string(os.PathSeparator) + path)
	path, _ = filepath.Rel(string(os.PathSeparator), path)
	path = filepath.Clean(path)
	if !isValidPath(path) {
		return "", errors.Errorf("tar writer: path outside tar root: %s", path)
	}
	return path, nil
}

func isValidPath(path string) bool {
	prfx := string(os.PathSeparator) + "___"
	return filepath.HasPrefix(filepath.Join(prfx, path), prfx)
}

func (w *TarWriter) addWritten(path string, a *fs.FileAttrs) {
	w.written[path] = a
}

func (w *TarWriter) Link(path, target string) (err error) {
	if path, err = normalize(path); err != nil {
		return
	}
	if !filepath.IsAbs(target) {
		target = filepath.Join(filepath.Dir(path), target)
	}
	if target, err = normalize(target); err != nil {
		return errors.Wrap(err, "link")
	}

	a := w.written[target]
	if a == nil {
		return errors.Errorf("write tar: link entry %s: target %s does not exist", path, target)
	}
	hdr, err := w.toTarHeader(path, *a)
	if err != nil {
		return
	}
	hdr.Typeflag = tar.TypeLink
	hdr.Linkname = target
	hdr.Size = 0

	err = w.writer.WriteHeader(hdr)
	return errors.Wrap(err, "tar writer: write link")
}

func (w *TarWriter) Symlink(path string, a fs.FileAttrs) (err error) {
	if path, err = normalize(path); err != nil {
		return
	}

	a.Mode |= os.ModeSymlink | 0777
	a.Symlink, err = normalizeLinkDest(path, a.Symlink)
	if err != nil {
		return
	}
	return w.writeTarHeader(path, a)
}

func (w *TarWriter) Fifo(path string, a fs.DeviceAttrs) (err error) {
	a.Mode |= syscall.S_IFIFO
	return w.device(path, &a)
}

func (w *TarWriter) device(path string, a *fs.DeviceAttrs) (err error) {
	if path, err = normalize(path); err != nil {
		return
	}
	hdr, err := w.toTarHeader(path, a.FileAttrs)
	if err != nil {
		return
	}
	hdr.Size = 0
	hdr.Devmajor = a.Devmajor
	hdr.Devminor = a.Devminor
	err = w.writer.WriteHeader(hdr)
	return errors.Wrap(err, "tar writer: write device")
}

func (w *TarWriter) Device(path string, a fs.DeviceAttrs) (err error) {
	return w.device(path, &a)
}

func (w *TarWriter) Mkdir(path string) (err error) {
	if path, err = normalize(path); err != nil {
		return
	}
	return w.writeTarHeader(path+string(os.PathSeparator), fs.FileAttrs{Mode: os.ModeDir | 0755})
}

func (w *TarWriter) Dir(path, base string, a fs.FileAttrs) (err error) {
	if path, err = normalize(path); err != nil {
		return
	}
	a.Mode |= os.ModeDir
	return w.writeTarHeader(path+string(os.PathSeparator), a)
}

func (w *TarWriter) Remove(path string) (err error) {
	if path, err = normalize(path); err != nil {
		return
	}
	delete(w.written, path)
	dir, file := filepath.Split(path)
	file = fs.WhiteoutPrefix + file
	now := time.Now()
	// Using current time for header values which leads to unreproducable layer
	// TODO: maybe change to fixed time instead of now()
	return w.writeTarHeader(filepath.Join(dir, file), fs.FileAttrs{FileTimes: fs.FileTimes{Atime: now, Mtime: now}})
}
<|endoftext|>


<commit_before>/*
 Copyright 2010 Jeremy Wall (jeremy@marzhillstudios.com)
 Use of this source code is governed by the Artistic License 2.0.
 That License is included in the LICENSE file.
*/
package transform

import (
	. "html"
	"os"
	"log"
	"strings"
	v "container/vector"
)

type NodeType int

const (
	TEXT NodeType = iota // 0 value so the default
	TAG
)

type Node struct {
	nodeType NodeType
	nodeValue string
	nodeAttributes map[string] string
	children v.Vector
}

func lazyTokens(t *Tokenizer) <-chan Token {
	tokens := make(chan Token, 1)
	go func() {
		for {
			tt := t.Next()
			if tt == Error {
				switch t.Error() {
				case os.EOF:
					break
				default:
					log.Panicf(
						"Error tokenizing string: %s",
						t.Error())
				}
			}
			tokens <- t.Token()
		}
	}()
	return tokens
}

type Document struct {
	top Node
}

func transformAttributes(attrs []Attribute) map[string] string {
	attributes := make(map[string] string)
	for _, attr := range attrs {
		attributes[attr.Key] = attr.Val
	}
	return attributes
}

func typeFromToken(t Token) NodeType {
	if t.Type == Text {
		return TEXT
	}
	return TAG
}

func nodeFromToken(t Token) Node {
	return Node{
		nodeType: typeFromToken(t),
		nodeValue: t.Data,
		nodeAttributes: transformAttributes(t.Attr),
	}
}

func NewDoc(s string) *Document {
	t := NewTokenizer(strings.NewReader(s))
	tokens := lazyTokens(t)
	tok1 := <-tokens
	doc := Document{top: nodeFromToken(tok1)}

	queue := new(v.Vector)
	queue.Push(doc.top)
	for tok := range tokens {
		curr := queue.At(0).(Node)
		switch tok.Type {
		case SelfClosingTag, Text:
			curr.children.Push(nodeFromToken(tok))
		case StartTag:
			curr.children.Push(nodeFromToken(tok))
			queue.Push(nodeFromToken(tok))
		case EndTag:
			queue.Pop()
		}
	}
	return &doc
}

// TODO(jwall): not sure I even need fragments but oh well
type Fragment struct {
	*Document
}

func NewFragment(s string) *Fragment {
	return &Fragment{NewDoc(s)}
}

// TODO(jwall): css style addressing of elements
<commit_msg>untested but compiling selector parser<commit_after>/*
 Copyright 2010 Jeremy Wall (jeremy@marzhillstudios.com)
 Use of this source code is governed by the Artistic License 2.0.
 That License is included in the LICENSE file.
*/
package transform

import (
	v "container/vector"
	. "html"
	"log"
	"os"
	"strings"
)

type NodeType int

const (
	TEXT NodeType = iota // 0 value so the default
	TAG
)

type Node struct {
	nodeType NodeType
	nodeValue string
	nodeAttributes map[string] string
	children v.Vector
}

func lazyTokens(t *Tokenizer) <-chan Token {
	tokens := make(chan Token, 1)
	go func() {
		for {
			tt := t.Next()
			if tt == Error {
				switch t.Error() {
				case os.EOF:
					break
				default:
					log.Panicf(
						"Error tokenizing string: %s",
						t.Error())
				}
			}
			tokens <- t.Token()
		}
	}()
	return tokens
}

type Document struct {
	top Node
}

func transformAttributes(attrs []Attribute) map[string] string {
	attributes := make(map[string] string)
	for _, attr := range attrs {
		attributes[attr.Key] = attr.Val
	}
	return attributes
}

func typeFromToken(t Token) NodeType {
	if t.Type == Text {
		return TEXT
	}
	return TAG
}

func nodeFromToken(t Token) Node {
	return Node{
		nodeType: typeFromToken(t),
		nodeValue: t.Data,
		nodeAttributes: transformAttributes(t.Attr),
	}
}

func NewDoc(s string) *Document {
	t := NewTokenizer(strings.NewReader(s))
	tokens := lazyTokens(t)
	tok1 := <-tokens
	doc := Document{top: nodeFromToken(tok1)}

	queue := new(v.Vector)
	queue.Push(doc.top)
	for tok := range tokens {
		curr := queue.At(0).(Node)
		switch tok.Type {
		case SelfClosingTag, Text:
			curr.children.Push(nodeFromToken(tok))
		case StartTag:
			curr.children.Push(nodeFromToken(tok))
			queue.Push(nodeFromToken(tok))
		case EndTag:
			queue.Pop()
		}
	}
	return &doc
}

// TODO(jwall): not sure I even need fragments but oh well
type Fragment struct {
	*Document
}

func NewFragment(s string) *Fragment {
	return &Fragment{NewDoc(s)}
}

// TODO(jwall): css style addressing of elements
<|endoftext|>


<commit_before>package landscaper

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

	"github.com/pmezard/go-difflib/difflib"
	"github.com/sirupsen/logrus"
	"google.golang.org/grpc"
	"k8s.io/helm/pkg/helm"
)

// Executor is responsible for applying a desired landscape to the actual landscape
type Executor interface {
	Apply(Components, Components) error

	CreateComponent(*Component) error
	UpdateComponent(*Component) error
	DeleteComponent(*Component) error
}

type executor struct {
	helmClient     helm.Interface
	chartLoader    ChartLoader
	kubeSecrets    SecretsWriteDeleter
	dryRun         bool
	wait           bool
	waitTimeout    int64
	disabledStages []string
}

// NewExecutor is a factory method to create a new Executor
func NewExecutor(helmClient helm.Interface, chartLoader ChartLoader, kubeSecrets SecretsWriteDeleter, dryRun bool, wait bool, waitTimeout int64, disabledStages []string) Executor {
	return &executor{
		helmClient:     helmClient,
		chartLoader:    chartLoader,
		kubeSecrets:    kubeSecrets,
		dryRun:         dryRun,
		wait:           wait,
		waitTimeout:    waitTimeout,
		disabledStages: disabledStages,
	}
}

// gatherForcedUpdates returns a map that for each to-be-updated component indicates if it needs a forced update.
// there may be several reasons to do so: releases that differ only in secret values are forced so that pods will restart with the new values; releases that differ in namespace cannot be updated
func (e *executor) gatherForcedUpdates(current, update Components) (map[string]bool, error) {
	needForcedUpdate := map[string]bool{}

	for _, cmp := range update {
		// releases that differ only in secret values are forced so that pods will restart with the new values
		for _, curCmp := range current {
			if curCmp.Name == cmp.Name && isOnlySecretValueDiff(*curCmp, *cmp) {
				logrus.Infof("%s differs in secrets values only; don't update but delete + create instead", cmp.Name)
				needForcedUpdate[cmp.Name] = true
			}
		}
		if curCmp := current[cmp.Name]; curCmp != nil {
			if curCmp.Namespace != cmp.Namespace {
				logrus.Infof("%s differs in namespace; don't update but delete + create instead", cmp.Name)
				needForcedUpdate[cmp.Name] = true
			}
		}
	}

	return needForcedUpdate, nil
}

// Apply transforms the current state into the desired state
func (e *executor) Apply(desired, current Components) error {
	create, update, delete := diff(desired, current)

	// some to-be-updated components need a delete + create instead
	needForcedUpdate, err := e.gatherForcedUpdates(current, update)
	if err != nil {
		return err
	}

	// delete+create pairs will never work in dry run since the dry-run "deleted" component will exist in create
	if !e.dryRun {
		create, update, delete = integrateForcedUpdates(current, create, update, delete, needForcedUpdate)
	}

	logrus.WithFields(logrus.Fields{"create": len(create), "update": len(update), "delete": len(delete)}).Info("Apply desired state")

	if err := logDifferences(current, create, update, delete, e.stageEnabled("update"), e.stageEnabled("delete"), logrus.Infof); err != nil {
		return err
	}

	if e.stageEnabled("delete") {
		for _, cmp := range delete {
			if err := e.DeleteComponent(cmp); err != nil {
				logrus.WithFields(logrus.Fields{"error": err}).Error("DeleteComponent failed")
				return err
			}
		}
	}

	if e.stageEnabled("update") {
		for _, cmp := range update {
			if err := e.UpdateComponent(cmp); err != nil {
				logrus.WithFields(logrus.Fields{"error": err}).Error("UpdateComponent failed")
				return err
			}
		}
	}

	if e.stageEnabled("create") {
		for _, cmp := range create {
			if err := e.CreateComponent(cmp); err != nil {
				logrus.WithFields(logrus.Fields{"error": err}).Error("CreateComponent failed")
				return err
			}
		}
	}

	logrus.WithFields(logrus.Fields{"created": len(create), "updated": len(update), "deleted": len(delete)}).Info("Applied desired state successfully")
	return nil
}

func (e *executor) stageEnabled(stage string) bool {
	for _, stageDisabled := range e.disabledStages {
		if stageDisabled == stage {
			return false
		}
	}
	return true
}

// CreateComponent creates the given Component
func (e *executor) CreateComponent(cmp *Component) error {
	// We need to ensure the chart is available on the local system. LoadChart will ensure
	// this is the case by downloading the chart if it is not there yet
	chartRef, err := cmp.FullChartRef()
	if err != nil {
		return err
	}
	_, chartPath, err := e.chartLoader.Load(chartRef)
	if err != nil {
		return err
	}

	rawValues, err := cmp.Configuration.YAML()
	if err != nil {
		return err
	}

	logrus.WithFields(logrus.Fields{
		"release":   cmp.Name,
		"chart":     cmp.Release.Chart,
		"chartPath": chartPath,
		"rawValues": rawValues,
		"values":    cmp.Configuration,
		"dryrun":    e.dryRun,
	}).Debug("Create component")

	if len(cmp.SecretValues) > 0 && !e.dryRun {
		err = e.kubeSecrets.Write(cmp.Name, cmp.Namespace, cmp.SecretValues)
		if err != nil {
			return err
		}
	}

	_, err = e.helmClient.InstallRelease(
		chartPath,
		cmp.Namespace,
		helm.ValueOverrides([]byte(rawValues)),
		helm.ReleaseName(cmp.Name),
		helm.InstallDryRun(e.dryRun),
		helm.InstallReuseName(true),
		helm.InstallWait(e.wait),
		helm.InstallTimeout(e.waitTimeout),
	)
	if err != nil {
		return errors.New(grpc.ErrorDesc(err))
	}

	return nil
}

// UpdateComponent updates the given Component
func (e *executor) UpdateComponent(cmp *Component) error {
	// We need to ensure the chart is available on the local system. LoadChart will ensure
	// this is the case by downloading the chart if it is not there yet
	chartRef, err := cmp.FullChartRef()
	if err != nil {
		return err
	}
	_, chartPath, err := e.chartLoader.Load(chartRef)
	if err != nil {
		return err
	}

	rawValues, err := cmp.Configuration.YAML()
	if err != nil {
		return err
	}

	if !e.dryRun {
		if e.stageEnabled("deleteSecrets") || len(cmp.SecretValues) > 0 {
			err = e.kubeSecrets.Delete(cmp.Name, cmp.Namespace)
		}

		if len(cmp.SecretValues) > 0 {
			err = e.kubeSecrets.Write(cmp.Name, cmp.Namespace, cmp.SecretValues)
			if err != nil {
				return err
			}
		}
	}

	logrus.WithFields(logrus.Fields{
		"release":   cmp.Name,
		"chart":     cmp.Release.Chart,
		"chartPath": chartPath,
		"values":    cmp.Configuration,
		"dryrun":    e.dryRun,
	}).Debug("Update component")

	_, err = e.helmClient.UpdateRelease(
		cmp.Name,
		chartPath,
		helm.UpdateValueOverrides([]byte(rawValues)),
		helm.UpgradeDryRun(e.dryRun),
		helm.UpgradeWait(e.wait),
		helm.UpgradeTimeout(e.waitTimeout),
	)
	if err != nil {
		return errors.New(grpc.ErrorDesc(err))
	}

	return nil
}

// DeleteComponent removes the given Component
func (e *executor) DeleteComponent(cmp *Component) error {
	logrus.WithFields(logrus.Fields{
		"release": cmp.Name,
		"values":  cmp.Configuration,
		"dryrun":  e.dryRun,
	}).Debug("Delete component")

	if len(cmp.SecretValues) > 0 && !e.dryRun {
		err := e.kubeSecrets.Delete(cmp.Name, cmp.Namespace)
		if err != nil {
			return err
		}
	}

	if !e.dryRun {
		_, err := e.helmClient.DeleteRelease(
			cmp.Name,
			helm.DeletePurge(true),
			helm.DeleteDryRun(e.dryRun),
		)
		if err != nil {
			return errors.New(grpc.ErrorDesc(err))
		}
	}

	return nil
}

// diff takes desired and current components, and returns the components to create, update and delete to get from current to desired
func diff(desired, current Components) (Components, Components, Components) {
	create := Components{}
	update := Components{}
	delete := Components{}

	for name, desiredCmp := range desired {
		if currentCmp, ok := current[name]; ok {
			if !desiredCmp.Equals(currentCmp) {
				update[name] = desiredCmp
			}
		} else {
			create[name] = desiredCmp
		}
	}

	for name, currentCmp := range current {
		if _, ok := desired[name]; !ok {
			delete[name] = currentCmp
		}
	}

	return create, update, delete
}

// componentDiffText returns a diff as text. current and desired can be nil and indicate non-existence (e.g. current nil and desired non-nil means: create)
func componentDiffText(current, desired *Component) (string, error) {
	cText, dText := []string{}, []string{}
	cName, dName := "<none>", "<none>"
	if current != nil {
		cs, err := json.MarshalIndent(current, "", "  ")
		if err != nil {
			return "", err
		}
		cText = difflib.SplitLines(string(cs))
		cName = current.Name
	}
	if desired != nil {
		ds, err := json.MarshalIndent(desired, "", "  ")
		if err != nil {
			return "", err
		}
		dText = difflib.SplitLines(string(ds))
		dName = desired.Name
	}

	return difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
		A:        cText,
		FromFile: "Current " + cName,
		B:        dText,
		ToFile:   "Desired " + dName,
		Context:  3,
	})
}

// logDifferences logs the Create, Update and Delete w.r.t. current to logf
func logDifferences(current, creates, updates, deletes Components, updateStageEnabled bool, deleteStageEnabled bool, logf func(format string, args ...interface{})) error {
	log := func(action string, current, desired *Component) error {
		diff, err := componentDiffText(current, desired)
		if err != nil {
			return err
		}
		logf("%s", action)
		if diff != "" {
			logf("Diff:\n%s", diff)
		}
		if current != nil && desired != nil && !reflect.DeepEqual(current.SecretValues, desired.SecretValues) {
			logrus.Info("Diff: secrets have changed, not shown here")
		}
		return nil
	}

	// Log diffs only if delete is applied
	if deleteStageEnabled {
		for _, d := range deletes {
			logf("Delete: %s", d.Name)
		}
	}

	for _, d := range creates {
		if err := log("Create: "+d.Name, nil, d); err != nil {
			return err
		}
	}

	// Log diffs only if upadete is applied
	if updateStageEnabled {
		for _, d := range updates {
			c := current[d.Name]
			if err := log("Update: "+d.Name, c, d); err != nil {
				return err
			}
		}
	}

	return nil
}

// integrateForcedUpdates removes forceUpdate from update and inserts it into delete + create
func integrateForcedUpdates(current, create, update, delete Components, forceUpdate map[string]bool) (Components, Components, Components) {
	fixUpdate := Components{}
	for _, cmp := range update {
		if forceUpdate[cmp.Name] {
			if currentCmp, ok := current[cmp.Name]; ok {
				delete[currentCmp.Name] = currentCmp // delete the current component
			}
			create[cmp.Name] = cmp // create cmp, by definition a desired component
		} else {
			fixUpdate[cmp.Name] = cmp
		}
	}
	return create, fixUpdate, delete
}

// isOnlySecretValueDiff tells whether the given Components differ in their .SecretValues fields and are identical otherwise
func isOnlySecretValueDiff(a, b Component) bool {
	secValsEqual := reflect.DeepEqual(a.SecretValues, b.SecretValues)
	a.SecretValues = SecretValues{}
	b.SecretValues = SecretValues{}
	return !secValsEqual && reflect.DeepEqual(a, b)
}
<commit_msg>feat(log) Log which component has failed<commit_after>package landscaper

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

	"github.com/pmezard/go-difflib/difflib"
	"github.com/sirupsen/logrus"
	"google.golang.org/grpc"
	"k8s.io/helm/pkg/helm"
)

// Executor is responsible for applying a desired landscape to the actual landscape
type Executor interface {
	Apply(Components, Components) error

	CreateComponent(*Component) error
	UpdateComponent(*Component) error
	DeleteComponent(*Component) error
}

type executor struct {
	helmClient     helm.Interface
	chartLoader    ChartLoader
	kubeSecrets    SecretsWriteDeleter
	dryRun         bool
	wait           bool
	waitTimeout    int64
	disabledStages []string
}

// NewExecutor is a factory method to create a new Executor
func NewExecutor(helmClient helm.Interface, chartLoader ChartLoader, kubeSecrets SecretsWriteDeleter, dryRun bool, wait bool, waitTimeout int64, disabledStages []string) Executor {
	return &executor{
		helmClient:     helmClient,
		chartLoader:    chartLoader,
		kubeSecrets:    kubeSecrets,
		dryRun:         dryRun,
		wait:           wait,
		waitTimeout:    waitTimeout,
		disabledStages: disabledStages,
	}
}

// gatherForcedUpdates returns a map that for each to-be-updated component indicates if it needs a forced update.
// there may be several reasons to do so: releases that differ only in secret values are forced so that pods will restart with the new values; releases that differ in namespace cannot be updated
func (e *executor) gatherForcedUpdates(current, update Components) (map[string]bool, error) {
	needForcedUpdate := map[string]bool{}

	for _, cmp := range update {
		// releases that differ only in secret values are forced so that pods will restart with the new values
		for _, curCmp := range current {
			if curCmp.Name == cmp.Name && isOnlySecretValueDiff(*curCmp, *cmp) {
				logrus.Infof("%s differs in secrets values only; don't update but delete + create instead", cmp.Name)
				needForcedUpdate[cmp.Name] = true
			}
		}
		if curCmp := current[cmp.Name]; curCmp != nil {
			if curCmp.Namespace != cmp.Namespace {
				logrus.Infof("%s differs in namespace; don't update but delete + create instead", cmp.Name)
				needForcedUpdate[cmp.Name] = true
			}
		}
	}

	return needForcedUpdate, nil
}

// Apply transforms the current state into the desired state
func (e *executor) Apply(desired, current Components) error {
	create, update, delete := diff(desired, current)

	// some to-be-updated components need a delete + create instead
	needForcedUpdate, err := e.gatherForcedUpdates(current, update)
	if err != nil {
		return err
	}

	// delete+create pairs will never work in dry run since the dry-run "deleted" component will exist in create
	if !e.dryRun {
		create, update, delete = integrateForcedUpdates(current, create, update, delete, needForcedUpdate)
	}

	logrus.WithFields(logrus.Fields{"create": len(create), "update": len(update), "delete": len(delete)}).Info("Apply desired state")

	if err := logDifferences(current, create, update, delete, e.stageEnabled("update"), e.stageEnabled("delete"), logrus.Infof); err != nil {
		return err
	}

	if e.stageEnabled("delete") {
		for _, cmp := range delete {
			if err := e.DeleteComponent(cmp); err != nil {
				logrus.WithFields(logrus.Fields{"error": err, "component": cmp}).Error("DeleteComponent failed")
				return err
			}
		}
	}

	if e.stageEnabled("update") {
		for _, cmp := range update {
			if err := e.UpdateComponent(cmp); err != nil {
				logrus.WithFields(logrus.Fields{"error": err, "component": cmp}).Error("UpdateComponent failed")
				return err
			}
		}
	}

	if e.stageEnabled("create") {
		for _, cmp := range create {
			if err := e.CreateComponent(cmp); err != nil {
				logrus.WithFields(logrus.Fields{"error": err, "component": cmp}).Error("CreateComponent failed")
				return err
			}
		}
	}

	logrus.WithFields(logrus.Fields{"created": len(create), "updated": len(update), "deleted": len(delete)}).Info("Applied desired state successfully")
	return nil
}

func (e *executor) stageEnabled(stage string) bool {
	for _, stageDisabled := range e.disabledStages {
		if stageDisabled == stage {
			return false
		}
	}
	return true
}

// CreateComponent creates the given Component
func (e *executor) CreateComponent(cmp *Component) error {
	// We need to ensure the chart is available on the local system. LoadChart will ensure
	// this is the case by downloading the chart if it is not there yet
	chartRef, err := cmp.FullChartRef()
	if err != nil {
		return err
	}
	_, chartPath, err := e.chartLoader.Load(chartRef)
	if err != nil {
		return err
	}

	rawValues, err := cmp.Configuration.YAML()
	if err != nil {
		return err
	}

	logrus.WithFields(logrus.Fields{
		"release":   cmp.Name,
		"chart":     cmp.Release.Chart,
		"chartPath": chartPath,
		"rawValues": rawValues,
		"values":    cmp.Configuration,
		"dryrun":    e.dryRun,
	}).Debug("Create component")

	if len(cmp.SecretValues) > 0 && !e.dryRun {
		err = e.kubeSecrets.Write(cmp.Name, cmp.Namespace, cmp.SecretValues)
		if err != nil {
			return err
		}
	}

	_, err = e.helmClient.InstallRelease(
		chartPath,
		cmp.Namespace,
		helm.ValueOverrides([]byte(rawValues)),
		helm.ReleaseName(cmp.Name),
		helm.InstallDryRun(e.dryRun),
		helm.InstallReuseName(true),
		helm.InstallWait(e.wait),
		helm.InstallTimeout(e.waitTimeout),
	)
	if err != nil {
		return errors.New(grpc.ErrorDesc(err))
	}

	return nil
}

// UpdateComponent updates the given Component
func (e *executor) UpdateComponent(cmp *Component) error {
	// We need to ensure the chart is available on the local system. LoadChart will ensure
	// this is the case by downloading the chart if it is not there yet
	chartRef, err := cmp.FullChartRef()
	if err != nil {
		return err
	}
	_, chartPath, err := e.chartLoader.Load(chartRef)
	if err != nil {
		return err
	}

	rawValues, err := cmp.Configuration.YAML()
	if err != nil {
		return err
	}

	if !e.dryRun {
		if e.stageEnabled("deleteSecrets") || len(cmp.SecretValues) > 0 {
			err = e.kubeSecrets.Delete(cmp.Name, cmp.Namespace)
		}

		if len(cmp.SecretValues) > 0 {
			err = e.kubeSecrets.Write(cmp.Name, cmp.Namespace, cmp.SecretValues)
			if err != nil {
				return err
			}
		}
	}

	logrus.WithFields(logrus.Fields{
		"release":   cmp.Name,
		"chart":     cmp.Release.Chart,
		"chartPath": chartPath,
		"values":    cmp.Configuration,
		"dryrun":    e.dryRun,
	}).Debug("Update component")

	_, err = e.helmClient.UpdateRelease(
		cmp.Name,
		chartPath,
		helm.UpdateValueOverrides([]byte(rawValues)),
		helm.UpgradeDryRun(e.dryRun),
		helm.UpgradeWait(e.wait),
		helm.UpgradeTimeout(e.waitTimeout),
	)
	if err != nil {
		return errors.New(grpc.ErrorDesc(err))
	}

	return nil
}

// DeleteComponent removes the given Component
func (e *executor) DeleteComponent(cmp *Component) error {
	logrus.WithFields(logrus.Fields{
		"release": cmp.Name,
		"values":  cmp.Configuration,
		"dryrun":  e.dryRun,
	}).Debug("Delete component")

	if len(cmp.SecretValues) > 0 && !e.dryRun {
		err := e.kubeSecrets.Delete(cmp.Name, cmp.Namespace)
		if err != nil {
			return err
		}
	}

	if !e.dryRun {
		_, err := e.helmClient.DeleteRelease(
			cmp.Name,
			helm.DeletePurge(true),
			helm.DeleteDryRun(e.dryRun),
		)
		if err != nil {
			return errors.New(grpc.ErrorDesc(err))
		}
	}

	return nil
}

// diff takes desired and current components, and returns the components to create, update and delete to get from current to desired
func diff(desired, current Components) (Components, Components, Components) {
	create := Components{}
	update := Components{}
	delete := Components{}

	for name, desiredCmp := range desired {
		if currentCmp, ok := current[name]; ok {
			if !desiredCmp.Equals(currentCmp) {
				update[name] = desiredCmp
			}
		} else {
			create[name] = desiredCmp
		}
	}

	for name, currentCmp := range current {
		if _, ok := desired[name]; !ok {
			delete[name] = currentCmp
		}
	}

	return create, update, delete
}

// componentDiffText returns a diff as text. current and desired can be nil and indicate non-existence (e.g. current nil and desired non-nil means: create)
func componentDiffText(current, desired *Component) (string, error) {
	cText, dText := []string{}, []string{}
	cName, dName := "<none>", "<none>"
	if current != nil {
		cs, err := json.MarshalIndent(current, "", "  ")
		if err != nil {
			return "", err
		}
		cText = difflib.SplitLines(string(cs))
		cName = current.Name
	}
	if desired != nil {
		ds, err := json.MarshalIndent(desired, "", "  ")
		if err != nil {
			return "", err
		}
		dText = difflib.SplitLines(string(ds))
		dName = desired.Name
	}

	return difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
		A:        cText,
		FromFile: "Current " + cName,
		B:        dText,
		ToFile:   "Desired " + dName,
		Context:  3,
	})
}

// logDifferences logs the Create, Update and Delete w.r.t. current to logf
func logDifferences(current, creates, updates, deletes Components, updateStageEnabled bool, deleteStageEnabled bool, logf func(format string, args ...interface{})) error {
	log := func(action string, current, desired *Component) error {
		diff, err := componentDiffText(current, desired)
		if err != nil {
			return err
		}
		logf("%s", action)
		if diff != "" {
			logf("Diff:\n%s", diff)
		}
		if current != nil && desired != nil && !reflect.DeepEqual(current.SecretValues, desired.SecretValues) {
			logrus.Info("Diff: secrets have changed, not shown here")
		}
		return nil
	}

	// Log diffs only if delete is applied
	if deleteStageEnabled {
		for _, d := range deletes {
			logf("Delete: %s", d.Name)
		}
	}

	for _, d := range creates {
		if err := log("Create: "+d.Name, nil, d); err != nil {
			return err
		}
	}

	// Log diffs only if upadete is applied
	if updateStageEnabled {
		for _, d := range updates {
			c := current[d.Name]
			if err := log("Update: "+d.Name, c, d); err != nil {
				return err
			}
		}
	}

	return nil
}

// integrateForcedUpdates removes forceUpdate from update and inserts it into delete + create
func integrateForcedUpdates(current, create, update, delete Components, forceUpdate map[string]bool) (Components, Components, Components) {
	fixUpdate := Components{}
	for _, cmp := range update {
		if forceUpdate[cmp.Name] {
			if currentCmp, ok := current[cmp.Name]; ok {
				delete[currentCmp.Name] = currentCmp // delete the current component
			}
			create[cmp.Name] = cmp // create cmp, by definition a desired component
		} else {
			fixUpdate[cmp.Name] = cmp
		}
	}
	return create, fixUpdate, delete
}

// isOnlySecretValueDiff tells whether the given Components differ in their .SecretValues fields and are identical otherwise
func isOnlySecretValueDiff(a, b Component) bool {
	secValsEqual := reflect.DeepEqual(a.SecretValues, b.SecretValues)
	a.SecretValues = SecretValues{}
	b.SecretValues = SecretValues{}
	return !secValsEqual && reflect.DeepEqual(a, b)
}
<|endoftext|>


<commit_before>// +build integration

package proxy

import (
	"io/ioutil"
	"net/http"
	"testing"

	"github.com/hellofresh/janus/pkg/router"
	"github.com/hellofresh/janus/pkg/test"
	log "github.com/sirupsen/logrus"
	"github.com/stretchr/testify/assert"
)

var tests = []struct {
	description         string
	method              string
	url                 string
	expectedContentType string
	expectedCode        int
}{
	{
		description:         "Get example route",
		method:              "GET",
		url:                 "/example",
		expectedContentType: "application/json; charset=utf-8",
		expectedCode:        http.StatusOK,
	}, {
		description:         "Get invalid route",
		method:              "GET",
		url:                 "/invalid-route",
		expectedContentType: "text/plain; charset=utf-8",
		expectedCode:        http.StatusNotFound,
	},
	{
		description:         "Get one posts - strip path",
		method:              "GET",
		url:                 "/posts/1",
		expectedContentType: "application/json; charset=utf-8",
		expectedCode:        http.StatusOK,
	},
	{
		description:         "Get one posts - append path",
		method:              "GET",
		url:                 "/append",
		expectedContentType: "application/json; charset=utf-8",
		expectedCode:        http.StatusOK,
	},
	{
		description:         "Get one recipe - parameter interpolation",
		url:                 "/api/recipes/5252b1b5301bbf46038b473f",
		expectedContentType: "application/json; charset=utf-8",
		expectedCode:        http.StatusOK,
	},
	{
		description:         "No parameter to interpolate",
		url:                 "/api/recipes/search",
		expectedContentType: "application/json",
		expectedCode:        http.StatusNotFound,
	},
}

func TestSuccessfulProxy(t *testing.T) {
	t.Parallel()

	log.SetOutput(ioutil.Discard)

	ts := test.NewServer(createRegisterAndRouter())
	defer ts.Close()

	for _, tc := range tests {
		t.Run(tc.description, func(t *testing.T) {
			res, err := ts.Do(tc.method, tc.url, make(map[string]string))
			assert.NoError(t, err)
			if res != nil {
				defer res.Body.Close()
			}

			assert.Equal(t, tc.expectedContentType, res.Header.Get("Content-Type"), tc.description)
			assert.Equal(t, tc.expectedCode, res.StatusCode, tc.description)
		})
	}
}

func createProxyDefinitions() []*Definition {
	return []*Definition{
		{
			ListenPath: "/example/*",
			Upstreams: &Upstreams{
				Balancing: "roundrobin",
				Targets:   []*Target{{Target: "http://localhost:9089/hello-world"}},
			},
			Methods: []string{"ALL"},
		},
		{
			ListenPath: "/posts/*",
			StripPath:  true,
			Upstreams: &Upstreams{
				Balancing: "roundrobin",
				Targets:   []*Target{{Target: "http://localhost:9089/posts"}},
			},
			Methods: []string{"ALL"},
		},
		{
			ListenPath: "/append/*",
			Upstreams: &Upstreams{
				Balancing: "roundrobin",
				Targets:   []*Target{{Target: "http://localhost:9089/hello-world"}},
			},
			AppendPath: true,
			Methods:    []string{"GET"},
		},
		{
			ListenPath: "/api/recipes/{id:[\\da-f]{24}}",
			Upstreams: &Upstreams{
				Balancing: "roundrobin",
				Targets:   []*Target{{Target: "http://localhost:9089/recipes/{id}"}},
			},
			Methods: []string{"GET"},
		},
		{
			ListenPath: "/api/recipes/search",
			Upstreams: &Upstreams{
				Balancing: "roundrobin",
				Targets:   []*Target{{Target: "http://localhost:9089/recipes/{id}"}},
			},
			Methods: []string{"GET"},
		},
	}
}

func createRegisterAndRouter() router.Router {
	r := router.NewChiRouter()
	createRegister(r)
	return r
}

func createRegister(r router.Router) *Register {
	register := NewRegister(r, Params{})

	definitions := createProxyDefinitions()
	for _, def := range definitions {
		register.Add(def)
	}

	return register
}
<commit_msg>Fixed Register call<commit_after>// +build integration

package proxy

import (
	"io/ioutil"
	"net/http"
	"testing"

	"github.com/hellofresh/janus/pkg/router"
	"github.com/hellofresh/janus/pkg/test"
	log "github.com/sirupsen/logrus"
	"github.com/stretchr/testify/assert"
)

var tests = []struct {
	description         string
	method              string
	url                 string
	expectedContentType string
	expectedCode        int
}{
	{
		description:         "Get example route",
		method:              "GET",
		url:                 "/example",
		expectedContentType: "application/json; charset=utf-8",
		expectedCode:        http.StatusOK,
	}, {
		description:         "Get invalid route",
		method:              "GET",
		url:                 "/invalid-route",
		expectedContentType: "text/plain; charset=utf-8",
		expectedCode:        http.StatusNotFound,
	},
	{
		description:         "Get one posts - strip path",
		method:              "GET",
		url:                 "/posts/1",
		expectedContentType: "application/json; charset=utf-8",
		expectedCode:        http.StatusOK,
	},
	{
		description:         "Get one posts - append path",
		method:              "GET",
		url:                 "/append",
		expectedContentType: "application/json; charset=utf-8",
		expectedCode:        http.StatusOK,
	},
	{
		description:         "Get one recipe - parameter interpolation",
		url:                 "/api/recipes/5252b1b5301bbf46038b473f",
		expectedContentType: "application/json; charset=utf-8",
		expectedCode:        http.StatusOK,
	},
	{
		description:         "No parameter to interpolate",
		url:                 "/api/recipes/search",
		expectedContentType: "application/json",
		expectedCode:        http.StatusNotFound,
	},
}

func TestSuccessfulProxy(t *testing.T) {
	t.Parallel()

	log.SetOutput(ioutil.Discard)

	ts := test.NewServer(createRegisterAndRouter())
	defer ts.Close()

	for _, tc := range tests {
		t.Run(tc.description, func(t *testing.T) {
			res, err := ts.Do(tc.method, tc.url, make(map[string]string))
			assert.NoError(t, err)
			if res != nil {
				defer res.Body.Close()
			}

			assert.Equal(t, tc.expectedContentType, res.Header.Get("Content-Type"), tc.description)
			assert.Equal(t, tc.expectedCode, res.StatusCode, tc.description)
		})
	}
}

func createProxyDefinitions() []*Definition {
	return []*Definition{
		{
			ListenPath: "/example/*",
			Upstreams: &Upstreams{
				Balancing: "roundrobin",
				Targets:   []*Target{{Target: "http://localhost:9089/hello-world"}},
			},
			Methods: []string{"ALL"},
		},
		{
			ListenPath: "/posts/*",
			StripPath:  true,
			Upstreams: &Upstreams{
				Balancing: "roundrobin",
				Targets:   []*Target{{Target: "http://localhost:9089/posts"}},
			},
			Methods: []string{"ALL"},
		},
		{
			ListenPath: "/append/*",
			Upstreams: &Upstreams{
				Balancing: "roundrobin",
				Targets:   []*Target{{Target: "http://localhost:9089/hello-world"}},
			},
			AppendPath: true,
			Methods:    []string{"GET"},
		},
		{
			ListenPath: "/api/recipes/{id:[\\da-f]{24}}",
			Upstreams: &Upstreams{
				Balancing: "roundrobin",
				Targets:   []*Target{{Target: "http://localhost:9089/recipes/{id}"}},
			},
			Methods: []string{"GET"},
		},
		{
			ListenPath: "/api/recipes/search",
			Upstreams: &Upstreams{
				Balancing: "roundrobin",
				Targets:   []*Target{{Target: "http://localhost:9089/recipes/{id}"}},
			},
			Methods: []string{"GET"},
		},
	}
}

func createRegisterAndRouter() router.Router {
	r := router.NewChiRouter()
	createRegister(r)
	return r
}

func createRegister(r router.Router) *Register {
	register := NewRegister(WithRouter(r))

	definitions := createProxyDefinitions()
	for _, def := range definitions {
		register.Add(def)
	}

	return register
}
<|endoftext|>


<commit_before>package application

import (
	"encoding/json"
	"github.com/thoas/gokvstores"
	"github.com/thoas/muxer"
	"github.com/thoas/picfit/extractors"
	"github.com/thoas/picfit/hash"
	"github.com/thoas/picfit/image"
	"github.com/thoas/picfit/util"
	"net/http"
	"net/url"
)

var Extractors = map[string]extractors.Extractor{
	"op":   extractors.Operation,
	"fmt":  extractors.Format,
	"url":  extractors.URL,
	"q":    extractors.Quality,
	"path": extractors.Path,
}

func NotFoundHandler() http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.Error(w, "404 not found", http.StatusNotFound)
	})
}

type Options struct {
	Format  string
	Quality int
}

type Request struct {
	*muxer.Request
	Operation  *image.Operation
	Connection gokvstores.KVStoreConnection
	Key        string
	URL        *url.URL
	Filepath   string
	Options    *Options
}

type Handler func(muxer.Response, *Request)

func (h Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	con := App.KVStore.Connection()
	defer con.Close()

	request := muxer.NewRequest(req)

	for k, v := range request.Params {
		request.QueryString[k] = v
	}

	res := muxer.NewResponse(w)

	extracted := map[string]interface{}{}

	for key, extractor := range Extractors {
		result, err := extractor(key, request)

		if err != nil {
			App.Logger.Info(err)

			res.BadRequest()
			return
		}

		extracted[key] = result
	}

	sorted := util.SortMapString(request.QueryString)

	valid := App.IsValidSign(sorted)

	delete(sorted, "sig")

	serialized := hash.Serialize(sorted)

	key := hash.Tokey(serialized)

	App.Logger.Infof("Generating key %s from request: %s", key, serialized)

	var u *url.URL
	var path string
	var format string
	var quality int

	value, ok := extracted["url"]

	if ok && value != nil {
		u = value.(*url.URL)
	}

	value, ok = extracted["path"]

	if ok {
		path = value.(string)
	}

	if !valid || (u == nil && path == "") {
		res.BadRequest()
		return
	}

	value, ok = extracted["fmt"]

	if ok {
		format = value.(string)
	}

	value, ok = extracted["q"]

	if ok && value != nil {
		quality = value.(int)
	}

	options := &Options{Quality: quality, Format: format}

	h(res, &Request{
		request,
		extracted["op"].(*image.Operation),
		con,
		key,
		u,
		path,
		options,
	})
}

var ImageHandler Handler = func(res muxer.Response, req *Request) {
	file, err := App.ImageFileFromRequest(req, true, true)

	util.PanicIf(err)

	content, err := file.ToBytes()

	util.PanicIf(err)

	res.SetHeaders(file.Headers, true)
	res.ResponseWriter.Write(content)
}

var GetHandler Handler = func(res muxer.Response, req *Request) {
	file, err := App.ImageFileFromRequest(req, false, false)

	util.PanicIf(err)

	content, err := json.Marshal(map[string]string{
		"filename": file.Filename(),
		"path":     file.Path(),
		"url":      file.URL(),
	})

	util.PanicIf(err)

	res.ContentType("application/json")
	res.ResponseWriter.Write(content)
}

var RedirectHandler Handler = func(res muxer.Response, req *Request) {
	file, err := App.ImageFileFromRequest(req, false, false)

	util.PanicIf(err)

	res.PermanentRedirect(file.URL())
}
<commit_msg>Fix nil reference on fmt and path<commit_after>package application

import (
	"encoding/json"
	"github.com/thoas/gokvstores"
	"github.com/thoas/muxer"
	"github.com/thoas/picfit/extractors"
	"github.com/thoas/picfit/hash"
	"github.com/thoas/picfit/image"
	"github.com/thoas/picfit/util"
	"net/http"
	"net/url"
)

var Extractors = map[string]extractors.Extractor{
	"op":   extractors.Operation,
	"fmt":  extractors.Format,
	"url":  extractors.URL,
	"q":    extractors.Quality,
	"path": extractors.Path,
}

func NotFoundHandler() http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.Error(w, "404 not found", http.StatusNotFound)
	})
}

type Options struct {
	Format  string
	Quality int
}

type Request struct {
	*muxer.Request
	Operation  *image.Operation
	Connection gokvstores.KVStoreConnection
	Key        string
	URL        *url.URL
	Filepath   string
	Options    *Options
}

type Handler func(muxer.Response, *Request)

func (h Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	con := App.KVStore.Connection()
	defer con.Close()

	request := muxer.NewRequest(req)

	for k, v := range request.Params {
		request.QueryString[k] = v
	}

	res := muxer.NewResponse(w)

	extracted := map[string]interface{}{}

	for key, extractor := range Extractors {
		result, err := extractor(key, request)

		if err != nil {
			App.Logger.Info(err)

			res.BadRequest()
			return
		}

		extracted[key] = result
	}

	sorted := util.SortMapString(request.QueryString)

	valid := App.IsValidSign(sorted)

	delete(sorted, "sig")

	serialized := hash.Serialize(sorted)

	key := hash.Tokey(serialized)

	App.Logger.Infof("Generating key %s from request: %s", key, serialized)

	var u *url.URL
	var path string
	var format string
	var quality int

	value, ok := extracted["url"]

	if ok && value != nil {
		u = value.(*url.URL)
	}

	value, ok = extracted["path"]

	if ok && value != nil {
		path = value.(string)
	}

	if !valid || (u == nil && path == "") {
		res.BadRequest()
		return
	}

	value, ok = extracted["fmt"]

	if ok && value != nil {
		format = value.(string)
	}

	value, ok = extracted["q"]

	if ok && value != nil {
		quality = value.(int)
	}

	options := &Options{Quality: quality, Format: format}

	h(res, &Request{
		request,
		extracted["op"].(*image.Operation),
		con,
		key,
		u,
		path,
		options,
	})
}

var ImageHandler Handler = func(res muxer.Response, req *Request) {
	file, err := App.ImageFileFromRequest(req, true, true)

	util.PanicIf(err)

	content, err := file.ToBytes()

	util.PanicIf(err)

	res.SetHeaders(file.Headers, true)
	res.ResponseWriter.Write(content)
}

var GetHandler Handler = func(res muxer.Response, req *Request) {
	file, err := App.ImageFileFromRequest(req, false, false)

	util.PanicIf(err)

	content, err := json.Marshal(map[string]string{
		"filename": file.Filename(),
		"path":     file.Path(),
		"url":      file.URL(),
	})

	util.PanicIf(err)

	res.ContentType("application/json")
	res.ResponseWriter.Write(content)
}

var RedirectHandler Handler = func(res muxer.Response, req *Request) {
	file, err := App.ImageFileFromRequest(req, false, false)

	util.PanicIf(err)

	res.PermanentRedirect(file.URL())
}
<|endoftext|>


<commit_before>// Package transform invokes the golang HTML parser, executes the
// individual transfomers (unless overridden), and prints the output
// to the provided string.
package transform

import (
	"fmt"
	"net/url"
	"strings"

	"github.com/ampproject/amppackager/pkg/printer"
	"github.com/ampproject/amppackager/pkg/transformer"
	"golang.org/x/net/html"
)

// Transformer functions must be added here in order to be passed in from
// the command line or invoked from other languages. Please keep alphabetical.
//
// NOTE: The string mapping is necessary as a cross-over to allow
// invocation from C/C++.
var transformerFunctionMap = map[string]func(*transformer.Engine){
	"AMPBoilerplateTransformer":      transformer.AMPBoilerplateTransformer,
	"ReorderHeadTransformer":         transformer.ReorderHeadTransformer,
	"ServerSideRenderingTransformer": transformer.ServerSideRenderingTransformer,
	"URLTransformer":                 transformer.URLTransformer,
}

// The transformers to execute, in the order in which to execute them.
var transformers = []string{
	"URLTransformer",
	"AMPBoilerplateTransformer",
	"ServerSideRenderingTransformer",
	"ReorderHeadTransformer",
}

// Process will parse the given HTML byte array, applying all the
// transformers and return the transformed HTML, or an error.
// TODO(b/112356610): Clean up these args into a proto.
func Process(data, docURL string) (string, error) {
	return ProcessSome(data, docURL, transformers)
}

// Process will parse the given HTML byte array, and execute the named
// transformers, returning the transformed HTML, or an error.
// TODO(b/112356610): Clean up these args into a proto.
func ProcessSome(data, docURL string, transformers []string) (string, error) {
	doc, err := html.Parse(strings.NewReader(data))
	if err != nil {
		return "", err
	}

	fns := []func(*transformer.Engine){}
	for _, val := range transformers {
		fn, ok := transformerFunctionMap[val]
		if !ok {
			return "", fmt.Errorf("transformer doesn't exist: %s", val)
		}
		fns = append(fns, fn)
	}
	u, err := url.Parse(docURL)
	if err != nil {
		return "", err
	}
	e := transformer.Engine{doc, u, fns}
	e.Transform()
	var o strings.Builder
	err = printer.Print(&o, e.Doc)
	if err != nil {
		return "", err
	}
	return o.String(), nil
}
<commit_msg>Point diff.sh to new //third_party/amppackager location.<commit_after>// Package transform invokes the golang HTML parser, executes the
// individual transfomers (unless overridden), and prints the output
// to the provided string.
package transform

import (
	"fmt"
	"net/url"
	"strings"

	"github.com/ampproject/amppackager/pkg/printer"
	"github.com/ampproject/amppackager/pkg/transformer"
	"golang.org/x/net/html"
)

// Transformer functions must be added here in order to be passed in from
// the command line or invoked from other languages. Please keep alphabetical.
//
// NOTE: The string mapping is necessary as a cross-over to allow
// invocation from C/C++.
var transformerFunctionMap = map[string]func(*transformer.Engine){
	"AMPBoilerplateTransformer":        transformer.AMPBoilerplateTransformer,
	"ReorderHeadTransformer":           transformer.ReorderHeadTransformer,
	"ServerSideRenderingTransformer":   transformer.ServerSideRenderingTransformer,
	"TransformedIdentifierTransformer": transformer.TransformedIdentifierTransformer,
	"URLTransformer":                   transformer.URLTransformer,
}

// The transformers to execute, in the order in which to execute them.
var transformers = []string{
	"URLTransformer",
	"AMPBoilerplateTransformer",
	"ServerSideRenderingTransformer",
	"TransformedIdentifierTransformer",
	"ReorderHeadTransformer",
}

// Process will parse the given HTML byte array, applying all the
// transformers and return the transformed HTML, or an error.
// TODO(b/112356610): Clean up these args into a proto.
func Process(data, docURL string) (string, error) {
	return ProcessSome(data, docURL, transformers)
}

// Process will parse the given HTML byte array, and execute the named
// transformers, returning the transformed HTML, or an error.
// TODO(b/112356610): Clean up these args into a proto.
func ProcessSome(data, docURL string, transformers []string) (string, error) {
	doc, err := html.Parse(strings.NewReader(data))
	if err != nil {
		return "", err
	}

	fns := []func(*transformer.Engine){}
	for _, val := range transformers {
		fn, ok := transformerFunctionMap[val]
		if !ok {
			return "", fmt.Errorf("transformer doesn't exist: %s", val)
		}
		fns = append(fns, fn)
	}
	u, err := url.Parse(docURL)
	if err != nil {
		return "", err
	}
	e := transformer.Engine{doc, u, fns}
	e.Transform()
	var o strings.Builder
	err = printer.Print(&o, e.Doc)
	if err != nil {
		return "", err
	}
	return o.String(), nil
}
<|endoftext|>


<commit_before>package log

import (
	"bufio"
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"
	"time"

	kitlog "github.com/go-kit/kit/log"
	uuid "github.com/satori/go.uuid"
)

type key int

const (
	KubernikusRequestID key = 0
)

func RequestIDHandler(next http.Handler) http.Handler {
	return http.HandlerFunc(func(rw http.ResponseWriter, request *http.Request) {
		if id := request.Context().Value(KubernikusRequestID); id == nil {
			request = request.WithContext(context.WithValue(request.Context(), KubernikusRequestID, uuid.NewV4()))
		}
		next.ServeHTTP(rw, request)
	})
}

func LoggingHandler(logger kitlog.Logger, next http.Handler) http.Handler {
	ingress_logger := kitlog.With(logger, "api", "ingress")
	return http.HandlerFunc(func(rw http.ResponseWriter, request *http.Request) {
		wrapper := makeWrapper(rw)

		inner_logger := kitlog.With(ingress_logger)

		id := ""
		if reqId := request.Context().Value(KubernikusRequestID); reqId != nil {
			id = fmt.Sprintf("%s", reqId)
			inner_logger = kitlog.With(inner_logger, "id", id)
		}
		request = request.WithContext(context.WithValue(request.Context(), "logger", inner_logger))

		defer func(begin time.Time) {
			var keyvals = make([]interface{}, 0, 4)

			keyvals = append(keyvals,
				"status", wrapper.Status(),
				"size", wrapper.Size(),
				"took", time.Since(begin),
			)

			if id != "" {
				keyvals = append(keyvals, "id", id)
			}

			log(inner_logger, request, keyvals...)
		}(time.Now())

		next.ServeHTTP(wrapper, request)
	})
}

func log(logger kitlog.Logger, request *http.Request, extra ...interface{}) {
	var keyvals []interface{}

	source_ip, _, err := net.SplitHostPort(request.RemoteAddr)
	if err != nil {
		source_ip = request.RemoteAddr
	}

	if source_ip != "" {
		keyvals = append(keyvals, "source_ip", source_ip)
	}

	keyvals = append(keyvals, "method", request.Method)

	host, host_port, err := net.SplitHostPort(request.Host)
	if err == nil {
		if host != "" {
			keyvals = append(keyvals,
				"host", host)
		}
		if host_port != "" {
			keyvals = append(keyvals,
				"port", host_port)
		}
	}

	keyvals = append(keyvals, "path", request.URL.EscapedPath())

	for i, k := range request.URL.Query() {
		keyvals = append(keyvals, i, strings.Join(k, ","))
	}

	keyvals = append(keyvals, "user_agent", request.UserAgent())
	keyvals = append(keyvals, extra...)
	logger.Log(keyvals...)
}

// this stuff is copied from gorilla

func makeWrapper(w http.ResponseWriter) loggingResponseWriter {
	var logger loggingResponseWriter = &responseLogger{w: w, status: http.StatusOK}
	if _, ok := w.(http.Hijacker); ok {
		logger = &hijackLogger{responseLogger{w: w, status: http.StatusOK}}
	}
	h, ok1 := logger.(http.Hijacker)
	c, ok2 := w.(http.CloseNotifier)
	if ok1 && ok2 {
		return hijackCloseNotifier{logger, h, c}
	}
	if ok2 {
		return &closeNotifyWriter{logger, c}
	}
	return logger
}

type hijackLogger struct {
	responseLogger
}

func (l *hijackLogger) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	h := l.responseLogger.w.(http.Hijacker)
	conn, rw, err := h.Hijack()
	if err == nil && l.responseLogger.status == 0 {
		// The status will be StatusSwitchingProtocols if there was no error and
		// WriteHeader has not been called yet
		l.responseLogger.status = http.StatusSwitchingProtocols
	}
	return conn, rw, err
}

type closeNotifyWriter struct {
	loggingResponseWriter
	http.CloseNotifier
}

type hijackCloseNotifier struct {
	loggingResponseWriter
	http.Hijacker
	http.CloseNotifier
}

type loggingResponseWriter interface {
	commonLoggingResponseWriter
	http.Pusher
}

type commonLoggingResponseWriter interface {
	http.ResponseWriter
	http.Flusher
	Status() int
	Size() int
}

type responseLogger struct {
	w      http.ResponseWriter
	status int
	size   int
}

func (l *responseLogger) Header() http.Header {
	return l.w.Header()
}

func (l *responseLogger) Write(b []byte) (int, error) {
	size, err := l.w.Write(b)
	l.size += size
	return size, err
}

func (l *responseLogger) WriteHeader(s int) {
	l.w.WriteHeader(s)
	l.status = s
}

func (l *responseLogger) Status() int {
	return l.status
}

func (l *responseLogger) Size() int {
	return l.size
}

func (l *responseLogger) Flush() {
	f, ok := l.w.(http.Flusher)
	if ok {
		f.Flush()
	}
}

func (l *responseLogger) Push(target string, opts *http.PushOptions) error {
	p, ok := l.w.(http.Pusher)
	if !ok {
		return fmt.Errorf("responseLogger does not implement http.Pusher")
	}
	return p.Push(target, opts)
}
<commit_msg>removes dublicate id<commit_after>package log

import (
	"bufio"
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"
	"time"

	kitlog "github.com/go-kit/kit/log"
	uuid "github.com/satori/go.uuid"
)

type key int

const (
	KubernikusRequestID key = 0
)

func RequestIDHandler(next http.Handler) http.Handler {
	return http.HandlerFunc(func(rw http.ResponseWriter, request *http.Request) {
		if id := request.Context().Value(KubernikusRequestID); id == nil {
			request = request.WithContext(context.WithValue(request.Context(), KubernikusRequestID, uuid.NewV4()))
		}
		next.ServeHTTP(rw, request)
	})
}

func LoggingHandler(logger kitlog.Logger, next http.Handler) http.Handler {
	ingress_logger := kitlog.With(logger, "api", "ingress")
	return http.HandlerFunc(func(rw http.ResponseWriter, request *http.Request) {
		wrapper := makeWrapper(rw)

		inner_logger := kitlog.With(ingress_logger)

		id := ""
		if reqId := request.Context().Value(KubernikusRequestID); reqId != nil {
			id = fmt.Sprintf("%s", reqId)
			inner_logger = kitlog.With(inner_logger, "id", id)
		}
		request = request.WithContext(context.WithValue(request.Context(), "logger", inner_logger))

		defer func(begin time.Time) {
			var keyvals = make([]interface{}, 0, 4)

			keyvals = append(keyvals,
				"status", wrapper.Status(),
				"size", wrapper.Size(),
				"took", time.Since(begin),
			)

			log(inner_logger, request, keyvals...)
		}(time.Now())

		next.ServeHTTP(wrapper, request)
	})
}

func log(logger kitlog.Logger, request *http.Request, extra ...interface{}) {
	var keyvals []interface{}

	source_ip, _, err := net.SplitHostPort(request.RemoteAddr)
	if err != nil {
		source_ip = request.RemoteAddr
	}

	if source_ip != "" {
		keyvals = append(keyvals, "source_ip", source_ip)
	}

	keyvals = append(keyvals, "method", request.Method)

	host, host_port, err := net.SplitHostPort(request.Host)
	if err == nil {
		if host != "" {
			keyvals = append(keyvals,
				"host", host)
		}
		if host_port != "" {
			keyvals = append(keyvals,
				"port", host_port)
		}
	}

	keyvals = append(keyvals, "path", request.URL.EscapedPath())

	for i, k := range request.URL.Query() {
		keyvals = append(keyvals, i, strings.Join(k, ","))
	}

	keyvals = append(keyvals, "user_agent", request.UserAgent())
	keyvals = append(keyvals, extra...)
	logger.Log(keyvals...)
}

// this stuff is copied from gorilla

func makeWrapper(w http.ResponseWriter) loggingResponseWriter {
	var logger loggingResponseWriter = &responseLogger{w: w, status: http.StatusOK}
	if _, ok := w.(http.Hijacker); ok {
		logger = &hijackLogger{responseLogger{w: w, status: http.StatusOK}}
	}
	h, ok1 := logger.(http.Hijacker)
	c, ok2 := w.(http.CloseNotifier)
	if ok1 && ok2 {
		return hijackCloseNotifier{logger, h, c}
	}
	if ok2 {
		return &closeNotifyWriter{logger, c}
	}
	return logger
}

type hijackLogger struct {
	responseLogger
}

func (l *hijackLogger) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	h := l.responseLogger.w.(http.Hijacker)
	conn, rw, err := h.Hijack()
	if err == nil && l.responseLogger.status == 0 {
		// The status will be StatusSwitchingProtocols if there was no error and
		// WriteHeader has not been called yet
		l.responseLogger.status = http.StatusSwitchingProtocols
	}
	return conn, rw, err
}

type closeNotifyWriter struct {
	loggingResponseWriter
	http.CloseNotifier
}

type hijackCloseNotifier struct {
	loggingResponseWriter
	http.Hijacker
	http.CloseNotifier
}

type loggingResponseWriter interface {
	commonLoggingResponseWriter
	http.Pusher
}

type commonLoggingResponseWriter interface {
	http.ResponseWriter
	http.Flusher
	Status() int
	Size() int
}

type responseLogger struct {
	w      http.ResponseWriter
	status int
	size   int
}

func (l *responseLogger) Header() http.Header {
	return l.w.Header()
}

func (l *responseLogger) Write(b []byte) (int, error) {
	size, err := l.w.Write(b)
	l.size += size
	return size, err
}

func (l *responseLogger) WriteHeader(s int) {
	l.w.WriteHeader(s)
	l.status = s
}

func (l *responseLogger) Status() int {
	return l.status
}

func (l *responseLogger) Size() int {
	return l.size
}

func (l *responseLogger) Flush() {
	f, ok := l.w.(http.Flusher)
	if ok {
		f.Flush()
	}
}

func (l *responseLogger) Push(target string, opts *http.PushOptions) error {
	p, ok := l.w.(http.Pusher)
	if !ok {
		return fmt.Errorf("responseLogger does not implement http.Pusher")
	}
	return p.Push(target, opts)
}
<|endoftext|>