<commit_before>package apiserver

import (
	"net"
	"net/http"
	"net/url"
	"path"
	"strconv"
	"time"

	"github.com/golang/glog"
	"k8s.io/kubernetes/pkg/api/legacyscheme"

	"github.com/elazarl/go-bindata-assetfs"

	genericapifilters "k8s.io/apiserver/pkg/endpoints/filters"
	apirequest "k8s.io/apiserver/pkg/endpoints/request"
	"k8s.io/apiserver/pkg/features"
	"k8s.io/apiserver/pkg/server"
	genericapiserver "k8s.io/apiserver/pkg/server"
	genericfilters "k8s.io/apiserver/pkg/server/filters"
	genericmux "k8s.io/apiserver/pkg/server/mux"
	genericapiserveroptions "k8s.io/apiserver/pkg/server/options"
	utilfeature "k8s.io/apiserver/pkg/util/feature"
	utilflag "k8s.io/apiserver/pkg/util/flag"
	kversion "k8s.io/kubernetes/pkg/version"

	"github.com/openshift/origin/pkg/api"
	"github.com/openshift/origin/pkg/assets"
	"github.com/openshift/origin/pkg/assets/java"
	oapi "github.com/openshift/origin/pkg/cmd/server/api"
	"github.com/openshift/origin/pkg/cmd/server/crypto"
	cmdutil "github.com/openshift/origin/pkg/cmd/util"
	oauthutil "github.com/openshift/origin/pkg/oauth/util"
	"github.com/openshift/origin/pkg/util/httprequest"
	oversion "github.com/openshift/origin/pkg/version"
)

const (
	OpenShiftWebConsoleClientID = "openshift-web-console"
)

type ExtraConfig struct {
	Options   oapi.AssetConfig
	PublicURL url.URL
}

type AssetServerConfig struct {
	GenericConfig *genericapiserver.RecommendedConfig
	ExtraConfig   ExtraConfig
}

// AssetServer serves non-API endpoints for openshift.
type AssetServer struct {
	GenericAPIServer *genericapiserver.GenericAPIServer

	PublicURL url.URL
}

type completedConfig struct {
	GenericConfig genericapiserver.CompletedConfig
	ExtraConfig   *ExtraConfig
}

type CompletedConfig struct {
	// Embed a private pointer that cannot be instantiated outside of this package.
	*completedConfig
}

func NewAssetServerConfig(assetConfig oapi.AssetConfig) (*AssetServerConfig, error) {
	publicURL, err := url.Parse(assetConfig.PublicURL)
	if err != nil {
		glog.Fatal(err)
	}
	_, portString, err := net.SplitHostPort(assetConfig.ServingInfo.BindAddress)
	if err != nil {
		return nil, err
	}
	port, err := strconv.Atoi(portString)
	if err != nil {
		return nil, err
	}
	secureServingOptions := genericapiserveroptions.SecureServingOptions{}
	secureServingOptions.BindPort = port
	secureServingOptions.ServerCert.CertKey.CertFile = assetConfig.ServingInfo.ServerCert.CertFile
	secureServingOptions.ServerCert.CertKey.KeyFile = assetConfig.ServingInfo.ServerCert.KeyFile
	for _, nc := range assetConfig.ServingInfo.NamedCertificates {
		sniCert := utilflag.NamedCertKey{
			CertFile: nc.CertFile,
			KeyFile:  nc.KeyFile,
			Names:    nc.Names,
		}
		secureServingOptions.SNICertKeys = append(secureServingOptions.SNICertKeys, sniCert)
	}

	genericConfig := genericapiserver.NewConfig(legacyscheme.Codecs)
	genericConfig.EnableDiscovery = false
	genericConfig.BuildHandlerChainFunc = buildHandlerChainForAssets(publicURL.Path)
	if err := secureServingOptions.ApplyTo(genericConfig); err != nil {
		return nil, err
	}
	genericConfig.SecureServingInfo.BindAddress = assetConfig.ServingInfo.BindAddress
	genericConfig.SecureServingInfo.BindNetwork = assetConfig.ServingInfo.BindNetwork
	genericConfig.SecureServingInfo.MinTLSVersion = crypto.TLSVersionOrDie(assetConfig.ServingInfo.MinTLSVersion)
	genericConfig.SecureServingInfo.CipherSuites = crypto.CipherSuitesOrDie(assetConfig.ServingInfo.CipherSuites)

	return &AssetServerConfig{
		GenericConfig: &genericapiserver.RecommendedConfig{Config: *genericConfig},
		ExtraConfig: ExtraConfig{
			Options:   assetConfig,
			PublicURL: *publicURL,
		},
	}, nil
}

// Complete fills in any fields not set that are required to have valid data. It's mutating the receiver.
func (c *AssetServerConfig) Complete() completedConfig {
	cfg := completedConfig{
		c.GenericConfig.Complete(),
		&c.ExtraConfig,
	}

	return cfg
}

func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget) (*AssetServer, error) {
	genericServer, err := c.GenericConfig.New("openshift-non-api-routes", delegationTarget)
	if err != nil {
		return nil, err
	}

	s := &AssetServer{
		GenericAPIServer: genericServer,
		PublicURL:        c.ExtraConfig.PublicURL,
	}

	if err := c.addAssets(s.GenericAPIServer.Handler.NonGoRestfulMux); err != nil {
		return nil, err
	}
	if err := c.addExtensionScripts(s.GenericAPIServer.Handler.NonGoRestfulMux); err != nil {
		return nil, err
	}
	if err := c.addExtensionStyleSheets(s.GenericAPIServer.Handler.NonGoRestfulMux); err != nil {
		return nil, err
	}
	c.addExtensionFiles(s.GenericAPIServer.Handler.NonGoRestfulMux)
	if err := c.addWebConsoleConfig(s.GenericAPIServer.Handler.NonGoRestfulMux); err != nil {
		return nil, err
	}

	return s, nil
}

// buildHandlerChainForAssets is the handling chain used to protect the asset server.  With no secret information to protect
// the chain is very short.
func buildHandlerChainForAssets(consoleRedirectPath string) func(startingHandler http.Handler, c *genericapiserver.Config) http.Handler {
	return func(startingHandler http.Handler, c *genericapiserver.Config) http.Handler {
		handler := WithAssetServerRedirect(startingHandler, consoleRedirectPath)
		handler = genericfilters.WithMaxInFlightLimit(handler, c.MaxRequestsInFlight, c.MaxMutatingRequestsInFlight, c.RequestContextMapper, c.LongRunningFunc)
		if utilfeature.DefaultFeatureGate.Enabled(features.AdvancedAuditing) {
			handler = genericapifilters.WithAudit(handler, c.RequestContextMapper, c.AuditBackend, c.AuditPolicyChecker, c.LongRunningFunc)
		}
		handler = genericfilters.WithTimeoutForNonLongRunningRequests(handler, c.RequestContextMapper, c.LongRunningFunc, c.RequestTimeout)
		handler = genericapifilters.WithRequestInfo(handler, genericapiserver.NewRequestInfoResolver(c), c.RequestContextMapper)
		handler = apirequest.WithRequestContext(handler, c.RequestContextMapper)
		handler = genericfilters.WithPanicRecovery(handler)

		return handler
	}
}

func (c completedConfig) addAssets(serverMux *genericmux.PathRecorderMux) error {
	assetHandler, err := c.buildAssetHandler()
	if err != nil {
		return err
	}

	serverMux.UnlistedHandlePrefix(c.ExtraConfig.PublicURL.Path, http.StripPrefix(c.ExtraConfig.PublicURL.Path, assetHandler))
	serverMux.UnlistedHandle(c.ExtraConfig.PublicURL.Path[0:len(c.ExtraConfig.PublicURL.Path)-1], http.RedirectHandler(c.ExtraConfig.PublicURL.Path, http.StatusMovedPermanently))
	return nil
}

func (c completedConfig) addExtensionScripts(serverMux *genericmux.PathRecorderMux) error {
	// Extension scripts
	extScriptsPath := path.Join(c.ExtraConfig.PublicURL.Path, "scripts/extensions.js")
	extScriptsHandler, err := assets.ExtensionScriptsHandler(c.ExtraConfig.Options.ExtensionScripts, c.ExtraConfig.Options.ExtensionDevelopment)
	if err != nil {
		return err
	}
	extScriptsHandler = assets.SecurityHeadersHandler(extScriptsHandler)
	serverMux.UnlistedHandle(extScriptsPath, assets.GzipHandler(extScriptsHandler))
	return nil
}

func (c completedConfig) addExtensionStyleSheets(serverMux *genericmux.PathRecorderMux) error {
	// Extension stylesheets
	extStylesheetsPath := path.Join(c.ExtraConfig.PublicURL.Path, "styles/extensions.css")
	extStylesheetsHandler, err := assets.ExtensionStylesheetsHandler(c.ExtraConfig.Options.ExtensionStylesheets, c.ExtraConfig.Options.ExtensionDevelopment)
	if err != nil {
		return err
	}
	extStylesheetsHandler = assets.SecurityHeadersHandler(extStylesheetsHandler)
	serverMux.UnlistedHandle(extStylesheetsPath, assets.GzipHandler(extStylesheetsHandler))
	return nil
}

func (c completedConfig) addExtensionFiles(serverMux *genericmux.PathRecorderMux) {
	// Extension files
	for _, extConfig := range c.ExtraConfig.Options.Extensions {
		extBasePath := path.Join(c.ExtraConfig.PublicURL.Path, "extensions", extConfig.Name)
		extPath := extBasePath + "/"
		extHandler := assets.AssetExtensionHandler(extConfig.SourceDirectory, extPath, extConfig.HTML5Mode)
		serverMux.UnlistedHandlePrefix(extPath, http.StripPrefix(extBasePath, extHandler))
		serverMux.UnlistedHandle(extBasePath, http.RedirectHandler(extPath, http.StatusMovedPermanently))
	}
}

func (c *completedConfig) addWebConsoleConfig(serverMux *genericmux.PathRecorderMux) error {
	masterURL, err := url.Parse(c.ExtraConfig.Options.MasterPublicURL)
	if err != nil {
		return err
	}

	// Generated web console config and server version
	config := assets.WebConsoleConfig{
		APIGroupAddr:      masterURL.Host,
		APIGroupPrefix:    server.APIGroupPrefix,
		MasterAddr:        masterURL.Host,
		MasterPrefix:      api.Prefix,
		KubernetesAddr:    masterURL.Host,
		KubernetesPrefix:  server.DefaultLegacyAPIPrefix,
		OAuthAuthorizeURI: oauthutil.OpenShiftOAuthAuthorizeURL(masterURL.String()),
		OAuthTokenURI:     oauthutil.OpenShiftOAuthTokenURL(masterURL.String()),
		OAuthRedirectBase: c.ExtraConfig.Options.PublicURL,
		OAuthClientID:     OpenShiftWebConsoleClientID,
		LogoutURI:         c.ExtraConfig.Options.LogoutURL,
		LoggingURL:        c.ExtraConfig.Options.LoggingPublicURL,
		MetricsURL:        c.ExtraConfig.Options.MetricsPublicURL,
	}
	kVersionInfo := kversion.Get()
	oVersionInfo := oversion.Get()
	versionInfo := assets.WebConsoleVersion{
		KubernetesVersion: kVersionInfo.GitVersion,
		OpenShiftVersion:  oVersionInfo.GitVersion,
	}

	extensionProps := assets.WebConsoleExtensionProperties{
		ExtensionProperties: extensionPropertyArray(c.ExtraConfig.Options.ExtensionProperties),
	}
	configPath := path.Join(c.ExtraConfig.PublicURL.Path, "config.js")
	configHandler, err := assets.GeneratedConfigHandler(config, versionInfo, extensionProps)
	configHandler = assets.SecurityHeadersHandler(configHandler)
	if err != nil {
		return err
	}
	serverMux.UnlistedHandle(configPath, assets.GzipHandler(configHandler))

	return nil
}

func (c completedConfig) buildAssetHandler() (http.Handler, error) {
	assets.RegisterMimeTypes()

	assetFunc := assets.JoinAssetFuncs(assets.Asset, java.Asset)
	assetDirFunc := assets.JoinAssetDirFuncs(assets.AssetDir, java.AssetDir)

	handler := http.FileServer(&assetfs.AssetFS{Asset: assetFunc, AssetDir: assetDirFunc, Prefix: ""})

	// Map of context roots (no leading or trailing slash) to the asset path to serve for requests to a missing asset
	subcontextMap := map[string]string{
		"":     "index.html",
		"java": "java/index.html",
	}

	var err error
	handler, err = assets.HTML5ModeHandler(c.ExtraConfig.PublicURL.Path, subcontextMap, handler, assetFunc)
	if err != nil {
		return nil, err
	}

	// Cache control should happen after all Vary headers are added, but before
	// any asset related routing (HTML5ModeHandler and FileServer)
	handler = assets.CacheControlHandler(oversion.Get().GitCommit, handler)

	handler = assets.SecurityHeadersHandler(handler)

	// Gzip first so that inner handlers can react to the addition of the Vary header
	handler = assets.GzipHandler(handler)

	return handler, nil
}

// Have to convert to arrays because go templates are limited and we need to be able to know
// if we are on the last index for trailing commas in JSON
func extensionPropertyArray(extensionProperties map[string]string) []assets.WebConsoleExtensionProperty {
	extensionPropsArray := []assets.WebConsoleExtensionProperty{}
	for key, value := range extensionProperties {
		extensionPropsArray = append(extensionPropsArray, assets.WebConsoleExtensionProperty{
			Key:   key,
			Value: value,
		})
	}
	return extensionPropsArray
}

// Run starts an http server for the static assets listening on the configured
// bind address
func RunAssetServer(assetServer *AssetServer, stopCh <-chan struct{}) error {
	go assetServer.GenericAPIServer.PrepareRun().Run(stopCh)

	glog.Infof("Web console listening at https://%s", assetServer.GenericAPIServer.SecureServingInfo.BindAddress)
	glog.Infof("Web console available at %s", assetServer.PublicURL.String())

	// Attempt to verify the server came up for 20 seconds (100 tries * 100ms, 100ms timeout per try)
	return cmdutil.WaitForSuccessfulDial(true, assetServer.GenericAPIServer.SecureServingInfo.BindNetwork, assetServer.GenericAPIServer.SecureServingInfo.BindAddress, 100*time.Millisecond, 100*time.Millisecond, 100)
}

// If we know the location of the asset server, redirect to it when / is requested
// and the Accept header supports text/html
func WithAssetServerRedirect(handler http.Handler, assetPublicURL string) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		if req.URL.Path == "/" {
			if httprequest.PrefersHTML(req) {
				http.Redirect(w, req, assetPublicURL, http.StatusFound)
			}
		}
		// Dispatch to the next handler
		handler.ServeHTTP(w, req)
	})
}
<commit_msg>SEPARATE: needs picking to webconsole server when they rebase<commit_after>package apiserver

import (
	"fmt"
	"net"
	"net/http"
	"net/url"
	"path"
	"strconv"
	"time"

	"github.com/golang/glog"
	"k8s.io/kubernetes/pkg/api/legacyscheme"

	"github.com/elazarl/go-bindata-assetfs"

	genericapifilters "k8s.io/apiserver/pkg/endpoints/filters"
	apirequest "k8s.io/apiserver/pkg/endpoints/request"
	"k8s.io/apiserver/pkg/features"
	"k8s.io/apiserver/pkg/server"
	genericapiserver "k8s.io/apiserver/pkg/server"
	genericfilters "k8s.io/apiserver/pkg/server/filters"
	genericmux "k8s.io/apiserver/pkg/server/mux"
	genericapiserveroptions "k8s.io/apiserver/pkg/server/options"
	utilfeature "k8s.io/apiserver/pkg/util/feature"
	utilflag "k8s.io/apiserver/pkg/util/flag"
	kversion "k8s.io/kubernetes/pkg/version"

	"github.com/openshift/origin/pkg/api"
	"github.com/openshift/origin/pkg/assets"
	"github.com/openshift/origin/pkg/assets/java"
	oapi "github.com/openshift/origin/pkg/cmd/server/api"
	"github.com/openshift/origin/pkg/cmd/server/crypto"
	cmdutil "github.com/openshift/origin/pkg/cmd/util"
	oauthutil "github.com/openshift/origin/pkg/oauth/util"
	"github.com/openshift/origin/pkg/util/httprequest"
	oversion "github.com/openshift/origin/pkg/version"
)

const (
	OpenShiftWebConsoleClientID = "openshift-web-console"
)

type ExtraConfig struct {
	Options   oapi.AssetConfig
	PublicURL url.URL
}

type AssetServerConfig struct {
	GenericConfig *genericapiserver.RecommendedConfig
	ExtraConfig   ExtraConfig
}

// AssetServer serves non-API endpoints for openshift.
type AssetServer struct {
	GenericAPIServer *genericapiserver.GenericAPIServer

	PublicURL url.URL

	// TODO figure out sttts envisions these being made available
	BindAddress string
	BindNetwork string
}

type completedConfig struct {
	GenericConfig genericapiserver.CompletedConfig
	ExtraConfig   *ExtraConfig
}

type CompletedConfig struct {
	// Embed a private pointer that cannot be instantiated outside of this package.
	*completedConfig
}

func NewAssetServerConfig(assetConfig oapi.AssetConfig) (*AssetServerConfig, error) {
	publicURL, err := url.Parse(assetConfig.PublicURL)
	if err != nil {
		glog.Fatal(err)
	}
	_, portString, err := net.SplitHostPort(assetConfig.ServingInfo.BindAddress)
	if err != nil {
		return nil, err
	}
	port, err := strconv.Atoi(portString)
	if err != nil {
		return nil, err
	}
	secureServingOptions := genericapiserveroptions.SecureServingOptions{}
	secureServingOptions.BindPort = port
	secureServingOptions.ServerCert.CertKey.CertFile = assetConfig.ServingInfo.ServerCert.CertFile
	secureServingOptions.ServerCert.CertKey.KeyFile = assetConfig.ServingInfo.ServerCert.KeyFile
	for _, nc := range assetConfig.ServingInfo.NamedCertificates {
		sniCert := utilflag.NamedCertKey{
			CertFile: nc.CertFile,
			KeyFile:  nc.KeyFile,
			Names:    nc.Names,
		}
		secureServingOptions.SNICertKeys = append(secureServingOptions.SNICertKeys, sniCert)
	}

	genericConfig := genericapiserver.NewConfig(legacyscheme.Codecs)
	genericConfig.EnableDiscovery = false
	genericConfig.BuildHandlerChainFunc = buildHandlerChainForAssets(publicURL.Path)
	if err := secureServingOptions.ApplyTo(genericConfig); err != nil {
		return nil, err
	}
	genericConfig.SecureServingInfo.Listener, err = net.Listen(assetConfig.ServingInfo.BindNetwork, assetConfig.ServingInfo.BindAddress)
	if err != nil {
		return nil, fmt.Errorf("failed to listen on %v: %v", assetConfig.ServingInfo.BindAddress, err)
	}
	genericConfig.SecureServingInfo.MinTLSVersion = crypto.TLSVersionOrDie(assetConfig.ServingInfo.MinTLSVersion)
	genericConfig.SecureServingInfo.CipherSuites = crypto.CipherSuitesOrDie(assetConfig.ServingInfo.CipherSuites)

	return &AssetServerConfig{
		GenericConfig: &genericapiserver.RecommendedConfig{Config: *genericConfig},
		ExtraConfig: ExtraConfig{
			Options:   assetConfig,
			PublicURL: *publicURL,
		},
	}, nil
}

// Complete fills in any fields not set that are required to have valid data. It's mutating the receiver.
func (c *AssetServerConfig) Complete() completedConfig {
	cfg := completedConfig{
		c.GenericConfig.Complete(),
		&c.ExtraConfig,
	}

	return cfg
}

func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget) (*AssetServer, error) {
	genericServer, err := c.GenericConfig.New("openshift-non-api-routes", delegationTarget)
	if err != nil {
		return nil, err
	}

	s := &AssetServer{
		GenericAPIServer: genericServer,

		PublicURL: c.ExtraConfig.PublicURL,
		// TODO figure out sttts envisions these being made available
		BindAddress: c.ExtraConfig.Options.ServingInfo.BindAddress,
		BindNetwork: c.ExtraConfig.Options.ServingInfo.BindNetwork,
	}

	if err := c.addAssets(s.GenericAPIServer.Handler.NonGoRestfulMux); err != nil {
		return nil, err
	}
	if err := c.addExtensionScripts(s.GenericAPIServer.Handler.NonGoRestfulMux); err != nil {
		return nil, err
	}
	if err := c.addExtensionStyleSheets(s.GenericAPIServer.Handler.NonGoRestfulMux); err != nil {
		return nil, err
	}
	c.addExtensionFiles(s.GenericAPIServer.Handler.NonGoRestfulMux)
	if err := c.addWebConsoleConfig(s.GenericAPIServer.Handler.NonGoRestfulMux); err != nil {
		return nil, err
	}

	return s, nil
}

// buildHandlerChainForAssets is the handling chain used to protect the asset server.  With no secret information to protect
// the chain is very short.
func buildHandlerChainForAssets(consoleRedirectPath string) func(startingHandler http.Handler, c *genericapiserver.Config) http.Handler {
	return func(startingHandler http.Handler, c *genericapiserver.Config) http.Handler {
		handler := WithAssetServerRedirect(startingHandler, consoleRedirectPath)
		handler = genericfilters.WithMaxInFlightLimit(handler, c.MaxRequestsInFlight, c.MaxMutatingRequestsInFlight, c.RequestContextMapper, c.LongRunningFunc)
		if utilfeature.DefaultFeatureGate.Enabled(features.AdvancedAuditing) {
			handler = genericapifilters.WithAudit(handler, c.RequestContextMapper, c.AuditBackend, c.AuditPolicyChecker, c.LongRunningFunc)
		}
		handler = genericfilters.WithTimeoutForNonLongRunningRequests(handler, c.RequestContextMapper, c.LongRunningFunc, c.RequestTimeout)
		handler = genericapifilters.WithRequestInfo(handler, genericapiserver.NewRequestInfoResolver(c), c.RequestContextMapper)
		handler = apirequest.WithRequestContext(handler, c.RequestContextMapper)
		handler = genericfilters.WithPanicRecovery(handler)

		return handler
	}
}

func (c completedConfig) addAssets(serverMux *genericmux.PathRecorderMux) error {
	assetHandler, err := c.buildAssetHandler()
	if err != nil {
		return err
	}

	serverMux.UnlistedHandlePrefix(c.ExtraConfig.PublicURL.Path, http.StripPrefix(c.ExtraConfig.PublicURL.Path, assetHandler))
	serverMux.UnlistedHandle(c.ExtraConfig.PublicURL.Path[0:len(c.ExtraConfig.PublicURL.Path)-1], http.RedirectHandler(c.ExtraConfig.PublicURL.Path, http.StatusMovedPermanently))
	return nil
}

func (c completedConfig) addExtensionScripts(serverMux *genericmux.PathRecorderMux) error {
	// Extension scripts
	extScriptsPath := path.Join(c.ExtraConfig.PublicURL.Path, "scripts/extensions.js")
	extScriptsHandler, err := assets.ExtensionScriptsHandler(c.ExtraConfig.Options.ExtensionScripts, c.ExtraConfig.Options.ExtensionDevelopment)
	if err != nil {
		return err
	}
	extScriptsHandler = assets.SecurityHeadersHandler(extScriptsHandler)
	serverMux.UnlistedHandle(extScriptsPath, assets.GzipHandler(extScriptsHandler))
	return nil
}

func (c completedConfig) addExtensionStyleSheets(serverMux *genericmux.PathRecorderMux) error {
	// Extension stylesheets
	extStylesheetsPath := path.Join(c.ExtraConfig.PublicURL.Path, "styles/extensions.css")
	extStylesheetsHandler, err := assets.ExtensionStylesheetsHandler(c.ExtraConfig.Options.ExtensionStylesheets, c.ExtraConfig.Options.ExtensionDevelopment)
	if err != nil {
		return err
	}
	extStylesheetsHandler = assets.SecurityHeadersHandler(extStylesheetsHandler)
	serverMux.UnlistedHandle(extStylesheetsPath, assets.GzipHandler(extStylesheetsHandler))
	return nil
}

func (c completedConfig) addExtensionFiles(serverMux *genericmux.PathRecorderMux) {
	// Extension files
	for _, extConfig := range c.ExtraConfig.Options.Extensions {
		extBasePath := path.Join(c.ExtraConfig.PublicURL.Path, "extensions", extConfig.Name)
		extPath := extBasePath + "/"
		extHandler := assets.AssetExtensionHandler(extConfig.SourceDirectory, extPath, extConfig.HTML5Mode)
		serverMux.UnlistedHandlePrefix(extPath, http.StripPrefix(extBasePath, extHandler))
		serverMux.UnlistedHandle(extBasePath, http.RedirectHandler(extPath, http.StatusMovedPermanently))
	}
}

func (c *completedConfig) addWebConsoleConfig(serverMux *genericmux.PathRecorderMux) error {
	masterURL, err := url.Parse(c.ExtraConfig.Options.MasterPublicURL)
	if err != nil {
		return err
	}

	// Generated web console config and server version
	config := assets.WebConsoleConfig{
		APIGroupAddr:      masterURL.Host,
		APIGroupPrefix:    server.APIGroupPrefix,
		MasterAddr:        masterURL.Host,
		MasterPrefix:      api.Prefix,
		KubernetesAddr:    masterURL.Host,
		KubernetesPrefix:  server.DefaultLegacyAPIPrefix,
		OAuthAuthorizeURI: oauthutil.OpenShiftOAuthAuthorizeURL(masterURL.String()),
		OAuthTokenURI:     oauthutil.OpenShiftOAuthTokenURL(masterURL.String()),
		OAuthRedirectBase: c.ExtraConfig.Options.PublicURL,
		OAuthClientID:     OpenShiftWebConsoleClientID,
		LogoutURI:         c.ExtraConfig.Options.LogoutURL,
		LoggingURL:        c.ExtraConfig.Options.LoggingPublicURL,
		MetricsURL:        c.ExtraConfig.Options.MetricsPublicURL,
	}
	kVersionInfo := kversion.Get()
	oVersionInfo := oversion.Get()
	versionInfo := assets.WebConsoleVersion{
		KubernetesVersion: kVersionInfo.GitVersion,
		OpenShiftVersion:  oVersionInfo.GitVersion,
	}

	extensionProps := assets.WebConsoleExtensionProperties{
		ExtensionProperties: extensionPropertyArray(c.ExtraConfig.Options.ExtensionProperties),
	}
	configPath := path.Join(c.ExtraConfig.PublicURL.Path, "config.js")
	configHandler, err := assets.GeneratedConfigHandler(config, versionInfo, extensionProps)
	configHandler = assets.SecurityHeadersHandler(configHandler)
	if err != nil {
		return err
	}
	serverMux.UnlistedHandle(configPath, assets.GzipHandler(configHandler))

	return nil
}

func (c completedConfig) buildAssetHandler() (http.Handler, error) {
	assets.RegisterMimeTypes()

	assetFunc := assets.JoinAssetFuncs(assets.Asset, java.Asset)
	assetDirFunc := assets.JoinAssetDirFuncs(assets.AssetDir, java.AssetDir)

	handler := http.FileServer(&assetfs.AssetFS{Asset: assetFunc, AssetDir: assetDirFunc, Prefix: ""})

	// Map of context roots (no leading or trailing slash) to the asset path to serve for requests to a missing asset
	subcontextMap := map[string]string{
		"":     "index.html",
		"java": "java/index.html",
	}

	var err error
	handler, err = assets.HTML5ModeHandler(c.ExtraConfig.PublicURL.Path, subcontextMap, handler, assetFunc)
	if err != nil {
		return nil, err
	}

	// Cache control should happen after all Vary headers are added, but before
	// any asset related routing (HTML5ModeHandler and FileServer)
	handler = assets.CacheControlHandler(oversion.Get().GitCommit, handler)

	handler = assets.SecurityHeadersHandler(handler)

	// Gzip first so that inner handlers can react to the addition of the Vary header
	handler = assets.GzipHandler(handler)

	return handler, nil
}

// Have to convert to arrays because go templates are limited and we need to be able to know
// if we are on the last index for trailing commas in JSON
func extensionPropertyArray(extensionProperties map[string]string) []assets.WebConsoleExtensionProperty {
	extensionPropsArray := []assets.WebConsoleExtensionProperty{}
	for key, value := range extensionProperties {
		extensionPropsArray = append(extensionPropsArray, assets.WebConsoleExtensionProperty{
			Key:   key,
			Value: value,
		})
	}
	return extensionPropsArray
}

// Run starts an http server for the static assets listening on the configured
// bind address
func RunAssetServer(assetServer *AssetServer, stopCh <-chan struct{}) error {
	go assetServer.GenericAPIServer.PrepareRun().Run(stopCh)

	glog.Infof("Web console listening at https://%s", assetServer.BindAddress)
	glog.Infof("Web console available at %s", assetServer.PublicURL.String())

	// Attempt to verify the server came up for 20 seconds (100 tries * 100ms, 100ms timeout per try)
	return cmdutil.WaitForSuccessfulDial(true, assetServer.BindNetwork, assetServer.BindAddress, 100*time.Millisecond, 100*time.Millisecond, 100)
}

// If we know the location of the asset server, redirect to it when / is requested
// and the Accept header supports text/html
func WithAssetServerRedirect(handler http.Handler, assetPublicURL string) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		if req.URL.Path == "/" {
			if httprequest.PrefersHTML(req) {
				http.Redirect(w, req, assetPublicURL, http.StatusFound)
			}
		}
		// Dispatch to the next handler
		handler.ServeHTTP(w, req)
	})
}
<|endoftext|>


<commit_before>// (C) Copyright 2017 Hewlett Packard Enterprise Development LP
// 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.
<commit_msg>lustre_exporter.go: Initial commit<commit_after>// (C) Copyright 2017 Hewlett Packard Enterprise Development LP
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"flag"
	"fmt"
	"net/http"
	"os"
	"strings"

	"github.com/joehandzik/lustre_exporter/exporter"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/common/log"
)

var (
	showVersion	= flag.Bool("version", false, "Print version information.")
	listenAddress	= flag.String("web.listen-address", ":9119", "Address to use to expose Lustre metrics.")
	metricsPath	= flag.String("web.telemetry-path", "/metrics", "Path to use to expose Lustre metrics.")
)

func init() {
	prometheus.MustRegister(version.NewCollector("lustre_exporter"))
}

func main() {
	flag.Parse()

	if *showVersion {
		fmt.Fprintln(os.Stdout, version.Print("lustre_exporter"))
		os.Exit(0)
	}

	log.Infoln("Starting lustre_exporter", version.Info())
	log.Infoln("Build context", version.BuildContext())

	prometheus.MustRegister(lustre.NewExporter())

	handler := prometheus.Handler()

	http.Handle(*metricsPath, handler)
	http.HandleFunc("/" func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte(`
			<title>Lustre Exporter</title>

Lustre Exporter

<a href="` + *metricsPath + `">Metrics

			`))
	})

	log.Infoln("Listening on", *listenAddress)
	err = http.ListenAndServe(*listenAddress, nil)
	if err != nil {
		log.Fatal(err)
	}
}
}
<|endoftext|>


<commit_before>/**
 * This file is a bit funny. The goal here is to use setns() to manipulate
 * files inside the container, so we don't have to reason about the paths to
 * make sure they don't escape (we can simply rely on the kernel for
 * correctness). Unfortunately, you can't setns() to a mount namespace with a
 * multi-threaded program, which every golang binary is. However, by declaring
 * our init as an initializer, we can capture process control before it is
 * transferred to the golang runtime, so we can then setns() as we'd like
 * before golang has a chance to set up any threads. So, we implement two new
 * lxd fork* commands which are captured here, and take a file on the host fs
 * and copy it into the container ns.
 *
 * An alternative to this would be to move this code into a separate binary,
 * which of course has problems of its own when it comes to packaging (how do
 * we find the binary, what do we do if someone does file push and it is
 * missing, etc.). After some discussion, even though the embedded method is
 * somewhat convoluted, it was preferred.
 */
package main

/*
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <linux/limits.h>
#include <sched.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

// External functions
extern void forkfile();
extern void forkmount();
extern void forknet();
extern void forkproxy();

// Command line parsing and tracking
#define CMDLINE_SIZE (8 * PATH_MAX)
char cmdline_buf[CMDLINE_SIZE];
char *cmdline_cur = NULL;
ssize_t cmdline_size = -1;

char* advance_arg(bool required) {
	while (*cmdline_cur != 0)
		cmdline_cur++;

	cmdline_cur++;
	if (cmdline_size <= cmdline_cur - cmdline_buf) {
		if (!required)
			return NULL;

		fprintf(stderr, "not enough arguments\n");
		_exit(1);
	}

	return cmdline_cur;
}

void error(char *msg)
{
	int old_errno = errno;

	if (old_errno == 0) {
		fprintf(stderr, "%s\n", msg);
		fprintf(stderr, "errno: 0\n");
		return;
	}

	perror(msg);
	fprintf(stderr, "errno: %d\n", old_errno);
}

int dosetns(int pid, char *nstype) {
	int mntns;
	char buf[PATH_MAX];

	sprintf(buf, "/proc/%d/ns/%s", pid, nstype);
	mntns = open(buf, O_RDONLY);
	if (mntns < 0) {
		error("error: open mntns");
		return -1;
	}

	if (setns(mntns, 0) < 0) {
		error("error: setns");
		close(mntns);
		return -1;
	}
	close(mntns);

	return 0;
}

void attach_userns(int pid) {
	char nspath[PATH_MAX];
	char userns_source[22];
	char userns_target[22];
	ssize_t len = 0;

	sprintf(nspath, "/proc/%d/ns/user", pid);
	if (access(nspath, F_OK) == 0) {
		len = readlink("/proc/self/ns/user", userns_source, 21);
		if (len < 0) {
			fprintf(stderr, "Failed readlink of source namespace: %s\n", strerror(errno));
			_exit(1);
		}
		userns_source[len] = '\0';

		len = readlink(nspath, userns_target, 21);
		if (len < 0) {
			fprintf(stderr, "Failed readlink of target namespace: %s\n", strerror(errno));
			_exit(1);
		}
		userns_target[len] = '\0';

		if (strcmp(userns_source, userns_target) != 0) {
			if (dosetns(pid, "user") < 0) {
				fprintf(stderr, "Failed setns to container user namespace: %s\n", strerror(errno));
				_exit(1);
			}

			if (setgroups(0, NULL) < 0) {
				fprintf(stderr, "Failed setgroups to container root groups: %s\n", strerror(errno));
				_exit(1);
			}

			if (setgid(0) < 0) {
				fprintf(stderr, "Failed setgid to container root group: %s\n", strerror(errno));
				_exit(1);
			}

			if (setuid(0) < 0) {
				fprintf(stderr, "Failed setuid to container root user: %s\n", strerror(errno));
				_exit(1);
			}

		}
	}
}

__attribute__((constructor)) void init(void) {
	int cmdline;

	// Extract arguments
	cmdline = open("/proc/self/cmdline", O_RDONLY);
	if (cmdline < 0) {
		error("error: open");
		_exit(232);
	}

	memset(cmdline_buf, 0, sizeof(cmdline_buf));
	if ((cmdline_size = read(cmdline, cmdline_buf, sizeof(cmdline_buf)-1)) < 0) {
		close(cmdline);
		error("error: read");
		_exit(232);
	}
	close(cmdline);

	// Skip the first argument (but don't fail on missing second argument)
	cmdline_cur = cmdline_buf;
	while (*cmdline_cur != 0)
		cmdline_cur++;
	cmdline_cur++;
	if (cmdline_size <= cmdline_cur - cmdline_buf)
		return;

	// Intercepts some subcommands
	if (strcmp(cmdline_cur, "forkfile") == 0) {
		forkfile();
	} else if (strcmp(cmdline_cur, "forkmount") == 0) {
		forkmount();
	} else if (strcmp(cmdline_cur, "forknet") == 0) {
		forknet();
	} else if (strcmp(cmdline_cur, "forkproxy") == 0) {
		forkproxy();
	}
}
*/
import "C"
<commit_msg>nsexec: simplify attach_userns()<commit_after>/**
 * This file is a bit funny. The goal here is to use setns() to manipulate
 * files inside the container, so we don't have to reason about the paths to
 * make sure they don't escape (we can simply rely on the kernel for
 * correctness). Unfortunately, you can't setns() to a mount namespace with a
 * multi-threaded program, which every golang binary is. However, by declaring
 * our init as an initializer, we can capture process control before it is
 * transferred to the golang runtime, so we can then setns() as we'd like
 * before golang has a chance to set up any threads. So, we implement two new
 * lxd fork* commands which are captured here, and take a file on the host fs
 * and copy it into the container ns.
 *
 * An alternative to this would be to move this code into a separate binary,
 * which of course has problems of its own when it comes to packaging (how do
 * we find the binary, what do we do if someone does file push and it is
 * missing, etc.). After some discussion, even though the embedded method is
 * somewhat convoluted, it was preferred.
 */
package main

/*
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <linux/limits.h>
#include <sched.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

// External functions
extern void forkfile();
extern void forkmount();
extern void forknet();
extern void forkproxy();

// Command line parsing and tracking
#define CMDLINE_SIZE (8 * PATH_MAX)
char cmdline_buf[CMDLINE_SIZE];
char *cmdline_cur = NULL;
ssize_t cmdline_size = -1;

char* advance_arg(bool required) {
	while (*cmdline_cur != 0)
		cmdline_cur++;

	cmdline_cur++;
	if (cmdline_size <= cmdline_cur - cmdline_buf) {
		if (!required)
			return NULL;

		fprintf(stderr, "not enough arguments\n");
		_exit(1);
	}

	return cmdline_cur;
}

void error(char *msg)
{
	int old_errno = errno;

	if (old_errno == 0) {
		fprintf(stderr, "%s\n", msg);
		fprintf(stderr, "errno: 0\n");
		return;
	}

	perror(msg);
	fprintf(stderr, "errno: %d\n", old_errno);
}

int dosetns(int pid, char *nstype) {
	int mntns;
	char buf[PATH_MAX];

	sprintf(buf, "/proc/%d/ns/%s", pid, nstype);
	mntns = open(buf, O_RDONLY);
	if (mntns < 0) {
		error("error: open mntns");
		return -1;
	}

	if (setns(mntns, 0) < 0) {
		error("error: setns");
		close(mntns);
		return -1;
	}
	close(mntns);

	return 0;
}

static int preserve_ns(const int pid, const char *ns)
{
	int ret;
// 5 /proc + 21 /int_as_str + 3 /ns + 20 /NS_NAME + 1 \0
#define __NS_PATH_LEN 50
	char path[__NS_PATH_LEN];

	// This way we can use this function to also check whether namespaces
	// are supported by the kernel by passing in the NULL or the empty
	// string.
	ret = snprintf(path, __NS_PATH_LEN, "/proc/%d/ns%s%s", pid,
		       !ns || strcmp(ns, "") == 0 ? "" : "/",
		       !ns || strcmp(ns, "") == 0 ? "" : ns);
	errno = EFBIG;
	if (ret < 0 || (size_t)ret >= __NS_PATH_LEN)
		return -EFBIG;

	return open(path, O_RDONLY | O_CLOEXEC);
}

// in_same_namespace - Check whether two processes are in the same namespace.
// @pid1 - PID of the first process.
// @pid2 - PID of the second process.
// @ns   - Name of the namespace to check. Must correspond to one of the names
//         for the namespaces as shown in /proc/<pid/ns/
//
// If the two processes are not in the same namespace returns an fd to the
// namespace of the second process identified by @pid2. If the two processes are
// in the same namespace returns -EINVAL, -1 if an error occurred.
static int in_same_namespace(pid_t pid1, pid_t pid2, const char *ns)
{
	int ns_fd1 = -1, ns_fd2 = -1, ret = -1;
	struct stat ns_st1, ns_st2;

	ns_fd1 = preserve_ns(pid1, ns);
	if (ns_fd1 < 0) {
		// The kernel does not support this namespace. This is not an
		// error.
		if (errno == ENOENT)
			return -EINVAL;

		goto out;
	}

	ns_fd2 = preserve_ns(pid2, ns);
	if (ns_fd2 < 0)
		goto out;

	ret = fstat(ns_fd1, &ns_st1);
	if (ret < 0)
		goto out;

	ret = fstat(ns_fd2, &ns_st2);
	if (ret < 0)
		goto out;

	// processes are in the same namespace
	ret = -EINVAL;
	if ((ns_st1.st_dev == ns_st2.st_dev ) && (ns_st1.st_ino == ns_st2.st_ino))
		goto out;

	// processes are in different namespaces
	ret = ns_fd2;
	ns_fd2 = -1;

out:

	if (ns_fd1 >= 0)
		close(ns_fd1);
	if (ns_fd2 >= 0)
		close(ns_fd2);

	return ret;
}

void attach_userns(int pid) {
	int ret, userns_fd;

	userns_fd = in_same_namespace(getpid(), pid, "user");
	if (userns_fd < 0) {
		if (userns_fd == -EINVAL)
			_exit(EXIT_SUCCESS);

		_exit(EXIT_FAILURE);
	}

	ret = setns(userns_fd, CLONE_NEWUSER);
	if (ret < 0) {
		fprintf(stderr, "Failed setns to container user namespace: %s\n", strerror(errno));
		_exit(EXIT_FAILURE);
	}

	ret = setuid(0);
	if (ret < 0) {
		fprintf(stderr, "Failed setuid to container root user: %s\n", strerror(errno));
		_exit(1);
	}

	ret = setgid(0);
	if (ret < 0) {
		fprintf(stderr, "Failed setgid to container root group: %s\n", strerror(errno));
		_exit(1);
	}

	ret = setgroups(0, NULL);
	if (ret < 0) {
		fprintf(stderr, "Failed setgroups to container root groups: %s\n", strerror(errno));
		_exit(1);
	}
}

__attribute__((constructor)) void init(void) {
	int cmdline;

	// Extract arguments
	cmdline = open("/proc/self/cmdline", O_RDONLY);
	if (cmdline < 0) {
		error("error: open");
		_exit(232);
	}

	memset(cmdline_buf, 0, sizeof(cmdline_buf));
	if ((cmdline_size = read(cmdline, cmdline_buf, sizeof(cmdline_buf)-1)) < 0) {
		close(cmdline);
		error("error: read");
		_exit(232);
	}
	close(cmdline);

	// Skip the first argument (but don't fail on missing second argument)
	cmdline_cur = cmdline_buf;
	while (*cmdline_cur != 0)
		cmdline_cur++;
	cmdline_cur++;
	if (cmdline_size <= cmdline_cur - cmdline_buf)
		return;

	// Intercepts some subcommands
	if (strcmp(cmdline_cur, "forkfile") == 0) {
		forkfile();
	} else if (strcmp(cmdline_cur, "forkmount") == 0) {
		forkmount();
	} else if (strcmp(cmdline_cur, "forknet") == 0) {
		forknet();
	} else if (strcmp(cmdline_cur, "forkproxy") == 0) {
		forkproxy();
	}
}
*/
import "C"
<|endoftext|>


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

// +build !appengine

package db

import (
	"fmt"
	"io"
	"os"
	"os/signal"
	"strconv"
	"strings"
	"time"

	"github.com/peterh/liner"
	"golang.org/x/net/context"

	"github.com/cayleygraph/cayley/clog"
	"github.com/cayleygraph/cayley/graph"
	"github.com/cayleygraph/cayley/quad/nquads"
	"github.com/cayleygraph/cayley/query"
)

func trace(s string) (string, time.Time) {
	return s, time.Now()
}

func un(s string, startTime time.Time) {
	endTime := time.Now()

	fmt.Printf(s, float64(endTime.UnixNano()-startTime.UnixNano())/float64(1E6))
}

func Run(ctx context.Context, qu string, ses query.REPLSession) error {
	nResults := 0
	startTrace, startTime := trace("Elapsed time: %g ms\n\n")
	defer func() {
		if nResults > 0 {
			un(startTrace, startTime)
		}
	}()
	fmt.Printf("\n")
	c := make(chan query.Result, 5)
	go ses.Execute(ctx, qu, c, 100)
	for res := range c {
		if err := res.Err(); err != nil {
			return err
		}
		fmt.Print(ses.FormatREPL(res))
		nResults++
	}
	if nResults > 0 {
		results := "Result"
		if nResults > 1 {
			results += "s"
		}
		fmt.Printf("-----------\n%d %s\n", nResults, results)
	}
	return nil
}

const (
	defaultLanguage = "gizmo"

	ps1 = "cayley> "
	ps2 = "...     "

	history = ".cayley_history"
)

func Repl(ctx context.Context, h *graph.Handle, queryLanguage string, timeout time.Duration) error {
	if queryLanguage == "" {
		queryLanguage = defaultLanguage
	}
	l := query.GetLanguage(queryLanguage)
	if l == nil || l.REPL == nil {
		return fmt.Errorf("unsupported query language: %q", queryLanguage)
	}
	ses := l.REPL(h.QuadStore)

	term, err := terminal(history)
	if os.IsNotExist(err) {
		fmt.Printf("creating new history file: %q\n", history)
	}
	defer persist(term, history)

	var (
		prompt = ps1

		code string
	)

	newCtx := func() (context.Context, func()) { return ctx, func() {} }
	if timeout > 0 {
		newCtx = func() (context.Context, func()) { return context.WithTimeout(ctx, timeout) }
	}

	for {
		select {
		case <-ctx.Done():
			return ctx.Err()
		default:
		}
		if len(code) == 0 {
			prompt = ps1
		} else {
			prompt = ps2
		}
		line, err := term.Prompt(prompt)
		if err != nil {
			if err == io.EOF {
				fmt.Println()
				return nil
			}
			return err
		}

		term.AppendHistory(line)

		line = strings.TrimSpace(line)
		if len(line) == 0 || line[0] == '#' {
			continue
		}

		if code == "" {
			cmd, args := splitLine(line)

			switch cmd {
			case ":debug":
				args = strings.TrimSpace(args)
				var debug bool
				switch args {
				case "t":
					debug = true
				case "f":
					// Do nothing.
				default:
					debug, err = strconv.ParseBool(args)
					if err != nil {
						fmt.Printf("Error: cannot parse %q as a valid boolean - acceptable values: 't'|'true' or 'f'|'false'\n", args)
						continue
					}
				}
				if debug {
					clog.SetV(2)
				} else {
					clog.SetV(0)
				}
				fmt.Printf("Debug set to %t\n", debug)
				continue

			case ":a":
				quad, err := nquads.Parse(args)
				if err == nil {
					err = h.QuadWriter.AddQuad(quad)
				}
				if err != nil {
					fmt.Printf("Error: not a valid quad: %v\n", err)
					continue
				}
				continue

			case ":d":
				quad, err := nquads.Parse(args)
				if err != nil {
					fmt.Printf("Error: not a valid quad: %v\n", err)
					continue
				}
				err = h.QuadWriter.RemoveQuad(quad)
				if err != nil {
					fmt.Printf("error deleting: %v\n", err)
				}
				continue

			case "exit":
				term.Close()
				os.Exit(0)

			default:
				if cmd[0] == ':' {
					fmt.Printf("Unknown command: %q\n", cmd)
					continue
				}
			}
		}

		code += line

		nctx, cancel := newCtx()
		err = Run(nctx, code, ses)
		cancel()
		if err == query.ErrParseMore {
			// collect more input
		} else if err != nil {
			fmt.Println("Error: ", err)
			code = ""
		} else {
			code = ""
		}
	}
}

// Splits a line into a command and its arguments
// e.g. ":a b c d ." will be split into ":a" and " b c d ."
func splitLine(line string) (string, string) {
	var command, arguments string

	line = strings.TrimSpace(line)

	// An empty line/a line consisting of whitespace contains neither command nor arguments
	if len(line) > 0 {
		command = strings.Fields(line)[0]

		// A line containing only a command has no arguments
		if len(line) > len(command) {
			arguments = line[len(command):]
		}
	}

	return command, arguments
}

func terminal(path string) (*liner.State, error) {
	term := liner.NewLiner()

	go func() {
		c := make(chan os.Signal, 1)
		signal.Notify(c, os.Interrupt, os.Kill)
		<-c

		err := persist(term, history)
		if err != nil {
			fmt.Fprintf(os.Stderr, "failed to properly clean up terminal: %v\n", err)
			os.Exit(1)
		}

		os.Exit(0)
	}()

	f, err := os.Open(path)
	if err != nil {
		return term, err
	}
	defer f.Close()
	_, err = term.ReadHistory(f)
	return term, err
}

func persist(term *liner.State, path string) error {
	f, err := os.OpenFile(path, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
	if err != nil {
		return fmt.Errorf("could not open %q to append history: %v", path, err)
	}
	defer f.Close()
	_, err = term.WriteHistory(f)
	if err != nil {
		return fmt.Errorf("could not write history to %q: %v", path, err)
	}
	return term.Close()
}
<commit_msg>adding help to repl<commit_after>// Copyright 2014 The Cayley Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// +build !appengine

package db

import (
	"fmt"
	"io"
	"os"
	"os/signal"
	"strconv"
	"strings"
	"time"

	"github.com/peterh/liner"
	"golang.org/x/net/context"

	"github.com/cayleygraph/cayley/clog"
	"github.com/cayleygraph/cayley/graph"
	"github.com/cayleygraph/cayley/quad/nquads"
	"github.com/cayleygraph/cayley/query"
)

func trace(s string) (string, time.Time) {
	return s, time.Now()
}

func un(s string, startTime time.Time) {
	endTime := time.Now()

	fmt.Printf(s, float64(endTime.UnixNano()-startTime.UnixNano())/float64(1E6))
}

func Run(ctx context.Context, qu string, ses query.REPLSession) error {
	nResults := 0
	startTrace, startTime := trace("Elapsed time: %g ms\n\n")
	defer func() {
		if nResults > 0 {
			un(startTrace, startTime)
		}
	}()
	fmt.Printf("\n")
	c := make(chan query.Result, 5)
	go ses.Execute(ctx, qu, c, 100)
	for res := range c {
		if err := res.Err(); err != nil {
			return err
		}
		fmt.Print(ses.FormatREPL(res))
		nResults++
	}
	if nResults > 0 {
		results := "Result"
		if nResults > 1 {
			results += "s"
		}
		fmt.Printf("-----------\n%d %s\n", nResults, results)
	}
	return nil
}

const (
	defaultLanguage = "gizmo"

	ps1 = "cayley> "
	ps2 = "...     "

	history = ".cayley_history"
)

func Repl(ctx context.Context, h *graph.Handle, queryLanguage string, timeout time.Duration) error {
	if queryLanguage == "" {
		queryLanguage = defaultLanguage
	}
	l := query.GetLanguage(queryLanguage)
	if l == nil || l.REPL == nil {
		return fmt.Errorf("unsupported query language: %q", queryLanguage)
	}
	ses := l.REPL(h.QuadStore)

	term, err := terminal(history)
	if os.IsNotExist(err) {
		fmt.Printf("creating new history file: %q\n", history)
	}
	defer persist(term, history)

	var (
		prompt = ps1

		code string
	)

	newCtx := func() (context.Context, func()) { return ctx, func() {} }
	if timeout > 0 {
		newCtx = func() (context.Context, func()) { return context.WithTimeout(ctx, timeout) }
	}

	for {
		select {
		case <-ctx.Done():
			return ctx.Err()
		default:
		}
		if len(code) == 0 {
			prompt = ps1
		} else {
			prompt = ps2
		}
		line, err := term.Prompt(prompt)
		if err != nil {
			if err == io.EOF {
				fmt.Println()
				return nil
			}
			return err
		}

		term.AppendHistory(line)

		line = strings.TrimSpace(line)
		if len(line) == 0 || line[0] == '#' {
			continue
		}

		if code == "" {
			cmd, args := splitLine(line)

			switch cmd {
			case ":debug":
				args = strings.TrimSpace(args)
				var debug bool
				switch args {
				case "t":
					debug = true
				case "f":
					// Do nothing.
				default:
					debug, err = strconv.ParseBool(args)
					if err != nil {
						fmt.Printf("Error: cannot parse %q as a valid boolean - acceptable values: 't'|'true' or 'f'|'false'\n", args)
						continue
					}
				}
				if debug {
					clog.SetV(2)
				} else {
					clog.SetV(0)
				}
				fmt.Printf("Debug set to %t\n", debug)
				continue

			case ":a":
				quad, err := nquads.Parse(args)
				if err == nil {
					err = h.QuadWriter.AddQuad(quad)
				}
				if err != nil {
					fmt.Printf("Error: not a valid quad: %v\n", err)
					continue
				}
				continue

			case ":d":
				quad, err := nquads.Parse(args)
				if err != nil {
					fmt.Printf("Error: not a valid quad: %v\n", err)
					continue
				}
				err = h.QuadWriter.RemoveQuad(quad)
				if err != nil {
					fmt.Printf("error deleting: %v\n", err)
				}
				continue

			case "help":
				fmt.Printf("Help\n\texit // Exit\n\thelp // this help\n\td: <quad> // delete quad\n\ta: <quad> // add quad\n\t:debug [t|f]\n")
				continue

			case "exit":
				term.Close()
				os.Exit(0)

			default:
				if cmd[0] == ':' {
					fmt.Printf("Unknown command: %q\n", cmd)
					continue
				}
			}
		}

		code += line

		nctx, cancel := newCtx()
		err = Run(nctx, code, ses)
		cancel()
		if err == query.ErrParseMore {
			// collect more input
		} else if err != nil {
			fmt.Println("Error: ", err)
			code = ""
		} else {
			code = ""
		}
	}
}

// Splits a line into a command and its arguments
// e.g. ":a b c d ." will be split into ":a" and " b c d ."
func splitLine(line string) (string, string) {
	var command, arguments string

	line = strings.TrimSpace(line)

	// An empty line/a line consisting of whitespace contains neither command nor arguments
	if len(line) > 0 {
		command = strings.Fields(line)[0]

		// A line containing only a command has no arguments
		if len(line) > len(command) {
			arguments = line[len(command):]
		}
	}

	return command, arguments
}

func terminal(path string) (*liner.State, error) {
	term := liner.NewLiner()

	go func() {
		c := make(chan os.Signal, 1)
		signal.Notify(c, os.Interrupt, os.Kill)
		<-c

		err := persist(term, history)
		if err != nil {
			fmt.Fprintf(os.Stderr, "failed to properly clean up terminal: %v\n", err)
			os.Exit(1)
		}

		os.Exit(0)
	}()

	f, err := os.Open(path)
	if err != nil {
		return term, err
	}
	defer f.Close()
	_, err = term.ReadHistory(f)
	return term, err
}

func persist(term *liner.State, path string) error {
	f, err := os.OpenFile(path, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
	if err != nil {
		return fmt.Errorf("could not open %q to append history: %v", path, err)
	}
	defer f.Close()
	_, err = term.WriteHistory(f)
	if err != nil {
		return fmt.Errorf("could not write history to %q: %v", path, err)
	}
	return term.Close()
}
<|endoftext|>


<commit_before>package git

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"
	"time"

	retry "github.com/avast/retry-go"
	gogit "github.com/go-git/go-git/v5"
	"github.com/go-git/go-git/v5/plumbing"
	"github.com/pkg/errors"
	gitconfig "github.com/tcnksm/go-gitconfig"
	"github.com/zaquestion/lab/internal/logger"
)

// Get internal lab logger instance
var log = logger.GetInstance()

// New looks up the git binary and returns a cmd which outputs to stdout
func New(args ...string) *exec.Cmd {
	gitPath, err := exec.LookPath("git")
	if err != nil {
		log.Fatal(err)
	}

	cmd := exec.Command(gitPath, args...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd
}

// Dir returns the full path to the .git directory
func Dir() (string, error) {
	cmd := New("rev-parse", "-q", "--git-dir")
	cmd.Stdout = nil
	cmd.Stderr = nil
	d, err := cmd.Output()
	if err != nil {
		return "", err
	}
	dir := string(d)
	dir = strings.TrimSpace(dir)
	if !filepath.IsAbs(dir) {
		dir, err = filepath.Abs(dir)
		if err != nil {
			return "", err
		}
	}

	return filepath.Clean(dir), nil
}

// WorkingDir returns the full path to the root of the current git repository
func WorkingDir() (string, error) {
	cmd := New("rev-parse", "--show-toplevel")
	cmd.Stdout = nil
	d, err := cmd.Output()
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(d)), nil
}

// CommentChar returns active comment char and defaults to '#'
func CommentChar() string {
	char, err := gitconfig.Entire("core.commentchar")
	if err == nil {
		return char
	}
	return "#"
}

// PagerCommand returns the commandline and environment for the pager
func PagerCommand() (string, []string) {
	// Set up environment for common pagers, see the documentation
	// for "core.pager" in git-config(1)
	env := os.Environ()
	if _, ok := os.LookupEnv("LESS"); !ok {
		env = append(env, "LESS=FRX")
	}
	if _, ok := os.LookupEnv("LESSSECURE"); !ok {
		env = append(env, "LESSSECURE=1")
	}
	if _, ok := os.LookupEnv("LV"); !ok {
		env = append(env, "LV=-c")
	}

	// Find an appropriate pager command, following git's preference
	cmd, ok := os.LookupEnv("GIT_PAGER")
	if ok {
		return cmd, env
	}
	cmd, err := gitconfig.Entire("core.pager")
	if err == nil {
		return cmd, env
	}
	cmd, ok = os.LookupEnv("PAGER")
	if ok {
		return cmd, env
	}
	return "less", env
}

// LastCommitMessage returns the last commits message as one line
func LastCommitMessage() (string, error) {
	cmd := New("show", "-s", "--format=%s%n%+b", "HEAD")
	cmd.Stdout = nil
	msg, err := cmd.Output()
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(msg)), nil
}

// Log produces a formatted gitlog between 2 git shas
func Log(sha1, sha2 string) (string, error) {
	cmd := New("-c", "log.showSignature=false",
		"log",
		"--no-color",
		"--format=%h (%aN)%n%w(78,3,3)%s%n",
		"--cherry",
		fmt.Sprintf("%s...%s", sha1, sha2))
	cmd.Stdout = nil
	outputs, err := cmd.Output()
	if err != nil {
		return "", errors.Errorf("Can't load git log %s..%s", sha1, sha2)
	}

	diffCmd := New("diff", "--stat", sha1)
	diffCmd.Stdout = nil
	diffOutput, err := diffCmd.Output()
	if err != nil {
		return "", errors.Errorf("Can't load diffstat")
	}

	return string(outputs) + string(diffOutput), nil
}

// CurrentBranch returns the currently checked out branch
func CurrentBranch() (string, error) {
	cmd := New("rev-parse", "--abbrev-ref", "HEAD")
	cmd.Stdout = nil
	branch, err := cmd.Output()
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(branch)), nil
}

// CurrentUpstreamBranch returns the upstream of the currently checked out branch
func CurrentUpstreamBranch() (string, error) {
	localBranch, err := CurrentBranch()
	if err != nil {
		return "", err
	}

	branch, err := UpstreamBranch(localBranch)
	if err != nil {
		return "", err
	}
	return branch, nil
}

// UpstreamBranch returns the upstream of the specified branch
func UpstreamBranch(branch string) (string, error) {
	upstreamBranch, err := gitconfig.Local("branch." + branch + ".merge")
	if err != nil {
		return "", errors.Errorf("No upstream for branch '%s'", branch)
	}
	return strings.TrimPrefix(upstreamBranch, "refs/heads/"), nil
}

// PathWithNameSpace returns the owner/repository for the current repo
// Such as zaquestion/lab
// Respects GitLab subgroups (https://docs.gitlab.com/ce/user/group/subgroups/)
func PathWithNameSpace(remote string) (string, error) {
	remoteURL, err := gitconfig.Local("remote." + remote + ".pushurl")
	if err != nil || remoteURL == "" {
		remoteURL, err = gitconfig.Local("remote." + remote + ".url")
		if err != nil {
			return "", err
		}
	}

	parts := strings.Split(remoteURL, "//")

	if len(parts) == 1 {
		// scp-like short syntax (e.g. git@gitlab.com...)
		part := parts[0]
		parts = strings.Split(part, ":")
	} else if len(parts) == 2 {
		// every other protocol syntax (e.g. ssh://, http://, git://)
		part := parts[1]
		parts = strings.SplitN(part, "/", 2)
	} else {
		return "", errors.Errorf("cannot parse remote: %s url: %s", remote, remoteURL)
	}

	if len(parts) != 2 {
		return "", errors.Errorf("cannot parse remote: %s url: %s", remote, remoteURL)
	}
	path := parts[1]
	path = strings.TrimSuffix(path, ".git")
	return path, nil
}

// RepoName returns the name of the repository, such as "lab"
func RepoName() (string, error) {
	o, err := PathWithNameSpace("origin")
	if err != nil {
		return "", err
	}
	parts := strings.Split(o, "/")
	return parts[len(parts)-1:][0], nil
}

// RemoteAdd both adds a remote and fetches it
func RemoteAdd(name, url, dir string) error {
	cmd := New("remote", "add", name, url)
	cmd.Dir = dir
	if err := cmd.Run(); err != nil {
		return err
	}
	fmt.Println("Updating", name)

	err := retry.Do(func() error {
		cmd = New("fetch", name)
		cmd.Dir = dir
		return cmd.Run()
	}, retry.Attempts(3), retry.Delay(time.Second))
	if err != nil {
		return err
	}
	fmt.Println("new remote:", name)
	return nil
}

// Remotes get the list of remotes available in the current repo dir
func Remotes() ([]string, error) {
	repo, err := gogit.PlainOpen(".")
	if err != nil {
		return []string{}, err
	}
	remotes, err := repo.Remotes()
	if err != nil {
		return []string{}, err
	}

	names := make([]string, len(remotes))
	for i, r := range remotes {
		names[i] = r.Config().Name
	}
	return names, nil
}

// RemoteBranches get the list of branches the specified remote has
func RemoteBranches(remote string) ([]string, error) {
	repo, err := gogit.PlainOpen(".")
	if err != nil {
		return []string{}, err
	}

	branches, err := repo.References() // TODO verify is a branch Branches didn't seem to work
	if err != nil {
		return []string{}, err
	}
	reg := regexp.MustCompile(`^refs/remotes/[^/]+/`)

	names := []string{}
	branches.ForEach(func(ref *plumbing.Reference) error {
		if ref.Name().IsRemote() && strings.HasPrefix(ref.Name().String(), "refs/remotes/"+remote) {
			names = append(names, reg.ReplaceAllString(ref.Name().String(), ""))
		}
		return nil
	})
	return names, nil
}

// IsRemote returns true when passed a valid remote in the git repo
func IsRemote(remote string) (bool, error) {
	cmd := New("remote")
	cmd.Stdout = nil
	cmd.Stderr = nil
	remotes, err := cmd.Output()
	if err != nil {
		return false, err
	}

	return bytes.Contains(remotes, []byte(remote+"\n")), nil
}

// InsideGitRepo returns true when the current working directory is inside the
// working tree of a git repo
func InsideGitRepo() bool {
	cmd := New("rev-parse", "--is-inside-work-tree")
	cmd.Stdout = nil
	cmd.Stderr = nil
	out, _ := cmd.CombinedOutput()
	return bytes.Contains(out, []byte("true\n"))
}

// Fetch a commit from a given remote
func Fetch(remote, commit string) error {
	gitcmd := []string{"fetch", remote, commit}
	cmd := New(gitcmd...)
	cmd.Stdout = nil
	cmd.Stderr = nil
	err := cmd.Run()
	if err != nil {
		return errors.Errorf("Can't fetch git commit %s from remote %s", commit, remote)
	}
	return nil
}

// Show all the commits between 2 git commits
func Show(commit1, commit2 string, reverse bool) {
	gitcmd := []string{"show"}
	if reverse {
		gitcmd = append(gitcmd, "--reverse")
	}
	gitcmd = append(gitcmd, fmt.Sprintf("%s..%s", commit1, commit2))
	New(gitcmd...).Run()
}

// GetLocalRemotes returns a string of local remote names and URLs
func GetLocalRemotes() (string, error) {
	cmd := New("remote", "-v")
	cmd.Stdout = nil
	remotes, err := cmd.Output()
	if err != nil {
		return "", err
	}

	return string(remotes), nil
}

// GetLocalRemotesFromFile returns a string of local remote names and URLs based
// on their placement within .git/config file, which holds a different ordering
// compared to the alternatives presented by Remotes() and GetLocalRemotes().
func GetLocalRemotesFromFile() (string, error) {
	cmd := New("config", "--local", "--name-only", "--get-regex", "^remote.*")
	cmd.Stdout = nil
	remotes, err := cmd.Output()
	if err != nil {
		return "", err
	}

	return string(remotes), nil
}

// GetUnifiedDiff return the full/unified patch diff, with max context length
func GetUnifiedDiff(BaseSHA string, HeadSHA string, oldPath string, newPath string) (string, error) {
	// I hate magic numbers as much as the next person but I cannot
	// figure out a better way to get a unified diff for an entire file.
	cmd := New("diff", "-U99999999", "--no-renames", BaseSHA, HeadSHA, "--", oldPath, "--", newPath)
	cmd.Stdout = nil
	cmd.Stderr = nil
	diff, err := cmd.Output()
	if err != nil {
		return "", err
	}
	return string(diff), nil
}

// NumberCommits returns the number of commits between two commit refs
func NumberCommits(sha1, sha2 string) int {
	cmd := New("log", "--oneline", fmt.Sprintf("%s...%s", sha1, sha2))
	cmd.Stdout = nil
	cmd.Stderr = nil
	CmdOut, err := cmd.Output()
	if err != nil {
		// silently fail and handle the return of 0 at caller
		return 0
	}
	numLines := strings.Count(string(CmdOut), "\n")
	return numLines
}
<commit_msg>internal/git: fix git log command with union behavior<commit_after>package git

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"
	"time"

	retry "github.com/avast/retry-go"
	gogit "github.com/go-git/go-git/v5"
	"github.com/go-git/go-git/v5/plumbing"
	"github.com/pkg/errors"
	gitconfig "github.com/tcnksm/go-gitconfig"
	"github.com/zaquestion/lab/internal/logger"
)

// Get internal lab logger instance
var log = logger.GetInstance()

// New looks up the git binary and returns a cmd which outputs to stdout
func New(args ...string) *exec.Cmd {
	gitPath, err := exec.LookPath("git")
	if err != nil {
		log.Fatal(err)
	}

	cmd := exec.Command(gitPath, args...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd
}

// Dir returns the full path to the .git directory
func Dir() (string, error) {
	cmd := New("rev-parse", "-q", "--git-dir")
	cmd.Stdout = nil
	cmd.Stderr = nil
	d, err := cmd.Output()
	if err != nil {
		return "", err
	}
	dir := string(d)
	dir = strings.TrimSpace(dir)
	if !filepath.IsAbs(dir) {
		dir, err = filepath.Abs(dir)
		if err != nil {
			return "", err
		}
	}

	return filepath.Clean(dir), nil
}

// WorkingDir returns the full path to the root of the current git repository
func WorkingDir() (string, error) {
	cmd := New("rev-parse", "--show-toplevel")
	cmd.Stdout = nil
	d, err := cmd.Output()
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(d)), nil
}

// CommentChar returns active comment char and defaults to '#'
func CommentChar() string {
	char, err := gitconfig.Entire("core.commentchar")
	if err == nil {
		return char
	}
	return "#"
}

// PagerCommand returns the commandline and environment for the pager
func PagerCommand() (string, []string) {
	// Set up environment for common pagers, see the documentation
	// for "core.pager" in git-config(1)
	env := os.Environ()
	if _, ok := os.LookupEnv("LESS"); !ok {
		env = append(env, "LESS=FRX")
	}
	if _, ok := os.LookupEnv("LESSSECURE"); !ok {
		env = append(env, "LESSSECURE=1")
	}
	if _, ok := os.LookupEnv("LV"); !ok {
		env = append(env, "LV=-c")
	}

	// Find an appropriate pager command, following git's preference
	cmd, ok := os.LookupEnv("GIT_PAGER")
	if ok {
		return cmd, env
	}
	cmd, err := gitconfig.Entire("core.pager")
	if err == nil {
		return cmd, env
	}
	cmd, ok = os.LookupEnv("PAGER")
	if ok {
		return cmd, env
	}
	return "less", env
}

// LastCommitMessage returns the last commits message as one line
func LastCommitMessage() (string, error) {
	cmd := New("show", "-s", "--format=%s%n%+b", "HEAD")
	cmd.Stdout = nil
	msg, err := cmd.Output()
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(msg)), nil
}

// Log produces a formatted gitlog between 2 git shas
func Log(sha1, sha2 string) (string, error) {
	cmd := New("-c", "log.showSignature=false",
		"log",
		"--no-color",
		"--format=%h (%aN)%n%w(78,3,3)%s%n",
		"--cherry",
		fmt.Sprintf("%s..%s", sha1, sha2))
	cmd.Stdout = nil
	outputs, err := cmd.Output()
	if err != nil {
		return "", errors.Errorf("Can't load git log %s..%s", sha1, sha2)
	}

	diffCmd := New("diff", "--stat", sha1)
	diffCmd.Stdout = nil
	diffOutput, err := diffCmd.Output()
	if err != nil {
		return "", errors.Errorf("Can't load diffstat")
	}

	return string(outputs) + string(diffOutput), nil
}

// CurrentBranch returns the currently checked out branch
func CurrentBranch() (string, error) {
	cmd := New("rev-parse", "--abbrev-ref", "HEAD")
	cmd.Stdout = nil
	branch, err := cmd.Output()
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(branch)), nil
}

// CurrentUpstreamBranch returns the upstream of the currently checked out branch
func CurrentUpstreamBranch() (string, error) {
	localBranch, err := CurrentBranch()
	if err != nil {
		return "", err
	}

	branch, err := UpstreamBranch(localBranch)
	if err != nil {
		return "", err
	}
	return branch, nil
}

// UpstreamBranch returns the upstream of the specified branch
func UpstreamBranch(branch string) (string, error) {
	upstreamBranch, err := gitconfig.Local("branch." + branch + ".merge")
	if err != nil {
		return "", errors.Errorf("No upstream for branch '%s'", branch)
	}
	return strings.TrimPrefix(upstreamBranch, "refs/heads/"), nil
}

// PathWithNameSpace returns the owner/repository for the current repo
// Such as zaquestion/lab
// Respects GitLab subgroups (https://docs.gitlab.com/ce/user/group/subgroups/)
func PathWithNameSpace(remote string) (string, error) {
	remoteURL, err := gitconfig.Local("remote." + remote + ".pushurl")
	if err != nil || remoteURL == "" {
		remoteURL, err = gitconfig.Local("remote." + remote + ".url")
		if err != nil {
			return "", err
		}
	}

	parts := strings.Split(remoteURL, "//")

	if len(parts) == 1 {
		// scp-like short syntax (e.g. git@gitlab.com...)
		part := parts[0]
		parts = strings.Split(part, ":")
	} else if len(parts) == 2 {
		// every other protocol syntax (e.g. ssh://, http://, git://)
		part := parts[1]
		parts = strings.SplitN(part, "/", 2)
	} else {
		return "", errors.Errorf("cannot parse remote: %s url: %s", remote, remoteURL)
	}

	if len(parts) != 2 {
		return "", errors.Errorf("cannot parse remote: %s url: %s", remote, remoteURL)
	}
	path := parts[1]
	path = strings.TrimSuffix(path, ".git")
	return path, nil
}

// RepoName returns the name of the repository, such as "lab"
func RepoName() (string, error) {
	o, err := PathWithNameSpace("origin")
	if err != nil {
		return "", err
	}
	parts := strings.Split(o, "/")
	return parts[len(parts)-1:][0], nil
}

// RemoteAdd both adds a remote and fetches it
func RemoteAdd(name, url, dir string) error {
	cmd := New("remote", "add", name, url)
	cmd.Dir = dir
	if err := cmd.Run(); err != nil {
		return err
	}
	fmt.Println("Updating", name)

	err := retry.Do(func() error {
		cmd = New("fetch", name)
		cmd.Dir = dir
		return cmd.Run()
	}, retry.Attempts(3), retry.Delay(time.Second))
	if err != nil {
		return err
	}
	fmt.Println("new remote:", name)
	return nil
}

// Remotes get the list of remotes available in the current repo dir
func Remotes() ([]string, error) {
	repo, err := gogit.PlainOpen(".")
	if err != nil {
		return []string{}, err
	}
	remotes, err := repo.Remotes()
	if err != nil {
		return []string{}, err
	}

	names := make([]string, len(remotes))
	for i, r := range remotes {
		names[i] = r.Config().Name
	}
	return names, nil
}

// RemoteBranches get the list of branches the specified remote has
func RemoteBranches(remote string) ([]string, error) {
	repo, err := gogit.PlainOpen(".")
	if err != nil {
		return []string{}, err
	}

	branches, err := repo.References() // TODO verify is a branch Branches didn't seem to work
	if err != nil {
		return []string{}, err
	}
	reg := regexp.MustCompile(`^refs/remotes/[^/]+/`)

	names := []string{}
	branches.ForEach(func(ref *plumbing.Reference) error {
		if ref.Name().IsRemote() && strings.HasPrefix(ref.Name().String(), "refs/remotes/"+remote) {
			names = append(names, reg.ReplaceAllString(ref.Name().String(), ""))
		}
		return nil
	})
	return names, nil
}

// IsRemote returns true when passed a valid remote in the git repo
func IsRemote(remote string) (bool, error) {
	cmd := New("remote")
	cmd.Stdout = nil
	cmd.Stderr = nil
	remotes, err := cmd.Output()
	if err != nil {
		return false, err
	}

	return bytes.Contains(remotes, []byte(remote+"\n")), nil
}

// InsideGitRepo returns true when the current working directory is inside the
// working tree of a git repo
func InsideGitRepo() bool {
	cmd := New("rev-parse", "--is-inside-work-tree")
	cmd.Stdout = nil
	cmd.Stderr = nil
	out, _ := cmd.CombinedOutput()
	return bytes.Contains(out, []byte("true\n"))
}

// Fetch a commit from a given remote
func Fetch(remote, commit string) error {
	gitcmd := []string{"fetch", remote, commit}
	cmd := New(gitcmd...)
	cmd.Stdout = nil
	cmd.Stderr = nil
	err := cmd.Run()
	if err != nil {
		return errors.Errorf("Can't fetch git commit %s from remote %s", commit, remote)
	}
	return nil
}

// Show all the commits between 2 git commits
func Show(commit1, commit2 string, reverse bool) {
	gitcmd := []string{"show"}
	if reverse {
		gitcmd = append(gitcmd, "--reverse")
	}
	gitcmd = append(gitcmd, fmt.Sprintf("%s..%s", commit1, commit2))
	New(gitcmd...).Run()
}

// GetLocalRemotes returns a string of local remote names and URLs
func GetLocalRemotes() (string, error) {
	cmd := New("remote", "-v")
	cmd.Stdout = nil
	remotes, err := cmd.Output()
	if err != nil {
		return "", err
	}

	return string(remotes), nil
}

// GetLocalRemotesFromFile returns a string of local remote names and URLs based
// on their placement within .git/config file, which holds a different ordering
// compared to the alternatives presented by Remotes() and GetLocalRemotes().
func GetLocalRemotesFromFile() (string, error) {
	cmd := New("config", "--local", "--name-only", "--get-regex", "^remote.*")
	cmd.Stdout = nil
	remotes, err := cmd.Output()
	if err != nil {
		return "", err
	}

	return string(remotes), nil
}

// GetUnifiedDiff return the full/unified patch diff, with max context length
func GetUnifiedDiff(BaseSHA string, HeadSHA string, oldPath string, newPath string) (string, error) {
	// I hate magic numbers as much as the next person but I cannot
	// figure out a better way to get a unified diff for an entire file.
	cmd := New("diff", "-U99999999", "--no-renames", BaseSHA, HeadSHA, "--", oldPath, "--", newPath)
	cmd.Stdout = nil
	cmd.Stderr = nil
	diff, err := cmd.Output()
	if err != nil {
		return "", err
	}
	return string(diff), nil
}

// NumberCommits returns the number of commits between two commit refs
func NumberCommits(sha1, sha2 string) int {
	cmd := New("log", "--oneline", fmt.Sprintf("%s..%s", sha1, sha2))
	cmd.Stdout = nil
	cmd.Stderr = nil
	CmdOut, err := cmd.Output()
	if err != nil {
		// silently fail and handle the return of 0 at caller
		return 0
	}
	numLines := strings.Count(string(CmdOut), "\n")
	return numLines
}
<|endoftext|>


<commit_before>// Copyright 2015 CNI 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 main

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/vishvananda/netlink"

	"github.com/containernetworking/cni/pkg/skel"
	"github.com/containernetworking/cni/pkg/types"
	"github.com/containernetworking/cni/pkg/types/current"
	"github.com/containernetworking/cni/pkg/version"

	"github.com/containernetworking/plugins/pkg/ip"
	"github.com/containernetworking/plugins/pkg/ipam"
	"github.com/containernetworking/plugins/pkg/ns"
	bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)

const (
	sysBusPCI = "/sys/bus/pci/devices"
)

//NetConf for host-device config, look the README to learn how to use those parameters
type NetConf struct {
	types.NetConf
	Device     string `json:"device"`     // Device-Name, something like eth0 or can0 etc.
	HWAddr     string `json:"hwaddr"`     // MAC Address of target network interface
	KernelPath string `json:"kernelpath"` // Kernelpath of the device
	PCIAddr    string `json:"pciBusID"`   // PCI Address of target network device
}

func init() {
	// this ensures that main runs only on main thread (thread group leader).
	// since namespace ops (unshare, setns) are done for a single thread, we
	// must ensure that the goroutine does not jump from OS thread to thread
	runtime.LockOSThread()
}

func loadConf(bytes []byte) (*NetConf, error) {
	n := &NetConf{}
	if err := json.Unmarshal(bytes, n); err != nil {
		return nil, fmt.Errorf("failed to load netconf: %v", err)
	}
	if n.Device == "" && n.HWAddr == "" && n.KernelPath == "" && n.PCIAddr == "" {
		return nil, fmt.Errorf(`specify either "device", "hwaddr", "kernelpath" or "pciBusID"`)
	}
	return n, nil
}

func cmdAdd(args *skel.CmdArgs) error {
	cfg, err := loadConf(args.StdinData)
	if err != nil {
		return err
	}
	containerNs, err := ns.GetNS(args.Netns)
	if err != nil {
		return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
	}
	defer containerNs.Close()

	hostDev, err := getLink(cfg.Device, cfg.HWAddr, cfg.KernelPath, cfg.PCIAddr)
	if err != nil {
		return fmt.Errorf("failed to find host device: %v", err)
	}

	contDev, err := moveLinkIn(hostDev, containerNs, args.IfName)
	if err != nil {
		return fmt.Errorf("failed to move link %v", err)
	}

	var result *current.Result
	// run the IPAM plugin and get back the config to apply
	if cfg.IPAM.Type != "" {
		r, err := ipam.ExecAdd(cfg.IPAM.Type, args.StdinData)
		if err != nil {
			return err
		}

		// Invoke ipam del if err to avoid ip leak
		defer func() {
			if err != nil {
				ipam.ExecDel(cfg.IPAM.Type, args.StdinData)
			}
		}()

		// Convert whatever the IPAM result was into the current Result type
		result, err = current.NewResultFromResult(r)
		if err != nil {
			return err
		}

		if len(result.IPs) == 0 {
			return errors.New("IPAM plugin returned missing IP config")
		}

		result.Interfaces = []*current.Interface{{
			Name:    contDev.Attrs().Name,
			Mac:     contDev.Attrs().HardwareAddr.String(),
			Sandbox: containerNs.Path(),
		}}
		for _, ipc := range result.IPs {
			// All addresses apply to the container interface (move from host)
			ipc.Interface = current.Int(0)
		}

		err = containerNs.Do(func(_ ns.NetNS) error {
			if err := ipam.ConfigureIface(args.IfName, result); err != nil {
				return err
			}
			return nil
		})
		if err != nil {
			return err
		}

		result.DNS = cfg.DNS

		return types.PrintResult(result, cfg.CNIVersion)
	}

	return printLink(contDev, cfg.CNIVersion, containerNs)
}

func cmdDel(args *skel.CmdArgs) error {
	cfg, err := loadConf(args.StdinData)
	if err != nil {
		return err
	}
	if args.Netns == "" {
		return nil
	}
	containerNs, err := ns.GetNS(args.Netns)
	if err != nil {
		return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
	}
	defer containerNs.Close()

	if err := moveLinkOut(containerNs, args.IfName); err != nil {
		return err
	}

	if cfg.IPAM.Type != "" {
		if err := ipam.ExecDel(cfg.IPAM.Type, args.StdinData); err != nil {
			return err
		}
	}

	return nil
}

func moveLinkIn(hostDev netlink.Link, containerNs ns.NetNS, ifName string) (netlink.Link, error) {
	if err := netlink.LinkSetNsFd(hostDev, int(containerNs.Fd())); err != nil {
		return nil, err
	}

	var contDev netlink.Link
	if err := containerNs.Do(func(_ ns.NetNS) error {
		var err error
		contDev, err = netlink.LinkByName(hostDev.Attrs().Name)
		if err != nil {
			return fmt.Errorf("failed to find %q: %v", hostDev.Attrs().Name, err)
		}
		// Save host device name into the container device's alias property
		if err := netlink.LinkSetAlias(contDev, hostDev.Attrs().Name); err != nil {
			return fmt.Errorf("failed to set alias to %q: %v", hostDev.Attrs().Name, err)
		}
		// Rename container device to respect args.IfName
		if err := netlink.LinkSetName(contDev, ifName); err != nil {
			return fmt.Errorf("failed to rename device %q to %q: %v", hostDev.Attrs().Name, ifName, err)
		}
		// Retrieve link again to get up-to-date name and attributes
		contDev, err = netlink.LinkByName(ifName)
		if err != nil {
			return fmt.Errorf("failed to find %q: %v", ifName, err)
		}
		return nil
	}); err != nil {
		return nil, err
	}

	return contDev, nil
}

func moveLinkOut(containerNs ns.NetNS, ifName string) error {
	defaultNs, err := ns.GetCurrentNS()
	if err != nil {
		return err
	}
	defer defaultNs.Close()

	return containerNs.Do(func(_ ns.NetNS) error {
		dev, err := netlink.LinkByName(ifName)
		if err != nil {
			return fmt.Errorf("failed to find %q: %v", ifName, err)
		}

		// Devices can be renamed only when down
		if err = netlink.LinkSetDown(dev); err != nil {
			return fmt.Errorf("failed to set %q down: %v", ifName, err)
		}

		// Rename device to it's original name
		if err = netlink.LinkSetName(dev, dev.Attrs().Alias); err != nil {
			return fmt.Errorf("failed to restore %q to original name %q: %v", ifName, dev.Attrs().Alias, err)
		}
		defer func() {
			if err != nil {
				// if moving device to host namespace fails, we should revert device name
				// to ifName to make sure that device can be found in retries
				_ = netlink.LinkSetName(dev, ifName)
			}
		}()

		if err = netlink.LinkSetNsFd(dev, int(defaultNs.Fd())); err != nil {
			return fmt.Errorf("failed to move %q to host netns: %v", dev.Attrs().Alias, err)
		}
		return nil
	})
}

func printLink(dev netlink.Link, cniVersion string, containerNs ns.NetNS) error {
	result := current.Result{
		CNIVersion: current.ImplementedSpecVersion,
		Interfaces: []*current.Interface{
			{
				Name:    dev.Attrs().Name,
				Mac:     dev.Attrs().HardwareAddr.String(),
				Sandbox: containerNs.Path(),
			},
		},
	}
	return types.PrintResult(&result, cniVersion)
}

func getLink(devname, hwaddr, kernelpath, pciaddr string) (netlink.Link, error) {
	links, err := netlink.LinkList()
	if err != nil {
		return nil, fmt.Errorf("failed to list node links: %v", err)
	}

	if len(devname) > 0 {
		return netlink.LinkByName(devname)
	} else if len(hwaddr) > 0 {
		hwAddr, err := net.ParseMAC(hwaddr)
		if err != nil {
			return nil, fmt.Errorf("failed to parse MAC address %q: %v", hwaddr, err)
		}

		for _, link := range links {
			if bytes.Equal(link.Attrs().HardwareAddr, hwAddr) {
				return link, nil
			}
		}
	} else if len(kernelpath) > 0 {
		if !filepath.IsAbs(kernelpath) || !strings.HasPrefix(kernelpath, "/sys/devices/") {
			return nil, fmt.Errorf("kernel device path %q must be absolute and begin with /sys/devices/", kernelpath)
		}
		netDir := filepath.Join(kernelpath, "net")
		files, err := ioutil.ReadDir(netDir)
		if err != nil {
			return nil, fmt.Errorf("failed to find network devices at %q", netDir)
		}

		// Grab the first device from eg /sys/devices/pci0000:00/0000:00:19.0/net
		for _, file := range files {
			// Make sure it's really an interface
			for _, l := range links {
				if file.Name() == l.Attrs().Name {
					return l, nil
				}
			}
		}
	} else if len(pciaddr) > 0 {
		netDir := filepath.Join(sysBusPCI, pciaddr, "net")
		if _, err := os.Lstat(netDir); err != nil {
			return nil, fmt.Errorf("no net directory under pci device %s: %q", pciaddr, err)
		}
		fInfo, err := ioutil.ReadDir(netDir)
		if err != nil {
			return nil, fmt.Errorf("failed to read net directory %s: %q", netDir, err)
		}
		if len(fInfo) > 0 {
			return netlink.LinkByName(fInfo[0].Name())
		}
		return nil, fmt.Errorf("failed to find device name for pci address %s", pciaddr)
	}

	return nil, fmt.Errorf("failed to find physical interface")
}

func main() {
	skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("host-device"))
}

func cmdCheck(args *skel.CmdArgs) error {

	cfg, err := loadConf(args.StdinData)
	if err != nil {
		return err
	}
	netns, err := ns.GetNS(args.Netns)
	if err != nil {
		return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
	}
	defer netns.Close()

	// run the IPAM plugin and get back the config to apply
	if cfg.IPAM.Type != "" {
		err = ipam.ExecCheck(cfg.IPAM.Type, args.StdinData)
		if err != nil {
			return err
		}
	}

	// Parse previous result.
	if cfg.NetConf.RawPrevResult == nil {
		return fmt.Errorf("Required prevResult missing")
	}

	if err := version.ParsePrevResult(&cfg.NetConf); err != nil {
		return err
	}

	result, err := current.NewResultFromResult(cfg.PrevResult)
	if err != nil {
		return err
	}

	var contMap current.Interface
	// Find interfaces for name we know, that of host-device inside container
	for _, intf := range result.Interfaces {
		if args.IfName == intf.Name {
			if args.Netns == intf.Sandbox {
				contMap = *intf
				continue
			}
		}
	}

	// The namespace must be the same as what was configured
	if args.Netns != contMap.Sandbox {
		return fmt.Errorf("Sandbox in prevResult %s doesn't match configured netns: %s",
			contMap.Sandbox, args.Netns)
	}

	//
	// Check prevResults for ips, routes and dns against values found in the container
	if err := netns.Do(func(_ ns.NetNS) error {

		// Check interface against values found in the container
		err := validateCniContainerInterface(contMap)
		if err != nil {
			return err
		}

		err = ip.ValidateExpectedInterfaceIPs(args.IfName, result.IPs)
		if err != nil {
			return err
		}

		err = ip.ValidateExpectedRoute(result.Routes)
		if err != nil {
			return err
		}
		return nil
	}); err != nil {
		return err
	}

	//
	return nil
}

func validateCniContainerInterface(intf current.Interface) error {

	var link netlink.Link
	var err error

	if intf.Name == "" {
		return fmt.Errorf("Container interface name missing in prevResult: %v", intf.Name)
	}
	link, err = netlink.LinkByName(intf.Name)
	if err != nil {
		return fmt.Errorf("Container Interface name in prevResult: %s not found", intf.Name)
	}
	if intf.Sandbox == "" {
		return fmt.Errorf("Error: Container interface %s should not be in host namespace", link.Attrs().Name)
	}

	if intf.Mac != "" {
		if intf.Mac != link.Attrs().HardwareAddr.String() {
			return fmt.Errorf("Interface %s Mac %s doesn't match container Mac: %s", intf.Name, intf.Mac, link.Attrs().HardwareAddr)
		}
	}

	return nil
}
<commit_msg>Make host-device to work with virtio net device<commit_after>// Copyright 2015 CNI 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 main

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/vishvananda/netlink"

	"github.com/containernetworking/cni/pkg/skel"
	"github.com/containernetworking/cni/pkg/types"
	"github.com/containernetworking/cni/pkg/types/current"
	"github.com/containernetworking/cni/pkg/version"

	"github.com/containernetworking/plugins/pkg/ip"
	"github.com/containernetworking/plugins/pkg/ipam"
	"github.com/containernetworking/plugins/pkg/ns"
	bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)

const (
	sysBusPCI = "/sys/bus/pci/devices"
)

//NetConf for host-device config, look the README to learn how to use those parameters
type NetConf struct {
	types.NetConf
	Device     string `json:"device"`     // Device-Name, something like eth0 or can0 etc.
	HWAddr     string `json:"hwaddr"`     // MAC Address of target network interface
	KernelPath string `json:"kernelpath"` // Kernelpath of the device
	PCIAddr    string `json:"pciBusID"`   // PCI Address of target network device
}

func init() {
	// this ensures that main runs only on main thread (thread group leader).
	// since namespace ops (unshare, setns) are done for a single thread, we
	// must ensure that the goroutine does not jump from OS thread to thread
	runtime.LockOSThread()
}

func loadConf(bytes []byte) (*NetConf, error) {
	n := &NetConf{}
	if err := json.Unmarshal(bytes, n); err != nil {
		return nil, fmt.Errorf("failed to load netconf: %v", err)
	}
	if n.Device == "" && n.HWAddr == "" && n.KernelPath == "" && n.PCIAddr == "" {
		return nil, fmt.Errorf(`specify either "device", "hwaddr", "kernelpath" or "pciBusID"`)
	}
	return n, nil
}

func cmdAdd(args *skel.CmdArgs) error {
	cfg, err := loadConf(args.StdinData)
	if err != nil {
		return err
	}
	containerNs, err := ns.GetNS(args.Netns)
	if err != nil {
		return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
	}
	defer containerNs.Close()

	hostDev, err := getLink(cfg.Device, cfg.HWAddr, cfg.KernelPath, cfg.PCIAddr)
	if err != nil {
		return fmt.Errorf("failed to find host device: %v", err)
	}

	contDev, err := moveLinkIn(hostDev, containerNs, args.IfName)
	if err != nil {
		return fmt.Errorf("failed to move link %v", err)
	}

	var result *current.Result
	// run the IPAM plugin and get back the config to apply
	if cfg.IPAM.Type != "" {
		r, err := ipam.ExecAdd(cfg.IPAM.Type, args.StdinData)
		if err != nil {
			return err
		}

		// Invoke ipam del if err to avoid ip leak
		defer func() {
			if err != nil {
				ipam.ExecDel(cfg.IPAM.Type, args.StdinData)
			}
		}()

		// Convert whatever the IPAM result was into the current Result type
		result, err = current.NewResultFromResult(r)
		if err != nil {
			return err
		}

		if len(result.IPs) == 0 {
			return errors.New("IPAM plugin returned missing IP config")
		}

		result.Interfaces = []*current.Interface{{
			Name:    contDev.Attrs().Name,
			Mac:     contDev.Attrs().HardwareAddr.String(),
			Sandbox: containerNs.Path(),
		}}
		for _, ipc := range result.IPs {
			// All addresses apply to the container interface (move from host)
			ipc.Interface = current.Int(0)
		}

		err = containerNs.Do(func(_ ns.NetNS) error {
			if err := ipam.ConfigureIface(args.IfName, result); err != nil {
				return err
			}
			return nil
		})
		if err != nil {
			return err
		}

		result.DNS = cfg.DNS

		return types.PrintResult(result, cfg.CNIVersion)
	}

	return printLink(contDev, cfg.CNIVersion, containerNs)
}

func cmdDel(args *skel.CmdArgs) error {
	cfg, err := loadConf(args.StdinData)
	if err != nil {
		return err
	}
	if args.Netns == "" {
		return nil
	}
	containerNs, err := ns.GetNS(args.Netns)
	if err != nil {
		return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
	}
	defer containerNs.Close()

	if err := moveLinkOut(containerNs, args.IfName); err != nil {
		return err
	}

	if cfg.IPAM.Type != "" {
		if err := ipam.ExecDel(cfg.IPAM.Type, args.StdinData); err != nil {
			return err
		}
	}

	return nil
}

func moveLinkIn(hostDev netlink.Link, containerNs ns.NetNS, ifName string) (netlink.Link, error) {
	if err := netlink.LinkSetNsFd(hostDev, int(containerNs.Fd())); err != nil {
		return nil, err
	}

	var contDev netlink.Link
	if err := containerNs.Do(func(_ ns.NetNS) error {
		var err error
		contDev, err = netlink.LinkByName(hostDev.Attrs().Name)
		if err != nil {
			return fmt.Errorf("failed to find %q: %v", hostDev.Attrs().Name, err)
		}
		// Save host device name into the container device's alias property
		if err := netlink.LinkSetAlias(contDev, hostDev.Attrs().Name); err != nil {
			return fmt.Errorf("failed to set alias to %q: %v", hostDev.Attrs().Name, err)
		}
		// Rename container device to respect args.IfName
		if err := netlink.LinkSetName(contDev, ifName); err != nil {
			return fmt.Errorf("failed to rename device %q to %q: %v", hostDev.Attrs().Name, ifName, err)
		}
		// Retrieve link again to get up-to-date name and attributes
		contDev, err = netlink.LinkByName(ifName)
		if err != nil {
			return fmt.Errorf("failed to find %q: %v", ifName, err)
		}
		return nil
	}); err != nil {
		return nil, err
	}

	return contDev, nil
}

func moveLinkOut(containerNs ns.NetNS, ifName string) error {
	defaultNs, err := ns.GetCurrentNS()
	if err != nil {
		return err
	}
	defer defaultNs.Close()

	return containerNs.Do(func(_ ns.NetNS) error {
		dev, err := netlink.LinkByName(ifName)
		if err != nil {
			return fmt.Errorf("failed to find %q: %v", ifName, err)
		}

		// Devices can be renamed only when down
		if err = netlink.LinkSetDown(dev); err != nil {
			return fmt.Errorf("failed to set %q down: %v", ifName, err)
		}

		// Rename device to it's original name
		if err = netlink.LinkSetName(dev, dev.Attrs().Alias); err != nil {
			return fmt.Errorf("failed to restore %q to original name %q: %v", ifName, dev.Attrs().Alias, err)
		}
		defer func() {
			if err != nil {
				// if moving device to host namespace fails, we should revert device name
				// to ifName to make sure that device can be found in retries
				_ = netlink.LinkSetName(dev, ifName)
			}
		}()

		if err = netlink.LinkSetNsFd(dev, int(defaultNs.Fd())); err != nil {
			return fmt.Errorf("failed to move %q to host netns: %v", dev.Attrs().Alias, err)
		}
		return nil
	})
}

func printLink(dev netlink.Link, cniVersion string, containerNs ns.NetNS) error {
	result := current.Result{
		CNIVersion: current.ImplementedSpecVersion,
		Interfaces: []*current.Interface{
			{
				Name:    dev.Attrs().Name,
				Mac:     dev.Attrs().HardwareAddr.String(),
				Sandbox: containerNs.Path(),
			},
		},
	}
	return types.PrintResult(&result, cniVersion)
}

func getLink(devname, hwaddr, kernelpath, pciaddr string) (netlink.Link, error) {
	links, err := netlink.LinkList()
	if err != nil {
		return nil, fmt.Errorf("failed to list node links: %v", err)
	}

	if len(devname) > 0 {
		return netlink.LinkByName(devname)
	} else if len(hwaddr) > 0 {
		hwAddr, err := net.ParseMAC(hwaddr)
		if err != nil {
			return nil, fmt.Errorf("failed to parse MAC address %q: %v", hwaddr, err)
		}

		for _, link := range links {
			if bytes.Equal(link.Attrs().HardwareAddr, hwAddr) {
				return link, nil
			}
		}
	} else if len(kernelpath) > 0 {
		if !filepath.IsAbs(kernelpath) || !strings.HasPrefix(kernelpath, "/sys/devices/") {
			return nil, fmt.Errorf("kernel device path %q must be absolute and begin with /sys/devices/", kernelpath)
		}
		netDir := filepath.Join(kernelpath, "net")
		files, err := ioutil.ReadDir(netDir)
		if err != nil {
			return nil, fmt.Errorf("failed to find network devices at %q", netDir)
		}

		// Grab the first device from eg /sys/devices/pci0000:00/0000:00:19.0/net
		for _, file := range files {
			// Make sure it's really an interface
			for _, l := range links {
				if file.Name() == l.Attrs().Name {
					return l, nil
				}
			}
		}
	} else if len(pciaddr) > 0 {
		netDir := filepath.Join(sysBusPCI, pciaddr, "net")
		if _, err := os.Lstat(netDir); err != nil {
			virtioNetDir := filepath.Join(sysBusPCI, pciaddr, "virtio*", "net")
			matches, err := filepath.Glob(virtioNetDir)
			if matches == nil || err != nil {
				return nil, fmt.Errorf("no net directory under pci device %s", pciaddr)
			}
			netDir = matches[0]
		}
		fInfo, err := ioutil.ReadDir(netDir)
		if err != nil {
			return nil, fmt.Errorf("failed to read net directory %s: %q", netDir, err)
		}
		if len(fInfo) > 0 {
			return netlink.LinkByName(fInfo[0].Name())
		}
		return nil, fmt.Errorf("failed to find device name for pci address %s", pciaddr)
	}

	return nil, fmt.Errorf("failed to find physical interface")
}

func main() {
	skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("host-device"))
}

func cmdCheck(args *skel.CmdArgs) error {

	cfg, err := loadConf(args.StdinData)
	if err != nil {
		return err
	}
	netns, err := ns.GetNS(args.Netns)
	if err != nil {
		return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
	}
	defer netns.Close()

	// run the IPAM plugin and get back the config to apply
	if cfg.IPAM.Type != "" {
		err = ipam.ExecCheck(cfg.IPAM.Type, args.StdinData)
		if err != nil {
			return err
		}
	}

	// Parse previous result.
	if cfg.NetConf.RawPrevResult == nil {
		return fmt.Errorf("Required prevResult missing")
	}

	if err := version.ParsePrevResult(&cfg.NetConf); err != nil {
		return err
	}

	result, err := current.NewResultFromResult(cfg.PrevResult)
	if err != nil {
		return err
	}

	var contMap current.Interface
	// Find interfaces for name we know, that of host-device inside container
	for _, intf := range result.Interfaces {
		if args.IfName == intf.Name {
			if args.Netns == intf.Sandbox {
				contMap = *intf
				continue
			}
		}
	}

	// The namespace must be the same as what was configured
	if args.Netns != contMap.Sandbox {
		return fmt.Errorf("Sandbox in prevResult %s doesn't match configured netns: %s",
			contMap.Sandbox, args.Netns)
	}

	//
	// Check prevResults for ips, routes and dns against values found in the container
	if err := netns.Do(func(_ ns.NetNS) error {

		// Check interface against values found in the container
		err := validateCniContainerInterface(contMap)
		if err != nil {
			return err
		}

		err = ip.ValidateExpectedInterfaceIPs(args.IfName, result.IPs)
		if err != nil {
			return err
		}

		err = ip.ValidateExpectedRoute(result.Routes)
		if err != nil {
			return err
		}
		return nil
	}); err != nil {
		return err
	}

	//
	return nil
}

func validateCniContainerInterface(intf current.Interface) error {

	var link netlink.Link
	var err error

	if intf.Name == "" {
		return fmt.Errorf("Container interface name missing in prevResult: %v", intf.Name)
	}
	link, err = netlink.LinkByName(intf.Name)
	if err != nil {
		return fmt.Errorf("Container Interface name in prevResult: %s not found", intf.Name)
	}
	if intf.Sandbox == "" {
		return fmt.Errorf("Error: Container interface %s should not be in host namespace", link.Attrs().Name)
	}

	if intf.Mac != "" {
		if intf.Mac != link.Attrs().HardwareAddr.String() {
			return fmt.Errorf("Interface %s Mac %s doesn't match container Mac: %s", intf.Name, intf.Mac, link.Attrs().HardwareAddr)
		}
	}

	return nil
}
<|endoftext|>