<commit_before>/*
Copyright 2014 The Kubernetes Authors.

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

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

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

package authenticator

import (
	"time"

	"github.com/go-openapi/spec"

	"k8s.io/apiserver/pkg/authentication/authenticator"
	"k8s.io/apiserver/pkg/authentication/authenticatorfactory"
	"k8s.io/apiserver/pkg/authentication/group"
	"k8s.io/apiserver/pkg/authentication/request/anonymous"
	"k8s.io/apiserver/pkg/authentication/request/bearertoken"
	"k8s.io/apiserver/pkg/authentication/request/headerrequest"
	"k8s.io/apiserver/pkg/authentication/request/union"
	"k8s.io/apiserver/pkg/authentication/request/websocket"
	"k8s.io/apiserver/pkg/authentication/request/x509"
	tokencache "k8s.io/apiserver/pkg/authentication/token/cache"
	"k8s.io/apiserver/pkg/authentication/token/tokenfile"
	tokenunion "k8s.io/apiserver/pkg/authentication/token/union"
	utilfeature "k8s.io/apiserver/pkg/util/feature"
	"k8s.io/apiserver/plugin/pkg/authenticator/password/passwordfile"
	"k8s.io/apiserver/plugin/pkg/authenticator/request/basicauth"
	"k8s.io/apiserver/plugin/pkg/authenticator/token/oidc"
	"k8s.io/apiserver/plugin/pkg/authenticator/token/webhook"
	certutil "k8s.io/client-go/util/cert"
	"k8s.io/kubernetes/pkg/features"
	"k8s.io/kubernetes/pkg/serviceaccount"

	_ "k8s.io/client-go/plugin/pkg/client/auth"
)

type AuthenticatorConfig struct {
	Anonymous                   bool
	BasicAuthFile               string
	BootstrapToken              bool
	ClientCAFile                string
	TokenAuthFile               string
	OIDCIssuerURL               string
	OIDCClientID                string
	OIDCCAFile                  string
	OIDCUsernameClaim           string
	OIDCUsernamePrefix          string
	OIDCGroupsClaim             string
	OIDCGroupsPrefix            string
	OIDCSigningAlgs             []string
	ServiceAccountKeyFiles      []string
	ServiceAccountLookup        bool
	ServiceAccountIssuer        string
	ServiceAccountAPIAudiences  []string
	WebhookTokenAuthnConfigFile string
	WebhookTokenAuthnCacheTTL   time.Duration

	TokenSuccessCacheTTL time.Duration
	TokenFailureCacheTTL time.Duration

	RequestHeaderConfig *authenticatorfactory.RequestHeaderConfig

	// TODO, this is the only non-serializable part of the entire config.  Factor it out into a clientconfig
	ServiceAccountTokenGetter   serviceaccount.ServiceAccountTokenGetter
	BootstrapTokenAuthenticator authenticator.Token
}

// New returns an authenticator.Request or an error that supports the standard
// Kubernetes authentication mechanisms.
func (config AuthenticatorConfig) New() (authenticator.Request, *spec.SecurityDefinitions, error) {
	var authenticators []authenticator.Request
	var tokenAuthenticators []authenticator.Token
	securityDefinitions := spec.SecurityDefinitions{}
	hasBasicAuth := false

	// front-proxy, BasicAuth methods, local first, then remote
	// Add the front proxy authenticator if requested
	if config.RequestHeaderConfig != nil {
		requestHeaderAuthenticator, err := headerrequest.NewSecure(
			config.RequestHeaderConfig.ClientCA,
			config.RequestHeaderConfig.AllowedClientNames,
			config.RequestHeaderConfig.UsernameHeaders,
			config.RequestHeaderConfig.GroupHeaders,
			config.RequestHeaderConfig.ExtraHeaderPrefixes,
		)
		if err != nil {
			return nil, nil, err
		}
		authenticators = append(authenticators, requestHeaderAuthenticator)
	}

	if len(config.BasicAuthFile) > 0 {
		basicAuth, err := newAuthenticatorFromBasicAuthFile(config.BasicAuthFile)
		if err != nil {
			return nil, nil, err
		}
		authenticators = append(authenticators, basicAuth)
		hasBasicAuth = true
	}

	// X509 methods
	if len(config.ClientCAFile) > 0 {
		certAuth, err := newAuthenticatorFromClientCAFile(config.ClientCAFile)
		if err != nil {
			return nil, nil, err
		}
		authenticators = append(authenticators, certAuth)
	}

	// Bearer token methods, local first, then remote
	if len(config.TokenAuthFile) > 0 {
		tokenAuth, err := newAuthenticatorFromTokenFile(config.TokenAuthFile)
		if err != nil {
			return nil, nil, err
		}
		tokenAuthenticators = append(tokenAuthenticators, tokenAuth)
	}
	if len(config.ServiceAccountKeyFiles) > 0 {
		serviceAccountAuth, err := newLegacyServiceAccountAuthenticator(config.ServiceAccountKeyFiles, config.ServiceAccountLookup, config.ServiceAccountTokenGetter)
		if err != nil {
			return nil, nil, err
		}
		tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth)
	}
	if utilfeature.DefaultFeatureGate.Enabled(features.TokenRequest) && config.ServiceAccountIssuer != "" {
		serviceAccountAuth, err := newServiceAccountAuthenticator(config.ServiceAccountIssuer, config.ServiceAccountAPIAudiences, config.ServiceAccountKeyFiles, config.ServiceAccountTokenGetter)
		if err != nil {
			return nil, nil, err
		}
		tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth)
	}
	if config.BootstrapToken {
		if config.BootstrapTokenAuthenticator != nil {
			// TODO: This can sometimes be nil because of
			tokenAuthenticators = append(tokenAuthenticators, config.BootstrapTokenAuthenticator)
		}
	}
	// NOTE(ericchiang): Keep the OpenID Connect after Service Accounts.
	//
	// Because both plugins verify JWTs whichever comes first in the union experiences
	// cache misses for all requests using the other. While the service account plugin
	// simply returns an error, the OpenID Connect plugin may query the provider to
	// update the keys, causing performance hits.
	if len(config.OIDCIssuerURL) > 0 && len(config.OIDCClientID) > 0 {
		oidcAuth, err := newAuthenticatorFromOIDCIssuerURL(config.OIDCIssuerURL, config.OIDCClientID, config.OIDCCAFile, config.OIDCUsernameClaim, config.OIDCUsernamePrefix, config.OIDCGroupsClaim, config.OIDCGroupsPrefix, config.OIDCSigningAlgs)
		if err != nil {
			return nil, nil, err
		}
		tokenAuthenticators = append(tokenAuthenticators, oidcAuth)
	}
	if len(config.WebhookTokenAuthnConfigFile) > 0 {
		webhookTokenAuth, err := newWebhookTokenAuthenticator(config.WebhookTokenAuthnConfigFile, config.WebhookTokenAuthnCacheTTL)
		if err != nil {
			return nil, nil, err
		}
		tokenAuthenticators = append(tokenAuthenticators, webhookTokenAuth)
	}

	if hasBasicAuth {
		securityDefinitions["HTTPBasic"] = &spec.SecurityScheme{
			SecuritySchemeProps: spec.SecuritySchemeProps{
				Type:        "basic",
				Description: "HTTP Basic authentication",
			},
		}
	}

	if len(tokenAuthenticators) > 0 {
		// Union the token authenticators
		tokenAuth := tokenunion.New(tokenAuthenticators...)
		// Optionally cache authentication results
		if config.TokenSuccessCacheTTL > 0 || config.TokenFailureCacheTTL > 0 {
			tokenAuth = tokencache.New(tokenAuth, config.TokenSuccessCacheTTL, config.TokenFailureCacheTTL)
		}
		authenticators = append(authenticators, bearertoken.New(tokenAuth), websocket.NewProtocolAuthenticator(tokenAuth))
		securityDefinitions["BearerToken"] = &spec.SecurityScheme{
			SecuritySchemeProps: spec.SecuritySchemeProps{
				Type:        "apiKey",
				Name:        "authorization",
				In:          "header",
				Description: "Bearer Token authentication",
			},
		}
	}

	if len(authenticators) == 0 {
		if config.Anonymous {
			return anonymous.NewAuthenticator(), &securityDefinitions, nil
		}
	}

	switch len(authenticators) {
	case 0:
		return nil, &securityDefinitions, nil
	}

	authenticator := union.New(authenticators...)

	authenticator = group.NewAuthenticatedGroupAdder(authenticator)

	if config.Anonymous {
		// If the authenticator chain returns an error, return an error (don't consider a bad bearer token
		// or invalid username/password combination anonymous).
		authenticator = union.NewFailOnError(authenticator, anonymous.NewAuthenticator())
	}

	return authenticator, &securityDefinitions, nil
}

// IsValidServiceAccountKeyFile returns true if a valid public RSA key can be read from the given file
func IsValidServiceAccountKeyFile(file string) bool {
	_, err := certutil.PublicKeysFromFile(file)
	return err == nil
}

// newAuthenticatorFromBasicAuthFile returns an authenticator.Request or an error
func newAuthenticatorFromBasicAuthFile(basicAuthFile string) (authenticator.Request, error) {
	basicAuthenticator, err := passwordfile.NewCSV(basicAuthFile)
	if err != nil {
		return nil, err
	}

	return basicauth.New(basicAuthenticator), nil
}

// newAuthenticatorFromTokenFile returns an authenticator.Token or an error
func newAuthenticatorFromTokenFile(tokenAuthFile string) (authenticator.Token, error) {
	tokenAuthenticator, err := tokenfile.NewCSV(tokenAuthFile)
	if err != nil {
		return nil, err
	}

	return tokenAuthenticator, nil
}

// newAuthenticatorFromOIDCIssuerURL returns an authenticator.Token or an error.
func newAuthenticatorFromOIDCIssuerURL(issuerURL, clientID, caFile, usernameClaim, usernamePrefix, groupsClaim, groupsPrefix string, signingAlgs []string) (authenticator.Token, error) {
	const noUsernamePrefix = "-"

	if usernamePrefix == "" && usernameClaim != "email" {
		// Old behavior. If a usernamePrefix isn't provided, prefix all claims other than "email"
		// with the issuerURL.
		//
		// See https://github.com/kubernetes/kubernetes/issues/31380
		usernamePrefix = issuerURL + "#"
	}

	if usernamePrefix == noUsernamePrefix {
		// Special value indicating usernames shouldn't be prefixed.
		usernamePrefix = ""
	}

	tokenAuthenticator, err := oidc.New(oidc.Options{
		IssuerURL:            issuerURL,
		ClientID:             clientID,
		CAFile:               caFile,
		UsernameClaim:        usernameClaim,
		UsernamePrefix:       usernamePrefix,
		GroupsClaim:          groupsClaim,
		GroupsPrefix:         groupsPrefix,
		SupportedSigningAlgs: signingAlgs,
	})
	if err != nil {
		return nil, err
	}

	return tokenAuthenticator, nil
}

// newLegacyServiceAccountAuthenticator returns an authenticator.Token or an error
func newLegacyServiceAccountAuthenticator(keyfiles []string, lookup bool, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter) (authenticator.Token, error) {
	allPublicKeys := []interface{}{}
	for _, keyfile := range keyfiles {
		publicKeys, err := certutil.PublicKeysFromFile(keyfile)
		if err != nil {
			return nil, err
		}
		allPublicKeys = append(allPublicKeys, publicKeys...)
	}

	tokenAuthenticator := serviceaccount.JWTTokenAuthenticator(serviceaccount.LegacyIssuer, allPublicKeys, serviceaccount.NewLegacyValidator(lookup, serviceAccountGetter))
	return tokenAuthenticator, nil
}

// newServiceAccountAuthenticator returns an authenticator.Token or an error
func newServiceAccountAuthenticator(iss string, audiences []string, keyfiles []string, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter) (authenticator.Token, error) {
	allPublicKeys := []interface{}{}
	for _, keyfile := range keyfiles {
		publicKeys, err := certutil.PublicKeysFromFile(keyfile)
		if err != nil {
			return nil, err
		}
		allPublicKeys = append(allPublicKeys, publicKeys...)
	}

	tokenAuthenticator := serviceaccount.JWTTokenAuthenticator(iss, allPublicKeys, serviceaccount.NewValidator(audiences, serviceAccountGetter))
	return tokenAuthenticator, nil
}

// newAuthenticatorFromClientCAFile returns an authenticator.Request or an error
func newAuthenticatorFromClientCAFile(clientCAFile string) (authenticator.Request, error) {
	roots, err := certutil.NewPool(clientCAFile)
	if err != nil {
		return nil, err
	}

	opts := x509.DefaultVerifyOptions()
	opts.Roots = roots

	return x509.New(opts, x509.CommonNameUserConversion), nil
}

func newWebhookTokenAuthenticator(webhookConfigFile string, ttl time.Duration) (authenticator.Token, error) {
	webhookTokenAuthenticator, err := webhook.New(webhookConfigFile, ttl)
	if err != nil {
		return nil, err
	}

	return webhookTokenAuthenticator, nil
}
<commit_msg>Simplify authenticator configuration initialization<commit_after>/*
Copyright 2014 The Kubernetes Authors.

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

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

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

package authenticator

import (
	"time"

	"github.com/go-openapi/spec"

	"k8s.io/apiserver/pkg/authentication/authenticator"
	"k8s.io/apiserver/pkg/authentication/authenticatorfactory"
	"k8s.io/apiserver/pkg/authentication/group"
	"k8s.io/apiserver/pkg/authentication/request/anonymous"
	"k8s.io/apiserver/pkg/authentication/request/bearertoken"
	"k8s.io/apiserver/pkg/authentication/request/headerrequest"
	"k8s.io/apiserver/pkg/authentication/request/union"
	"k8s.io/apiserver/pkg/authentication/request/websocket"
	"k8s.io/apiserver/pkg/authentication/request/x509"
	tokencache "k8s.io/apiserver/pkg/authentication/token/cache"
	"k8s.io/apiserver/pkg/authentication/token/tokenfile"
	tokenunion "k8s.io/apiserver/pkg/authentication/token/union"
	utilfeature "k8s.io/apiserver/pkg/util/feature"
	"k8s.io/apiserver/plugin/pkg/authenticator/password/passwordfile"
	"k8s.io/apiserver/plugin/pkg/authenticator/request/basicauth"
	"k8s.io/apiserver/plugin/pkg/authenticator/token/oidc"
	"k8s.io/apiserver/plugin/pkg/authenticator/token/webhook"
	certutil "k8s.io/client-go/util/cert"
	"k8s.io/kubernetes/pkg/features"
	"k8s.io/kubernetes/pkg/serviceaccount"

	_ "k8s.io/client-go/plugin/pkg/client/auth"
)

type AuthenticatorConfig struct {
	Anonymous                   bool
	BasicAuthFile               string
	BootstrapToken              bool
	ClientCAFile                string
	TokenAuthFile               string
	OIDCIssuerURL               string
	OIDCClientID                string
	OIDCCAFile                  string
	OIDCUsernameClaim           string
	OIDCUsernamePrefix          string
	OIDCGroupsClaim             string
	OIDCGroupsPrefix            string
	OIDCSigningAlgs             []string
	ServiceAccountKeyFiles      []string
	ServiceAccountLookup        bool
	ServiceAccountIssuer        string
	ServiceAccountAPIAudiences  []string
	WebhookTokenAuthnConfigFile string
	WebhookTokenAuthnCacheTTL   time.Duration

	TokenSuccessCacheTTL time.Duration
	TokenFailureCacheTTL time.Duration

	RequestHeaderConfig *authenticatorfactory.RequestHeaderConfig

	// TODO, this is the only non-serializable part of the entire config.  Factor it out into a clientconfig
	ServiceAccountTokenGetter   serviceaccount.ServiceAccountTokenGetter
	BootstrapTokenAuthenticator authenticator.Token
}

// New returns an authenticator.Request or an error that supports the standard
// Kubernetes authentication mechanisms.
func (config AuthenticatorConfig) New() (authenticator.Request, *spec.SecurityDefinitions, error) {
	var authenticators []authenticator.Request
	var tokenAuthenticators []authenticator.Token
	securityDefinitions := spec.SecurityDefinitions{}

	// front-proxy, BasicAuth methods, local first, then remote
	// Add the front proxy authenticator if requested
	if config.RequestHeaderConfig != nil {
		requestHeaderAuthenticator, err := headerrequest.NewSecure(
			config.RequestHeaderConfig.ClientCA,
			config.RequestHeaderConfig.AllowedClientNames,
			config.RequestHeaderConfig.UsernameHeaders,
			config.RequestHeaderConfig.GroupHeaders,
			config.RequestHeaderConfig.ExtraHeaderPrefixes,
		)
		if err != nil {
			return nil, nil, err
		}
		authenticators = append(authenticators, requestHeaderAuthenticator)
	}

	// basic auth
	if len(config.BasicAuthFile) > 0 {
		basicAuth, err := newAuthenticatorFromBasicAuthFile(config.BasicAuthFile)
		if err != nil {
			return nil, nil, err
		}
		authenticators = append(authenticators, basicAuth)

		securityDefinitions["HTTPBasic"] = &spec.SecurityScheme{
			SecuritySchemeProps: spec.SecuritySchemeProps{
				Type:        "basic",
				Description: "HTTP Basic authentication",
			},
		}
	}

	// X509 methods
	if len(config.ClientCAFile) > 0 {
		certAuth, err := newAuthenticatorFromClientCAFile(config.ClientCAFile)
		if err != nil {
			return nil, nil, err
		}
		authenticators = append(authenticators, certAuth)
	}

	// Bearer token methods, local first, then remote
	if len(config.TokenAuthFile) > 0 {
		tokenAuth, err := newAuthenticatorFromTokenFile(config.TokenAuthFile)
		if err != nil {
			return nil, nil, err
		}
		tokenAuthenticators = append(tokenAuthenticators, tokenAuth)
	}
	if len(config.ServiceAccountKeyFiles) > 0 {
		serviceAccountAuth, err := newLegacyServiceAccountAuthenticator(config.ServiceAccountKeyFiles, config.ServiceAccountLookup, config.ServiceAccountTokenGetter)
		if err != nil {
			return nil, nil, err
		}
		tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth)
	}
	if utilfeature.DefaultFeatureGate.Enabled(features.TokenRequest) && config.ServiceAccountIssuer != "" {
		serviceAccountAuth, err := newServiceAccountAuthenticator(config.ServiceAccountIssuer, config.ServiceAccountAPIAudiences, config.ServiceAccountKeyFiles, config.ServiceAccountTokenGetter)
		if err != nil {
			return nil, nil, err
		}
		tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth)
	}
	if config.BootstrapToken {
		if config.BootstrapTokenAuthenticator != nil {
			// TODO: This can sometimes be nil because of
			tokenAuthenticators = append(tokenAuthenticators, config.BootstrapTokenAuthenticator)
		}
	}
	// NOTE(ericchiang): Keep the OpenID Connect after Service Accounts.
	//
	// Because both plugins verify JWTs whichever comes first in the union experiences
	// cache misses for all requests using the other. While the service account plugin
	// simply returns an error, the OpenID Connect plugin may query the provider to
	// update the keys, causing performance hits.
	if len(config.OIDCIssuerURL) > 0 && len(config.OIDCClientID) > 0 {
		oidcAuth, err := newAuthenticatorFromOIDCIssuerURL(config.OIDCIssuerURL, config.OIDCClientID, config.OIDCCAFile, config.OIDCUsernameClaim, config.OIDCUsernamePrefix, config.OIDCGroupsClaim, config.OIDCGroupsPrefix, config.OIDCSigningAlgs)
		if err != nil {
			return nil, nil, err
		}
		tokenAuthenticators = append(tokenAuthenticators, oidcAuth)
	}
	if len(config.WebhookTokenAuthnConfigFile) > 0 {
		webhookTokenAuth, err := newWebhookTokenAuthenticator(config.WebhookTokenAuthnConfigFile, config.WebhookTokenAuthnCacheTTL)
		if err != nil {
			return nil, nil, err
		}
		tokenAuthenticators = append(tokenAuthenticators, webhookTokenAuth)
	}

	if len(tokenAuthenticators) > 0 {
		// Union the token authenticators
		tokenAuth := tokenunion.New(tokenAuthenticators...)
		// Optionally cache authentication results
		if config.TokenSuccessCacheTTL > 0 || config.TokenFailureCacheTTL > 0 {
			tokenAuth = tokencache.New(tokenAuth, config.TokenSuccessCacheTTL, config.TokenFailureCacheTTL)
		}
		authenticators = append(authenticators, bearertoken.New(tokenAuth), websocket.NewProtocolAuthenticator(tokenAuth))
		securityDefinitions["BearerToken"] = &spec.SecurityScheme{
			SecuritySchemeProps: spec.SecuritySchemeProps{
				Type:        "apiKey",
				Name:        "authorization",
				In:          "header",
				Description: "Bearer Token authentication",
			},
		}
	}

	if len(authenticators) == 0 {
		if config.Anonymous {
			return anonymous.NewAuthenticator(), &securityDefinitions, nil
		}
	}

	if len(authenticators) == 0 {
		return nil, &securityDefinitions, nil
	}

	authenticator := union.New(authenticators...)

	authenticator = group.NewAuthenticatedGroupAdder(authenticator)

	if config.Anonymous {
		// If the authenticator chain returns an error, return an error (don't consider a bad bearer token
		// or invalid username/password combination anonymous).
		authenticator = union.NewFailOnError(authenticator, anonymous.NewAuthenticator())
	}

	return authenticator, &securityDefinitions, nil
}

// IsValidServiceAccountKeyFile returns true if a valid public RSA key can be read from the given file
func IsValidServiceAccountKeyFile(file string) bool {
	_, err := certutil.PublicKeysFromFile(file)
	return err == nil
}

// newAuthenticatorFromBasicAuthFile returns an authenticator.Request or an error
func newAuthenticatorFromBasicAuthFile(basicAuthFile string) (authenticator.Request, error) {
	basicAuthenticator, err := passwordfile.NewCSV(basicAuthFile)
	if err != nil {
		return nil, err
	}

	return basicauth.New(basicAuthenticator), nil
}

// newAuthenticatorFromTokenFile returns an authenticator.Token or an error
func newAuthenticatorFromTokenFile(tokenAuthFile string) (authenticator.Token, error) {
	tokenAuthenticator, err := tokenfile.NewCSV(tokenAuthFile)
	if err != nil {
		return nil, err
	}

	return tokenAuthenticator, nil
}

// newAuthenticatorFromOIDCIssuerURL returns an authenticator.Token or an error.
func newAuthenticatorFromOIDCIssuerURL(issuerURL, clientID, caFile, usernameClaim, usernamePrefix, groupsClaim, groupsPrefix string, signingAlgs []string) (authenticator.Token, error) {
	const noUsernamePrefix = "-"

	if usernamePrefix == "" && usernameClaim != "email" {
		// Old behavior. If a usernamePrefix isn't provided, prefix all claims other than "email"
		// with the issuerURL.
		//
		// See https://github.com/kubernetes/kubernetes/issues/31380
		usernamePrefix = issuerURL + "#"
	}

	if usernamePrefix == noUsernamePrefix {
		// Special value indicating usernames shouldn't be prefixed.
		usernamePrefix = ""
	}

	tokenAuthenticator, err := oidc.New(oidc.Options{
		IssuerURL:            issuerURL,
		ClientID:             clientID,
		CAFile:               caFile,
		UsernameClaim:        usernameClaim,
		UsernamePrefix:       usernamePrefix,
		GroupsClaim:          groupsClaim,
		GroupsPrefix:         groupsPrefix,
		SupportedSigningAlgs: signingAlgs,
	})
	if err != nil {
		return nil, err
	}

	return tokenAuthenticator, nil
}

// newLegacyServiceAccountAuthenticator returns an authenticator.Token or an error
func newLegacyServiceAccountAuthenticator(keyfiles []string, lookup bool, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter) (authenticator.Token, error) {
	allPublicKeys := []interface{}{}
	for _, keyfile := range keyfiles {
		publicKeys, err := certutil.PublicKeysFromFile(keyfile)
		if err != nil {
			return nil, err
		}
		allPublicKeys = append(allPublicKeys, publicKeys...)
	}

	tokenAuthenticator := serviceaccount.JWTTokenAuthenticator(serviceaccount.LegacyIssuer, allPublicKeys, serviceaccount.NewLegacyValidator(lookup, serviceAccountGetter))
	return tokenAuthenticator, nil
}

// newServiceAccountAuthenticator returns an authenticator.Token or an error
func newServiceAccountAuthenticator(iss string, audiences []string, keyfiles []string, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter) (authenticator.Token, error) {
	allPublicKeys := []interface{}{}
	for _, keyfile := range keyfiles {
		publicKeys, err := certutil.PublicKeysFromFile(keyfile)
		if err != nil {
			return nil, err
		}
		allPublicKeys = append(allPublicKeys, publicKeys...)
	}

	tokenAuthenticator := serviceaccount.JWTTokenAuthenticator(iss, allPublicKeys, serviceaccount.NewValidator(audiences, serviceAccountGetter))
	return tokenAuthenticator, nil
}

// newAuthenticatorFromClientCAFile returns an authenticator.Request or an error
func newAuthenticatorFromClientCAFile(clientCAFile string) (authenticator.Request, error) {
	roots, err := certutil.NewPool(clientCAFile)
	if err != nil {
		return nil, err
	}

	opts := x509.DefaultVerifyOptions()
	opts.Roots = roots

	return x509.New(opts, x509.CommonNameUserConversion), nil
}

func newWebhookTokenAuthenticator(webhookConfigFile string, ttl time.Duration) (authenticator.Token, error) {
	webhookTokenAuthenticator, err := webhook.New(webhookConfigFile, ttl)
	if err != nil {
		return nil, err
	}

	return webhookTokenAuthenticator, nil
}
<|endoftext|>


<commit_before>// +build !windows

/*
Copyright 2018 The Skaffold 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 tag

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"testing"
	"time"

	"github.com/GoogleContainerTools/skaffold/testutil"
	git "gopkg.in/src-d/go-git.v4"
	"gopkg.in/src-d/go-git.v4/plumbing"
	"gopkg.in/src-d/go-git.v4/plumbing/object"
)

// These tests do not run on windows
// See: https://github.com/src-d/go-git/issues/378
func TestGitCommit_GenerateFullyQualifiedImageName(t *testing.T) {
	tests := []struct {
		description   string
		expectedName  string
		createGitRepo func(string)
		subDir        string
		shouldErr     bool
	}{
		{
			description:  "success",
			expectedName: "test:eefe1b9",
			createGitRepo: func(dir string) {
				gitInit(t, dir).
					write("source.go", []byte("code")).
					add("source.go").
					commit("initial")
			},
		},
		{
			description:  "use tag over commit",
			expectedName: "test:v2",
			createGitRepo: func(dir string) {
				gitInit(t, dir).
					write("source.go", []byte("code")).
					add("source.go").
					commit("initial").
					tag("v1").
					write("other.go", []byte("other")).
					add("other.go").
					commit("second commit").
					tag("v2")
			},
		},
		{
			description:  "dirty",
			expectedName: "test:eefe1b9-dirty-af8de1fde8be4367",
			createGitRepo: func(dir string) {
				gitInit(t, dir).
					write("source.go", []byte("code")).
					add("source.go").
					commit("initial").
					write("source.go", []byte("updated code"))
			},
		},
		{
			description:  "ignore tag when dirty",
			expectedName: "test:eefe1b9-dirty-af8de1fde8be4367",
			createGitRepo: func(dir string) {
				gitInit(t, dir).
					write("source.go", []byte("code")).
					add("source.go").
					commit("initial").
					tag("v1").
					write("source.go", []byte("updated code"))
			},
		},
		{
			description:  "untracked",
			expectedName: "test:eefe1b9-dirty-bfe9b4566c9d3fec",
			createGitRepo: func(dir string) {
				gitInit(t, dir).
					write("source.go", []byte("code")).
					add("source.go").
					commit("initial").
					write("new.go", []byte("new code"))
			},
		},
		{
			description:  "one file deleted",
			expectedName: "test:279d53f-dirty-6a3ce511c689eda7",
			createGitRepo: func(dir string) {
				gitInit(t, dir).
					write("source1.go", []byte("code1")).
					write("source2.go", []byte("code2")).
					add("source1.go", "source2.go").
					commit("initial").
					delete("source1.go")
			},
		},
		{
			description:  "two files deleted",
			expectedName: "test:279d53f-dirty-d48c11ed65c37a09", // Must be <> than when only one file is deleted
			createGitRepo: func(dir string) {
				gitInit(t, dir).
					write("source1.go", []byte("code1")).
					write("source2.go", []byte("code2")).
					add("source1.go", "source2.go").
					commit("initial").
					delete("source1.go", "source2.go")
			},
		},
		{
			description:  "rename",
			expectedName: "test:eefe1b9-dirty-9c858d88cc0bf792",
			createGitRepo: func(dir string) {
				gitInit(t, dir).
					write("source.go", []byte("code")).
					add("source.go").
					commit("initial").
					rename("source.go", "source2.go")
			},
		},
		{
			description:  "rename to different name",
			expectedName: "test:eefe1b9-dirty-6534adc17ccd1cf4", // Must be <> each time a new name is used
			createGitRepo: func(dir string) {
				gitInit(t, dir).
					write("source.go", []byte("code")).
					add("source.go").
					commit("initial").
					rename("source.go", "source3.go")
			},
		},
		{
			description:  "sub directory",
			expectedName: "test:a7b32a6",
			createGitRepo: func(dir string) {
				gitInit(t, dir).
					mkdir("sub/sub").
					commit("initial")
			},
			subDir: "sub/sub",
		},
		{
			description:  "sub directory with dirty status",
			expectedName: "test:a7b32a6-dirty-83715cdc64e43ee9",
			createGitRepo: func(dir string) {
				gitInit(t, dir).
					mkdir("sub/sub").
					commit("initial").
					write("source.go", []byte("updated code"))
			},
			subDir: "sub/sub",
		},
		{
			description:   "failure",
			createGitRepo: func(dir string) {},
			shouldErr:     true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.description, func(t *testing.T) {
			tmpDir, cleanup := testutil.TempDir(t)
			defer cleanup()

			tt.createGitRepo(tmpDir)
			workspace := filepath.Join(tmpDir, tt.subDir)

			opts := &Options{
				ImageName: "test",
			}

			c := &GitCommit{}
			name, err := c.GenerateFullyQualifiedImageName(workspace, opts)
			testutil.CheckErrorAndDeepEqual(t, tt.shouldErr, err, tt.expectedName, name)

			name, err = generateNameGoGit(workspace, opts)
			testutil.CheckErrorAndDeepEqual(t, tt.shouldErr, err, tt.expectedName, name)

			name, err = generateNameGitShellOut(workspace, opts)
			testutil.CheckErrorAndDeepEqual(t, tt.shouldErr, err, tt.expectedName, name)
		})
	}
}

// gitRepo deals with test git repositories
type gitRepo struct {
	dir      string
	repo     *git.Repository
	workTree *git.Worktree
	t        *testing.T
}

func gitInit(t *testing.T, dir string) *gitRepo {
	repo, err := git.PlainInit(dir, false)
	failNowIfError(t, err)

	w, err := repo.Worktree()
	failNowIfError(t, err)

	return &gitRepo{
		dir:      dir,
		repo:     repo,
		workTree: w,
		t:        t,
	}
}

func (g *gitRepo) mkdir(folder string) *gitRepo {
	err := os.MkdirAll(filepath.Join(g.dir, folder), os.ModePerm)
	failNowIfError(g.t, err)
	return g
}

func (g *gitRepo) write(file string, content []byte) *gitRepo {
	err := ioutil.WriteFile(filepath.Join(g.dir, file), content, os.ModePerm)
	failNowIfError(g.t, err)
	return g
}

func (g *gitRepo) rename(file, to string) *gitRepo {
	err := os.Rename(filepath.Join(g.dir, file), filepath.Join(g.dir, to))
	failNowIfError(g.t, err)
	return g
}

func (g *gitRepo) delete(files ...string) *gitRepo {
	for _, file := range files {
		err := os.Remove(filepath.Join(g.dir, file))
		failNowIfError(g.t, err)
	}
	return g
}

func (g *gitRepo) add(files ...string) *gitRepo {
	for _, file := range files {
		_, err := g.workTree.Add(file)
		failNowIfError(g.t, err)
	}
	return g
}

func (g *gitRepo) commit(msg string) *gitRepo {
	now, err := time.Parse("Jan 2, 2006 at 15:04:05 -0700 MST", "Feb 3, 2013 at 19:54:00 -0700 MST")
	failNowIfError(g.t, err)

	_, err = g.workTree.Commit(msg, &git.CommitOptions{
		Author: &object.Signature{
			Name:  "John Doe",
			Email: "john@doe.org",
			When:  now,
		},
	})
	failNowIfError(g.t, err)

	return g
}

func (g *gitRepo) tag(tag string) *gitRepo {
	head, err := g.repo.Head()
	failNowIfError(g.t, err)

	n := plumbing.ReferenceName("refs/tags/" + tag)
	t := plumbing.NewHashReference(n, head.Hash())

	err = g.repo.Storer.SetReference(t)
	failNowIfError(g.t, err)

	return g
}

func failNowIfError(t *testing.T, err error) {
	if err != nil {
		t.Fatal(err)
	}
}
<commit_msg>Add test for two artifacts in same repo<commit_after>// +build !windows

/*
Copyright 2018 The Skaffold 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 tag

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"testing"
	"time"

	"github.com/GoogleContainerTools/skaffold/testutil"
	git "gopkg.in/src-d/go-git.v4"
	"gopkg.in/src-d/go-git.v4/plumbing"
	"gopkg.in/src-d/go-git.v4/plumbing/object"
)

// These tests do not run on windows
// See: https://github.com/src-d/go-git/issues/378
func TestGitCommit_GenerateFullyQualifiedImageName(t *testing.T) {
	tests := []struct {
		description   string
		expectedName  string
		createGitRepo func(string)
		subDir        string
		shouldErr     bool
	}{
		{
			description:  "success",
			expectedName: "test:eefe1b9",
			createGitRepo: func(dir string) {
				gitInit(t, dir).
					write("source.go", []byte("code")).
					add("source.go").
					commit("initial")
			},
		},
		{
			description:  "use tag over commit",
			expectedName: "test:v2",
			createGitRepo: func(dir string) {
				gitInit(t, dir).
					write("source.go", []byte("code")).
					add("source.go").
					commit("initial").
					tag("v1").
					write("other.go", []byte("other")).
					add("other.go").
					commit("second commit").
					tag("v2")
			},
		},
		{
			description:  "dirty",
			expectedName: "test:eefe1b9-dirty-af8de1fde8be4367",
			createGitRepo: func(dir string) {
				gitInit(t, dir).
					write("source.go", []byte("code")).
					add("source.go").
					commit("initial").
					write("source.go", []byte("updated code"))
			},
		},
		{
			description:  "ignore tag when dirty",
			expectedName: "test:eefe1b9-dirty-af8de1fde8be4367",
			createGitRepo: func(dir string) {
				gitInit(t, dir).
					write("source.go", []byte("code")).
					add("source.go").
					commit("initial").
					tag("v1").
					write("source.go", []byte("updated code"))
			},
		},
		{
			description:  "untracked",
			expectedName: "test:eefe1b9-dirty-bfe9b4566c9d3fec",
			createGitRepo: func(dir string) {
				gitInit(t, dir).
					write("source.go", []byte("code")).
					add("source.go").
					commit("initial").
					write("new.go", []byte("new code"))
			},
		},
		{
			description:  "one file deleted",
			expectedName: "test:279d53f-dirty-6a3ce511c689eda7",
			createGitRepo: func(dir string) {
				gitInit(t, dir).
					write("source1.go", []byte("code1")).
					write("source2.go", []byte("code2")).
					add("source1.go", "source2.go").
					commit("initial").
					delete("source1.go")
			},
		},
		{
			description:  "two files deleted",
			expectedName: "test:279d53f-dirty-d48c11ed65c37a09", // Must be <> than when only one file is deleted
			createGitRepo: func(dir string) {
				gitInit(t, dir).
					write("source1.go", []byte("code1")).
					write("source2.go", []byte("code2")).
					add("source1.go", "source2.go").
					commit("initial").
					delete("source1.go", "source2.go")
			},
		},
		{
			description:  "rename",
			expectedName: "test:eefe1b9-dirty-9c858d88cc0bf792",
			createGitRepo: func(dir string) {
				gitInit(t, dir).
					write("source.go", []byte("code")).
					add("source.go").
					commit("initial").
					rename("source.go", "source2.go")
			},
		},
		{
			description:  "rename to different name",
			expectedName: "test:eefe1b9-dirty-6534adc17ccd1cf4", // Must be <> each time a new name is used
			createGitRepo: func(dir string) {
				gitInit(t, dir).
					write("source.go", []byte("code")).
					add("source.go").
					commit("initial").
					rename("source.go", "source3.go")
			},
		},
		{
			description:  "sub directory",
			expectedName: "test:a7b32a6",
			createGitRepo: func(dir string) {
				gitInit(t, dir).
					mkdir("sub/sub").
					commit("initial")
			},
			subDir: "sub/sub",
		},
		{
			description:  "sub directory with dirty status",
			expectedName: "test:a7b32a6-dirty-83715cdc64e43ee9",
			createGitRepo: func(dir string) {
				gitInit(t, dir).
					mkdir("sub/sub").
					commit("initial").
					write("source.go", []byte("updated code"))
			},
			subDir: "sub/sub",
		},
		{
			description:  "clean artifact1 in tagged repo",
			expectedName: "test:v1",
			createGitRepo: func(dir string) {
				gitInit(t, dir).
					mkdir("artifact1").write("artifact1/source.go", []byte("code")).
					mkdir("artifact2").write("artifact2/source.go", []byte("code")).
					add("artifact1/source.go", "artifact2/source.go").
					commit("initial").tag("v1")
			},
			subDir: "artifact1",
		},
		{
			description:  "clean artifact2 in tagged repo",
			expectedName: "test:v1",
			createGitRepo: func(dir string) {
				gitInit(t, dir).
					mkdir("artifact1").write("artifact1/source.go", []byte("code")).
					mkdir("artifact2").write("artifact2/source.go", []byte("code")).
					add("artifact1/source.go", "artifact2/source.go").
					commit("initial").tag("v1")
			},
			subDir: "artifact2",
		},
		{
			description:  "clean artifact in dirty repo",
			expectedName: "test:0c60cb8-dirty-7dc1463a47f98a7b",
			createGitRepo: func(dir string) {
				gitInit(t, dir).
					mkdir("artifact1").write("artifact1/source.go", []byte("code")).
					mkdir("artifact2").write("artifact2/source.go", []byte("code")).
					add("artifact1/source.go", "artifact2/source.go").
					commit("initial").tag("v1").
					write("artifact2/source.go", []byte("updated code"))
			},
			subDir: "artifact1",
		},
		{
			description:  "updated artifact in dirty repo",
			expectedName: "test:0c60cb8-dirty-7dc1463a47f98a7b",
			createGitRepo: func(dir string) {
				gitInit(t, dir).
					mkdir("artifact1").write("artifact1/source.go", []byte("code")).
					mkdir("artifact2").write("artifact2/source.go", []byte("code")).
					add("artifact1/source.go", "artifact2/source.go").
					commit("initial").tag("v1").
					write("artifact2/source.go", []byte("updated code"))
			},
			subDir: "artifact2",
		},
		{
			description:   "failure",
			createGitRepo: func(dir string) {},
			shouldErr:     true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.description, func(t *testing.T) {
			tmpDir, cleanup := testutil.TempDir(t)
			defer cleanup()

			tt.createGitRepo(tmpDir)
			workspace := filepath.Join(tmpDir, tt.subDir)

			opts := &Options{
				ImageName: "test",
			}

			c := &GitCommit{}
			name, err := c.GenerateFullyQualifiedImageName(workspace, opts)
			testutil.CheckErrorAndDeepEqual(t, tt.shouldErr, err, tt.expectedName, name)

			name, err = generateNameGoGit(workspace, opts)
			testutil.CheckErrorAndDeepEqual(t, tt.shouldErr, err, tt.expectedName, name)

			name, err = generateNameGitShellOut(workspace, opts)
			testutil.CheckErrorAndDeepEqual(t, tt.shouldErr, err, tt.expectedName, name)
		})
	}
}

// gitRepo deals with test git repositories
type gitRepo struct {
	dir      string
	repo     *git.Repository
	workTree *git.Worktree
	t        *testing.T
}

func gitInit(t *testing.T, dir string) *gitRepo {
	repo, err := git.PlainInit(dir, false)
	failNowIfError(t, err)

	w, err := repo.Worktree()
	failNowIfError(t, err)

	return &gitRepo{
		dir:      dir,
		repo:     repo,
		workTree: w,
		t:        t,
	}
}

func (g *gitRepo) mkdir(folder string) *gitRepo {
	err := os.MkdirAll(filepath.Join(g.dir, folder), os.ModePerm)
	failNowIfError(g.t, err)
	return g
}

func (g *gitRepo) write(file string, content []byte) *gitRepo {
	err := ioutil.WriteFile(filepath.Join(g.dir, file), content, os.ModePerm)
	failNowIfError(g.t, err)
	return g
}

func (g *gitRepo) rename(file, to string) *gitRepo {
	err := os.Rename(filepath.Join(g.dir, file), filepath.Join(g.dir, to))
	failNowIfError(g.t, err)
	return g
}

func (g *gitRepo) delete(files ...string) *gitRepo {
	for _, file := range files {
		err := os.Remove(filepath.Join(g.dir, file))
		failNowIfError(g.t, err)
	}
	return g
}

func (g *gitRepo) add(files ...string) *gitRepo {
	for _, file := range files {
		_, err := g.workTree.Add(file)
		failNowIfError(g.t, err)
	}
	return g
}

func (g *gitRepo) commit(msg string) *gitRepo {
	now, err := time.Parse("Jan 2, 2006 at 15:04:05 -0700 MST", "Feb 3, 2013 at 19:54:00 -0700 MST")
	failNowIfError(g.t, err)

	_, err = g.workTree.Commit(msg, &git.CommitOptions{
		Author: &object.Signature{
			Name:  "John Doe",
			Email: "john@doe.org",
			When:  now,
		},
	})
	failNowIfError(g.t, err)

	return g
}

func (g *gitRepo) tag(tag string) *gitRepo {
	head, err := g.repo.Head()
	failNowIfError(g.t, err)

	n := plumbing.ReferenceName("refs/tags/" + tag)
	t := plumbing.NewHashReference(n, head.Hash())

	err = g.repo.Storer.SetReference(t)
	failNowIfError(g.t, err)

	return g
}

func failNowIfError(t *testing.T, err error) {
	if err != nil {
		t.Fatal(err)
	}
}
<|endoftext|>


<commit_before>package chyle

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
)

// githubRelease follows https://developer.github.com/v3/repos/releases/#create-a-release
type githubRelease struct {
	TagName         string `json:"tag_name"`
	TargetCommitish string `json:"target_commitish,omitempty"`
	Name            string `json:"name,omitempty"`
	Body            string `json:"body,omitempty"`
	Draft           bool   `json:"draft,omitempty"`
	PreRelease      bool   `json:"prerelease,omitempty"`
}

// ErrGithubSender handles error regarding github api
// it outputs direct errors coming from request bu as well
// api return payload
type ErrGithubSender struct {
	msg string
	err error
}

func (e ErrGithubSender) Error() string {
	return fmt.Sprintf("%s : %s", e.msg, e.err)
}

// buildGithubReleaseSender create a new GithubReleaseSender object from viper config
func buildGithubReleaseSender() sender {
	return newGithubReleaseSender(&http.Client{})
}

// githubReleaseSender fetch data using jira issue api
type githubReleaseSender struct {
	client *http.Client
}

// newGithubReleaseSender creates a new githubReleaseSender object
func newGithubReleaseSender(client *http.Client) githubReleaseSender {
	return githubReleaseSender{client}
}

// buildBody create a request body from changelog
func (g githubReleaseSender) buildBody(changelog *Changelog) ([]byte, error) {
	body, err := populateTemplate("github-release-template", chyleConfig.SENDERS.GITHUB.RELEASE.TEMPLATE, changelog)

	if err != nil {
		return []byte{}, err
	}

	r := githubRelease{
		chyleConfig.SENDERS.GITHUB.RELEASE.TAGNAME,
		chyleConfig.SENDERS.GITHUB.RELEASE.TARGETCOMMITISH,
		chyleConfig.SENDERS.GITHUB.RELEASE.NAME,
		body,
		chyleConfig.SENDERS.GITHUB.RELEASE.DRAFT,
		chyleConfig.SENDERS.GITHUB.RELEASE.PRERELEASE,
	}

	return json.Marshal(r)
}

// createRelease creates a release on github
func (g githubReleaseSender) createRelease(body []byte) error {
	errMsg := "can't create github release"

	URL := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases", chyleConfig.SENDERS.GITHUB.CREDENTIALS.OWNER, chyleConfig.SENDERS.GITHUB.REPOSITORY.NAME)

	req, err := http.NewRequest("POST", URL, bytes.NewBuffer(body))

	if err != nil {
		return err
	}

	setHeaders(req, map[string]string{
		"Authorization": "token " + chyleConfig.SENDERS.GITHUB.CREDENTIALS.OAUTHTOKEN,
		"Content-Type":  "application/json",
		"Accept":        "application/vnd.github.v3+json",
	})

	status, body, err := sendRequest(g.client, req)

	if err != nil {
		return ErrGithubSender{errMsg, err}
	}

	if status != 201 {
		return ErrGithubSender{errMsg, fmt.Errorf(string(body))}
	}

	return nil
}

// getReleaseID retrieves github release ID from a given tag name
func (g githubReleaseSender) getReleaseID() (int, error) {
	type s struct {
		ID int `json:"id"`
	}

	release := s{}

	errMsg := fmt.Sprintf("can't retrieve github release %s", chyleConfig.SENDERS.GITHUB.RELEASE.TAGNAME)
	URL := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/tags/%s", chyleConfig.SENDERS.GITHUB.CREDENTIALS.OWNER, chyleConfig.SENDERS.GITHUB.REPOSITORY.NAME, chyleConfig.SENDERS.GITHUB.RELEASE.TAGNAME)

	req, err := http.NewRequest("GET", URL, nil)

	if err != nil {
		return 0, err
	}

	setHeaders(req, map[string]string{
		"Authorization": "token " + chyleConfig.SENDERS.GITHUB.CREDENTIALS.OAUTHTOKEN,
		"Content-Type":  "application/json",
		"Accept":        "application/vnd.github.v3+json",
	})

	status, body, err := sendRequest(g.client, req)

	if err != nil {
		return 0, ErrGithubSender{errMsg, err}
	}

	if status != 200 {
		return 0, ErrGithubSender{errMsg, fmt.Errorf(string(body))}
	}

	err = json.Unmarshal(body, &release)

	if err != nil {
		return 0, ErrGithubSender{errMsg, fmt.Errorf("can't decode json body")}
	}

	return release.ID, nil
}

// updateRelease updates an existing release from a tag name
func (g githubReleaseSender) updateRelease(body []byte) error {
	ID, err := g.getReleaseID()

	if err != nil {
		return err
	}

	errMsg := fmt.Sprintf("can't update github release %s", chyleConfig.SENDERS.GITHUB.RELEASE.TAGNAME)
	URL := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/%d", chyleConfig.SENDERS.GITHUB.CREDENTIALS.OWNER, chyleConfig.SENDERS.GITHUB.REPOSITORY.NAME, ID)

	req, err := http.NewRequest("PATCH", URL, bytes.NewBuffer(body))

	if err != nil {
		return err
	}

	setHeaders(req, map[string]string{
		"Authorization": "token " + chyleConfig.SENDERS.GITHUB.CREDENTIALS.OAUTHTOKEN,
		"Content-Type":  "application/json",
		"Accept":        "application/vnd.github.v3+json",
	})

	status, body, err := sendRequest(g.client, req)

	if err != nil {
		return ErrGithubSender{errMsg, err}
	}

	if status != 200 {
		return ErrGithubSender{errMsg, fmt.Errorf(string(body))}
	}

	return nil
}

// Send push changelog to github release
func (g githubReleaseSender) Send(changelog *Changelog) error {
	body, err := g.buildBody(changelog)

	if err != nil {
		return err
	}

	if chyleConfig.SENDERS.GITHUB.RELEASE.UPDATE {
		return g.updateRelease(body)
	}

	return g.createRelease(body)
}
<commit_msg>test(chyle/sender_github_release) : disable codebeat warning<commit_after>package chyle

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
)

// githubRelease follows https://developer.github.com/v3/repos/releases/#create-a-release
// codebeat:disable[TOO_MANY_IVARS]
type githubRelease struct {
	TagName         string `json:"tag_name"`
	TargetCommitish string `json:"target_commitish,omitempty"`
	Name            string `json:"name,omitempty"`
	Body            string `json:"body,omitempty"`
	Draft           bool   `json:"draft,omitempty"`
	PreRelease      bool   `json:"prerelease,omitempty"`
}

// ErrGithubSender handles error regarding github api
// it outputs direct errors coming from request bu as well
// api return payload
type ErrGithubSender struct {
	msg string
	err error
}

func (e ErrGithubSender) Error() string {
	return fmt.Sprintf("%s : %s", e.msg, e.err)
}

// buildGithubReleaseSender create a new GithubReleaseSender object from viper config
func buildGithubReleaseSender() sender {
	return newGithubReleaseSender(&http.Client{})
}

// githubReleaseSender fetch data using jira issue api
type githubReleaseSender struct {
	client *http.Client
}

// newGithubReleaseSender creates a new githubReleaseSender object
func newGithubReleaseSender(client *http.Client) githubReleaseSender {
	return githubReleaseSender{client}
}

// buildBody create a request body from changelog
func (g githubReleaseSender) buildBody(changelog *Changelog) ([]byte, error) {
	body, err := populateTemplate("github-release-template", chyleConfig.SENDERS.GITHUB.RELEASE.TEMPLATE, changelog)

	if err != nil {
		return []byte{}, err
	}

	r := githubRelease{
		chyleConfig.SENDERS.GITHUB.RELEASE.TAGNAME,
		chyleConfig.SENDERS.GITHUB.RELEASE.TARGETCOMMITISH,
		chyleConfig.SENDERS.GITHUB.RELEASE.NAME,
		body,
		chyleConfig.SENDERS.GITHUB.RELEASE.DRAFT,
		chyleConfig.SENDERS.GITHUB.RELEASE.PRERELEASE,
	}

	return json.Marshal(r)
}

// createRelease creates a release on github
func (g githubReleaseSender) createRelease(body []byte) error {
	errMsg := "can't create github release"

	URL := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases", chyleConfig.SENDERS.GITHUB.CREDENTIALS.OWNER, chyleConfig.SENDERS.GITHUB.REPOSITORY.NAME)

	req, err := http.NewRequest("POST", URL, bytes.NewBuffer(body))

	if err != nil {
		return err
	}

	setHeaders(req, map[string]string{
		"Authorization": "token " + chyleConfig.SENDERS.GITHUB.CREDENTIALS.OAUTHTOKEN,
		"Content-Type":  "application/json",
		"Accept":        "application/vnd.github.v3+json",
	})

	status, body, err := sendRequest(g.client, req)

	if err != nil {
		return ErrGithubSender{errMsg, err}
	}

	if status != 201 {
		return ErrGithubSender{errMsg, fmt.Errorf(string(body))}
	}

	return nil
}

// getReleaseID retrieves github release ID from a given tag name
func (g githubReleaseSender) getReleaseID() (int, error) {
	type s struct {
		ID int `json:"id"`
	}

	release := s{}

	errMsg := fmt.Sprintf("can't retrieve github release %s", chyleConfig.SENDERS.GITHUB.RELEASE.TAGNAME)
	URL := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/tags/%s", chyleConfig.SENDERS.GITHUB.CREDENTIALS.OWNER, chyleConfig.SENDERS.GITHUB.REPOSITORY.NAME, chyleConfig.SENDERS.GITHUB.RELEASE.TAGNAME)

	req, err := http.NewRequest("GET", URL, nil)

	if err != nil {
		return 0, err
	}

	setHeaders(req, map[string]string{
		"Authorization": "token " + chyleConfig.SENDERS.GITHUB.CREDENTIALS.OAUTHTOKEN,
		"Content-Type":  "application/json",
		"Accept":        "application/vnd.github.v3+json",
	})

	status, body, err := sendRequest(g.client, req)

	if err != nil {
		return 0, ErrGithubSender{errMsg, err}
	}

	if status != 200 {
		return 0, ErrGithubSender{errMsg, fmt.Errorf(string(body))}
	}

	err = json.Unmarshal(body, &release)

	if err != nil {
		return 0, ErrGithubSender{errMsg, fmt.Errorf("can't decode json body")}
	}

	return release.ID, nil
}

// updateRelease updates an existing release from a tag name
func (g githubReleaseSender) updateRelease(body []byte) error {
	ID, err := g.getReleaseID()

	if err != nil {
		return err
	}

	errMsg := fmt.Sprintf("can't update github release %s", chyleConfig.SENDERS.GITHUB.RELEASE.TAGNAME)
	URL := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/%d", chyleConfig.SENDERS.GITHUB.CREDENTIALS.OWNER, chyleConfig.SENDERS.GITHUB.REPOSITORY.NAME, ID)

	req, err := http.NewRequest("PATCH", URL, bytes.NewBuffer(body))

	if err != nil {
		return err
	}

	setHeaders(req, map[string]string{
		"Authorization": "token " + chyleConfig.SENDERS.GITHUB.CREDENTIALS.OAUTHTOKEN,
		"Content-Type":  "application/json",
		"Accept":        "application/vnd.github.v3+json",
	})

	status, body, err := sendRequest(g.client, req)

	if err != nil {
		return ErrGithubSender{errMsg, err}
	}

	if status != 200 {
		return ErrGithubSender{errMsg, fmt.Errorf(string(body))}
	}

	return nil
}

// Send push changelog to github release
func (g githubReleaseSender) Send(changelog *Changelog) error {
	body, err := g.buildBody(changelog)

	if err != nil {
		return err
	}

	if chyleConfig.SENDERS.GITHUB.RELEASE.UPDATE {
		return g.updateRelease(body)
	}

	return g.createRelease(body)
}
<|endoftext|>


<commit_before>// Copyright 2011 Google Inc. All Rights Reserved.
// This file is available under the Apache license.

package tailer

import (
	"bytes"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"sync"
	"testing"
	"time"

	"github.com/golang/glog"
	"github.com/google/go-cmp/cmp"
	"github.com/google/mtail/watcher"

	"github.com/spf13/afero"
)

func makeTestTail(t *testing.T) (*Tailer, chan *LogLine, *watcher.FakeWatcher, afero.Fs) {
	fs := afero.NewMemMapFs()
	w := watcher.NewFakeWatcher()
	lines := make(chan *LogLine, 1)
	o := Options{lines, false, w, fs}
	ta, err := New(o)
	if err != nil {
		t.Fatal(err)
	}
	return ta, lines, w, fs
}

func makeTestTailReal(t *testing.T, prefix string) (*Tailer, chan *LogLine, *watcher.LogWatcher, afero.Fs, string) {
	if testing.Short() {
		t.Skip("skipping real fs test in short mode")
	}
	dir, err := ioutil.TempDir("", prefix)
	if err != nil {
		t.Fatalf("can't create tempdir: %v", err)
	}

	fs := afero.NewOsFs()
	w, err := watcher.NewLogWatcher()
	if err != nil {
		t.Fatalf("can't create watcher: %v", err)
	}
	lines := make(chan *LogLine, 1)
	o := Options{lines, false, w, fs}
	ta, err := New(o)
	if err != nil {
		t.Fatal(err)
	}
	return ta, lines, w, fs, dir
}

func TestTail(t *testing.T) {
	ta, _, w, fs := makeTestTail(t)
	fs.Mkdir("tail_test", os.ModePerm)
	logfile := "/tmp/log"
	f, err := fs.Create(logfile)
	if err != nil {
		t.Error(err)
	}
	defer f.Close()
	defer w.Close()

	err = ta.TailPath(logfile)
	if err != nil {
		t.Fatal(err)
	}
	// Tail also causes the log to be read, so no need to inject an event.

	if _, ok := ta.files[logfile]; !ok {
		t.Errorf("path not found in files map: %+#v", ta.files)
	}
}

func TestHandleLogUpdate(t *testing.T) {
	ta, lines, w, fs := makeTestTail(t)

	err := fs.Mkdir("/tail_test", os.ModePerm)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	logfile := "/tail_test/log"
	f, err := fs.Create(logfile)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	result := []*LogLine{}
	done := make(chan struct{})
	wg := sync.WaitGroup{}
	go func() {
		for line := range lines {
			result = append(result, line)
			wg.Done()
		}
		close(done)
	}()

	err = ta.TailPath(logfile)
	if err != nil {
		t.Fatal(err)
	}

	wg.Add(4)
	_, err = f.WriteString("a\nb\nc\nd\n")
	if err != nil {
		t.Fatal(err)
	}
	f.Seek(0, 0) // In memory files share the same offset
	w.InjectUpdate(logfile)

	// ugh
	wg.Wait()
	w.Close()
	<-done

	expected := []*LogLine{
		{logfile, "a"},
		{logfile, "b"},
		{logfile, "c"},
		{logfile, "d"},
	}
	if diff := cmp.Diff(expected, result); diff != "" {
		t.Errorf("result didn't match:\n%s", diff)
	}
}

// TestHandleLogTruncate writes to a file, waits for those
// writes to be seen, then truncates the file and writes some more.
// At the end all lines written must be reported by the tailer.
func TestHandleLogTruncate(t *testing.T) {
	ta, lines, w, fs, dir := makeTestTailReal(t, "/tmp")
	defer func() {
		if err := os.RemoveAll(dir); err != nil {
			t.Fatal(err)
		}
	}()

	logfile := filepath.Join(dir, "log")
	f, err := fs.Create(logfile)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	result := []*LogLine{}
	done := make(chan struct{})
	wg := sync.WaitGroup{}
	go func() {
		for line := range lines {
			result = append(result, line)
			wg.Done()
		}
		close(done)
	}()

	err = ta.TailPath(logfile)
	if err != nil {
		t.Fatal(err)
	}

	wg.Add(3)
	_, err = f.WriteString("a\nb\nc\n")
	if err != nil {
		t.Fatal(err)
	}
	wg.Wait()

	err = f.Truncate(0)
	if err != nil {
		t.Fatal(err)
	}
	// Truncate does not move the I/O offset, so move it explicitly.
	if _, terr := f.Seek(0, io.SeekStart); terr != nil {
		t.Fatal(terr)
	}

	// This is potentially racy.  Unlike in the case where we've got new
	// lines that we can verify were seen with the WaitGroup, here nothing
	// ensures that this update-due-to-truncate is seen by the Tailer before
	// we write new data to the file.  In order to avoid the race we'll make
	// sure that the total data size written post-truncate is less than
	// pre-truncate, so that the post-truncate offset is always smaller
	// than the offset seen after wg.Add(3); wg.Wait() above.

	wg.Add(2)
	_, err = f.WriteString("d\ne\n")
	if err != nil {
		t.Fatal(err)
	}

	wg.Wait()
	if err := w.Close(); err != nil {
		t.Log(err)
	}
	<-done

	expected := []*LogLine{
		{logfile, "a"},
		{logfile, "b"},
		{logfile, "c"},
		{logfile, "d"},
		{logfile, "e"},
	}
	if diff := cmp.Diff(expected, result); diff != "" {
		t.Errorf("result didn't match:\n%s", diff)
	}
}

func TestHandleLogUpdatePartialLine(t *testing.T) {
	ta, lines, w, fs := makeTestTail(t)

	err := fs.Mkdir("/tail_test", os.ModePerm)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	logfile := "/tail_test/log"
	f, err := fs.Create(logfile)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	result := []*LogLine{}
	done := make(chan struct{})
	wg := sync.WaitGroup{}
	wg.Add(1)
	go func() {
		for line := range lines {
			result = append(result, line)
			wg.Done()
		}
		close(done)
	}()

	err = ta.TailPath(logfile)
	if err != nil {
		t.Fatal(err)
	}

	_, err = f.WriteString("a")
	if err != nil {
		t.Fatal(err)
	}
	f.Seek(0, 0)
	w.InjectUpdate(logfile)

	f.Seek(1, 0)
	_, err = f.WriteString("b")
	if err != nil {
		t.Error(err)
	}
	f.Seek(1, 0)
	w.InjectUpdate(logfile)

	f.Seek(2, 0)
	_, err = f.WriteString("\n")
	if err != nil {
		t.Error(err)
	}
	f.Seek(2, 0)
	w.InjectUpdate(logfile)

	wg.Wait()
	w.Close()
	<-done

	expected := []*LogLine{
		{logfile, "ab"},
	}
	diff := cmp.Diff(expected, result)
	if diff != "" {
		t.Errorf("result didn't match:\n%s", diff)
	}

}

func TestReadPartial(t *testing.T) {
	ta, lines, w, fs := makeTestTail(t)
	defer w.Close()

	f, err := fs.Create("t")
	if err != nil {
		t.Fatal(err)
	}
	p := bytes.NewBufferString("")
	err = ta.read(f, p)
	if p.String() != "" {
		t.Errorf("partial line returned not empty: %q", p)
	}
	if err != io.EOF {
		t.Errorf("error returned not EOF: %v", err)
	}
	p.Reset()
	p.WriteString("o")
	f.WriteString("hi")
	f.Seek(0, 0)
	err = ta.read(f, p)
	if p.String() != "ohi" {
		t.Errorf("partial line returned not expected: %q", p)
	}
	if err != io.EOF {
		t.Errorf("error returned not EOF: %v", err)
	}
	p.Reset()
	err = ta.read(f, p)
	if err != io.EOF {
		t.Errorf("error returned not EOF: %v", err)
	}
	f.WriteString("\n")
	f.Seek(-1, os.SEEK_END)
	p.Reset()
	p.WriteString("ohi")
	err = ta.read(f, p)
	l := <-lines
	if l.Line != "ohi" {
		t.Errorf("line emitted not ohi: %q", l)
	}
	if p.String() != "" {
		t.Errorf("partial not empty: %q", p)
	}
	if err != io.EOF {
		t.Errorf("error returned not EOF: %v", err)
	}
}

func TestReadPipe(t *testing.T) {
	ta, lines, wa, _ := makeTestTail(t)
	defer wa.Close()

	r, w, err := os.Pipe()
	if err != nil {
		t.Fatal(err)
	}

	err = ta.TailFile(r)
	if err != nil {
		t.Fatal(err)
	}
	n, err := w.WriteString("hi\n")
	if err != nil {
		t.Fatal(err)
	}
	if n < 2 {
		t.Fatalf("Didn't write enough bytes: %d", n)
	}
	l := <-lines
	if l.Line != "hi" {
		t.Errorf("line not expected: %q", l)
	}
}

func TestOpenRetries(t *testing.T) {
	// This test is flaky (when go1.8 and GOMAXPROCS=2) because the write
	// immediately after open may race and there's a io.SeekEnd after the write
	// but before the reader reads from the start of the new file, missing the
	// content, so the test never reads a line.
	t.Skip("flaky")

	// Use the real filesystem because afero doesn't implement correct
	// permissions checking on OpenFile in the memfile implementation.
	ta, lines, w, fs, dir := makeTestTailReal(t, "retries")
	defer func() {
		if err := os.RemoveAll(dir); err != nil {
			t.Log(err)
		}
	}()

	logfile := filepath.Join(dir, "log")
	if _, err := fs.OpenFile(logfile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0); err != nil {
		t.Fatal(err)
	}

	done := make(chan struct{})
	wg := sync.WaitGroup{}
	wg.Add(1) // lines written
	go func() {
		for range lines {
			wg.Done()
		}
		close(done)
	}()

	if err := ta.TailPath(logfile); err != nil {
		t.Fatal(err)
	}
	time.Sleep(10 * time.Millisecond)
	glog.Info("remove")
	if err := fs.Remove(logfile); err != nil {
		t.Fatal(err)
	}
	time.Sleep(10 * time.Millisecond)
	glog.Info("openfile")
	f, err := fs.OpenFile(logfile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0)
	if err != nil {
		t.Fatal(err)
	}
	time.Sleep(10 * time.Millisecond)
	glog.Info("chmod")
	if err := fs.Chmod(logfile, 0666); err != nil {
		t.Fatal(err)
	}
	time.Sleep(10 * time.Millisecond)
	glog.Info("write string")
	if _, err := f.WriteString("\n"); err != nil {
		t.Fatal(err)
	}
	wg.Wait()
	if err := w.Close(); err != nil {
		t.Log(err)
	}
	<-done
}
<commit_msg>Test bad initialisation of the tailer.<commit_after>// Copyright 2011 Google Inc. All Rights Reserved.
// This file is available under the Apache license.

package tailer

import (
	"bytes"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"sync"
	"testing"
	"time"

	"github.com/golang/glog"
	"github.com/google/go-cmp/cmp"
	"github.com/google/mtail/watcher"

	"github.com/spf13/afero"
)

func makeTestTail(t *testing.T) (*Tailer, chan *LogLine, *watcher.FakeWatcher, afero.Fs) {
	fs := afero.NewMemMapFs()
	w := watcher.NewFakeWatcher()
	lines := make(chan *LogLine, 1)
	o := Options{lines, false, w, fs}
	ta, err := New(o)
	if err != nil {
		t.Fatal(err)
	}
	return ta, lines, w, fs
}

func makeTestTailReal(t *testing.T, prefix string) (*Tailer, chan *LogLine, *watcher.LogWatcher, afero.Fs, string) {
	if testing.Short() {
		t.Skip("skipping real fs test in short mode")
	}
	dir, err := ioutil.TempDir("", prefix)
	if err != nil {
		t.Fatalf("can't create tempdir: %v", err)
	}

	fs := afero.NewOsFs()
	w, err := watcher.NewLogWatcher()
	if err != nil {
		t.Fatalf("can't create watcher: %v", err)
	}
	lines := make(chan *LogLine, 1)
	o := Options{lines, false, w, fs}
	ta, err := New(o)
	if err != nil {
		t.Fatal(err)
	}
	return ta, lines, w, fs, dir
}

func TestTail(t *testing.T) {
	ta, _, w, fs := makeTestTail(t)
	fs.Mkdir("tail_test", os.ModePerm)
	logfile := "/tmp/log"
	f, err := fs.Create(logfile)
	if err != nil {
		t.Error(err)
	}
	defer f.Close()
	defer w.Close()

	err = ta.TailPath(logfile)
	if err != nil {
		t.Fatal(err)
	}
	// Tail also causes the log to be read, so no need to inject an event.

	if _, ok := ta.files[logfile]; !ok {
		t.Errorf("path not found in files map: %+#v", ta.files)
	}
}

func TestHandleLogUpdate(t *testing.T) {
	ta, lines, w, fs := makeTestTail(t)

	err := fs.Mkdir("/tail_test", os.ModePerm)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	logfile := "/tail_test/log"
	f, err := fs.Create(logfile)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	result := []*LogLine{}
	done := make(chan struct{})
	wg := sync.WaitGroup{}
	go func() {
		for line := range lines {
			result = append(result, line)
			wg.Done()
		}
		close(done)
	}()

	err = ta.TailPath(logfile)
	if err != nil {
		t.Fatal(err)
	}

	wg.Add(4)
	_, err = f.WriteString("a\nb\nc\nd\n")
	if err != nil {
		t.Fatal(err)
	}
	f.Seek(0, 0) // In memory files share the same offset
	w.InjectUpdate(logfile)

	// ugh
	wg.Wait()
	w.Close()
	<-done

	expected := []*LogLine{
		{logfile, "a"},
		{logfile, "b"},
		{logfile, "c"},
		{logfile, "d"},
	}
	if diff := cmp.Diff(expected, result); diff != "" {
		t.Errorf("result didn't match:\n%s", diff)
	}
}

// TestHandleLogTruncate writes to a file, waits for those
// writes to be seen, then truncates the file and writes some more.
// At the end all lines written must be reported by the tailer.
func TestHandleLogTruncate(t *testing.T) {
	ta, lines, w, fs, dir := makeTestTailReal(t, "/tmp")
	defer func() {
		if err := os.RemoveAll(dir); err != nil {
			t.Fatal(err)
		}
	}()

	logfile := filepath.Join(dir, "log")
	f, err := fs.Create(logfile)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	result := []*LogLine{}
	done := make(chan struct{})
	wg := sync.WaitGroup{}
	go func() {
		for line := range lines {
			result = append(result, line)
			wg.Done()
		}
		close(done)
	}()

	err = ta.TailPath(logfile)
	if err != nil {
		t.Fatal(err)
	}

	wg.Add(3)
	_, err = f.WriteString("a\nb\nc\n")
	if err != nil {
		t.Fatal(err)
	}
	wg.Wait()

	err = f.Truncate(0)
	if err != nil {
		t.Fatal(err)
	}
	// Truncate does not move the I/O offset, so move it explicitly.
	if _, terr := f.Seek(0, io.SeekStart); terr != nil {
		t.Fatal(terr)
	}

	// This is potentially racy.  Unlike in the case where we've got new
	// lines that we can verify were seen with the WaitGroup, here nothing
	// ensures that this update-due-to-truncate is seen by the Tailer before
	// we write new data to the file.  In order to avoid the race we'll make
	// sure that the total data size written post-truncate is less than
	// pre-truncate, so that the post-truncate offset is always smaller
	// than the offset seen after wg.Add(3); wg.Wait() above.

	wg.Add(2)
	_, err = f.WriteString("d\ne\n")
	if err != nil {
		t.Fatal(err)
	}

	wg.Wait()
	if err := w.Close(); err != nil {
		t.Log(err)
	}
	<-done

	expected := []*LogLine{
		{logfile, "a"},
		{logfile, "b"},
		{logfile, "c"},
		{logfile, "d"},
		{logfile, "e"},
	}
	if diff := cmp.Diff(expected, result); diff != "" {
		t.Errorf("result didn't match:\n%s", diff)
	}
}

func TestHandleLogUpdatePartialLine(t *testing.T) {
	ta, lines, w, fs := makeTestTail(t)

	err := fs.Mkdir("/tail_test", os.ModePerm)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	logfile := "/tail_test/log"
	f, err := fs.Create(logfile)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	result := []*LogLine{}
	done := make(chan struct{})
	wg := sync.WaitGroup{}
	wg.Add(1)
	go func() {
		for line := range lines {
			result = append(result, line)
			wg.Done()
		}
		close(done)
	}()

	err = ta.TailPath(logfile)
	if err != nil {
		t.Fatal(err)
	}

	_, err = f.WriteString("a")
	if err != nil {
		t.Fatal(err)
	}
	f.Seek(0, 0)
	w.InjectUpdate(logfile)

	f.Seek(1, 0)
	_, err = f.WriteString("b")
	if err != nil {
		t.Error(err)
	}
	f.Seek(1, 0)
	w.InjectUpdate(logfile)

	f.Seek(2, 0)
	_, err = f.WriteString("\n")
	if err != nil {
		t.Error(err)
	}
	f.Seek(2, 0)
	w.InjectUpdate(logfile)

	wg.Wait()
	w.Close()
	<-done

	expected := []*LogLine{
		{logfile, "ab"},
	}
	diff := cmp.Diff(expected, result)
	if diff != "" {
		t.Errorf("result didn't match:\n%s", diff)
	}

}

func TestReadPartial(t *testing.T) {
	ta, lines, w, fs := makeTestTail(t)
	defer w.Close()

	f, err := fs.Create("t")
	if err != nil {
		t.Fatal(err)
	}
	p := bytes.NewBufferString("")
	err = ta.read(f, p)
	if p.String() != "" {
		t.Errorf("partial line returned not empty: %q", p)
	}
	if err != io.EOF {
		t.Errorf("error returned not EOF: %v", err)
	}
	p.Reset()
	p.WriteString("o")
	f.WriteString("hi")
	f.Seek(0, 0)
	err = ta.read(f, p)
	if p.String() != "ohi" {
		t.Errorf("partial line returned not expected: %q", p)
	}
	if err != io.EOF {
		t.Errorf("error returned not EOF: %v", err)
	}
	p.Reset()
	err = ta.read(f, p)
	if err != io.EOF {
		t.Errorf("error returned not EOF: %v", err)
	}
	f.WriteString("\n")
	f.Seek(-1, os.SEEK_END)
	p.Reset()
	p.WriteString("ohi")
	err = ta.read(f, p)
	l := <-lines
	if l.Line != "ohi" {
		t.Errorf("line emitted not ohi: %q", l)
	}
	if p.String() != "" {
		t.Errorf("partial not empty: %q", p)
	}
	if err != io.EOF {
		t.Errorf("error returned not EOF: %v", err)
	}
}

func TestReadPipe(t *testing.T) {
	ta, lines, wa, _ := makeTestTail(t)
	defer wa.Close()

	r, w, err := os.Pipe()
	if err != nil {
		t.Fatal(err)
	}

	err = ta.TailFile(r)
	if err != nil {
		t.Fatal(err)
	}
	n, err := w.WriteString("hi\n")
	if err != nil {
		t.Fatal(err)
	}
	if n < 2 {
		t.Fatalf("Didn't write enough bytes: %d", n)
	}
	l := <-lines
	if l.Line != "hi" {
		t.Errorf("line not expected: %q", l)
	}
}

func TestOpenRetries(t *testing.T) {
	// This test is flaky (when go1.8 and GOMAXPROCS=2) because the write
	// immediately after open may race and there's a io.SeekEnd after the write
	// but before the reader reads from the start of the new file, missing the
	// content, so the test never reads a line.
	t.Skip("flaky")

	// Use the real filesystem because afero doesn't implement correct
	// permissions checking on OpenFile in the memfile implementation.
	ta, lines, w, fs, dir := makeTestTailReal(t, "retries")
	defer func() {
		if err := os.RemoveAll(dir); err != nil {
			t.Log(err)
		}
	}()

	logfile := filepath.Join(dir, "log")
	if _, err := fs.OpenFile(logfile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0); err != nil {
		t.Fatal(err)
	}

	done := make(chan struct{})
	wg := sync.WaitGroup{}
	wg.Add(1) // lines written
	go func() {
		for range lines {
			wg.Done()
		}
		close(done)
	}()

	if err := ta.TailPath(logfile); err != nil {
		t.Fatal(err)
	}
	time.Sleep(10 * time.Millisecond)
	glog.Info("remove")
	if err := fs.Remove(logfile); err != nil {
		t.Fatal(err)
	}
	time.Sleep(10 * time.Millisecond)
	glog.Info("openfile")
	f, err := fs.OpenFile(logfile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0)
	if err != nil {
		t.Fatal(err)
	}
	time.Sleep(10 * time.Millisecond)
	glog.Info("chmod")
	if err := fs.Chmod(logfile, 0666); err != nil {
		t.Fatal(err)
	}
	time.Sleep(10 * time.Millisecond)
	glog.Info("write string")
	if _, err := f.WriteString("\n"); err != nil {
		t.Fatal(err)
	}
	wg.Wait()
	if err := w.Close(); err != nil {
		t.Log(err)
	}
	<-done
}

func TestTailerInitErrors(t *testing.T) {
	o := Options{}
	_, err := New(o)
	if err == nil {
		t.Error("expected error")
	}
}
<|endoftext|>


<commit_before>package weed_server

import (
	"fmt"
	"github.com/chrislusf/seaweedfs/weed/pb/volume_server_pb"
	"github.com/chrislusf/seaweedfs/weed/storage"
	"github.com/chrislusf/seaweedfs/weed/storage/needle"
	"github.com/chrislusf/seaweedfs/weed/storage/super_block"
)

func (vs *VolumeServer) ReadAllNeedles(req *volume_server_pb.ReadAllNeedlesRequest, stream volume_server_pb.VolumeServer_ReadAllNeedlesServer) (err error) {

	v := vs.store.GetVolume(needle.VolumeId(req.VolumeId))
	if v == nil {
		return fmt.Errorf("not found volume id %d", req.VolumeId)
	}

	scanner := &VolumeFileScanner4ReadAll{
		stream: stream,
	}

	err = storage.ScanVolumeFileFrom(v.Version(), v.DataBackend, super_block.SuperBlockSize, scanner)

	return err

}

type VolumeFileScanner4ReadAll struct {
	stream volume_server_pb.VolumeServer_ReadAllNeedlesServer
}

func (scanner *VolumeFileScanner4ReadAll) VisitSuperBlock(superBlock super_block.SuperBlock) error {
	return nil

}
func (scanner *VolumeFileScanner4ReadAll) ReadNeedleBody() bool {
	return true
}

func (scanner *VolumeFileScanner4ReadAll) VisitNeedle(n *needle.Needle, offset int64, needleHeader, needleBody []byte) error {

	sendErr := scanner.stream.Send(&volume_server_pb.ReadAllNeedlesResponse{
		NeedleId:   uint64(n.Id),
		NeedleBlob: n.Data,
	})
	if sendErr != nil {
		return sendErr
	}
	return nil
}
<commit_msg>adjust starting offset<commit_after>package weed_server

import (
	"fmt"
	"github.com/chrislusf/seaweedfs/weed/pb/volume_server_pb"
	"github.com/chrislusf/seaweedfs/weed/storage"
	"github.com/chrislusf/seaweedfs/weed/storage/needle"
	"github.com/chrislusf/seaweedfs/weed/storage/super_block"
)

func (vs *VolumeServer) ReadAllNeedles(req *volume_server_pb.ReadAllNeedlesRequest, stream volume_server_pb.VolumeServer_ReadAllNeedlesServer) (err error) {

	v := vs.store.GetVolume(needle.VolumeId(req.VolumeId))
	if v == nil {
		return fmt.Errorf("not found volume id %d", req.VolumeId)
	}

	scanner := &VolumeFileScanner4ReadAll{
		stream: stream,
	}

	offset := int64(v.SuperBlock.BlockSize())

	err = storage.ScanVolumeFileFrom(v.Version(), v.DataBackend, offset, scanner)

	return err

}

type VolumeFileScanner4ReadAll struct {
	stream volume_server_pb.VolumeServer_ReadAllNeedlesServer
}

func (scanner *VolumeFileScanner4ReadAll) VisitSuperBlock(superBlock super_block.SuperBlock) error {
	return nil

}
func (scanner *VolumeFileScanner4ReadAll) ReadNeedleBody() bool {
	return true
}

func (scanner *VolumeFileScanner4ReadAll) VisitNeedle(n *needle.Needle, offset int64, needleHeader, needleBody []byte) error {

	sendErr := scanner.stream.Send(&volume_server_pb.ReadAllNeedlesResponse{
		NeedleId:   uint64(n.Id),
		NeedleBlob: n.Data,
	})
	if sendErr != nil {
		return sendErr
	}
	return nil
}
<|endoftext|>


<commit_before>package brats_test

import (
	"fmt"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gbytes"
	"github.com/onsi/gomega/gexec"
	"os/exec"
	"path/filepath"
	"regexp"
	"time"
)

func extractAzIpsMap(regex *regexp.Regexp, contents string) map[string][]string {
	out := map[string][]string{
		"z1": {},
		"z2": {},
	}

	instances := regex.FindAllStringSubmatch(contents, -1)
	Expect(instances).ToNot(BeNil())
	for _, q := range instances {
		out[q[1]] = append(out[q[1]], q[2])
	}

	return out
}

var _ = Describe("BoshDns", func() {
	Context("When deploy vms across different azs", func() {
		var deploymentName = "dns-with-templates"

		BeforeEach(func() {
			startInnerBosh()

			session, err := gexec.Start(exec.Command(boshBinaryPath, "-n", "upload-stemcell", candidateWardenLinuxStemcellPath), GinkgoWriter, GinkgoWriter)
			Expect(err).ToNot(HaveOccurred())
			Eventually(session, 5*time.Minute).Should(gexec.Exit(0))

			manifestPath, err := filepath.Abs("../assets/dns-with-templates-manifest.yml")

			session, err = gexec.Start(exec.Command(
				boshBinaryPath, "deploy",
				"-n",
				"-d", deploymentName,
				manifestPath,
				"-v", fmt.Sprintf("dns-release-path=%s", dnsReleasePath),
				"-v", "linked-template-release-path=../assets/linked-templates-release",
			), GinkgoWriter, GinkgoWriter)
			Expect(err).ToNot(HaveOccurred())
			Eventually(session, 3*time.Minute).Should(gexec.Exit(0))
		})

		AfterEach(stopInnerBosh)

		It("can find instances using the address helper", func() {
			session, err := gexec.Start(exec.Command(
				boshBinaryPath, "-n",
				"-d", deploymentName,
				"instances",
				"--column", "instance",
				"--column", "az",
				"--column", "ips",
			), GinkgoWriter, GinkgoWriter)
			Expect(err).ToNot(HaveOccurred())
			Eventually(session, time.Minute).Should(gexec.Exit(0))

			instanceList := session.Out.Contents()

			By("finding instances in all AZs", func() {
				matchExpression := regexp.MustCompile("provider\\S+\\s+(z1|z2)\\s+(\\S+)")
				knownProviders := extractAzIpsMap(matchExpression, string(instanceList))

				session, err = gexec.Start(exec.Command(boshBinaryPath,
					"-d", deploymentName,
					"run-errand", "query-all",
				), GinkgoWriter, GinkgoWriter)
				Expect(err).ToNot(HaveOccurred())
				Eventually(session, time.Minute).Should(gexec.Exit(0))

				Expect(session.Out).To(gbytes.Say("ANSWER: 3"))

				for _, ips := range knownProviders {
					for _, ip := range ips {
						Expect(string(session.Out.Contents())).To(ContainSubstring(ip))
					}
				}
			})

			By("finding instances filtering by AZ", func() {
				matchExpression := regexp.MustCompile("provider\\S+\\s+(z1)\\s+(\\S+)")
				knownProviders := extractAzIpsMap(matchExpression, string(instanceList))

				session, err = gexec.Start(exec.Command(boshBinaryPath,
					"-d", deploymentName,
					"run-errand", "query-with-az-filter",
				), GinkgoWriter, GinkgoWriter)
				Expect(err).ToNot(HaveOccurred())
				Eventually(session, time.Minute).Should(gexec.Exit(0))

				Expect(session.Out).To(gbytes.Say("ANSWER: 2"))

				for _, ips := range knownProviders {
					for _, ip := range ips {
						Expect(string(session.Out.Contents())).To(ContainSubstring(ip))
					}
				}
			})
		})
	})
})
<commit_msg>Increase timeout for DNS BRATS test<commit_after>package brats_test

import (
	"fmt"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gbytes"
	"github.com/onsi/gomega/gexec"
	"os/exec"
	"path/filepath"
	"regexp"
	"time"
)

func extractAzIpsMap(regex *regexp.Regexp, contents string) map[string][]string {
	out := map[string][]string{
		"z1": {},
		"z2": {},
	}

	instances := regex.FindAllStringSubmatch(contents, -1)
	Expect(instances).ToNot(BeNil())
	for _, q := range instances {
		out[q[1]] = append(out[q[1]], q[2])
	}

	return out
}

var _ = Describe("BoshDns", func() {
	Context("When deploy vms across different azs", func() {
		var deploymentName = "dns-with-templates"

		BeforeEach(func() {
			startInnerBosh()

			session, err := gexec.Start(exec.Command(boshBinaryPath, "-n", "upload-stemcell", candidateWardenLinuxStemcellPath), GinkgoWriter, GinkgoWriter)
			Expect(err).ToNot(HaveOccurred())
			Eventually(session, 5*time.Minute).Should(gexec.Exit(0))

			manifestPath, err := filepath.Abs("../assets/dns-with-templates-manifest.yml")

			session, err = gexec.Start(exec.Command(
				boshBinaryPath, "deploy",
				"-n",
				"-d", deploymentName,
				manifestPath,
				"-v", fmt.Sprintf("dns-release-path=%s", dnsReleasePath),
				"-v", "linked-template-release-path=../assets/linked-templates-release",
			), GinkgoWriter, GinkgoWriter)
			Expect(err).ToNot(HaveOccurred())
			Eventually(session, 6*time.Minute).Should(gexec.Exit(0))
		})

		AfterEach(stopInnerBosh)

		It("can find instances using the address helper", func() {
			session, err := gexec.Start(exec.Command(
				boshBinaryPath, "-n",
				"-d", deploymentName,
				"instances",
				"--column", "instance",
				"--column", "az",
				"--column", "ips",
			), GinkgoWriter, GinkgoWriter)
			Expect(err).ToNot(HaveOccurred())
			Eventually(session, time.Minute).Should(gexec.Exit(0))

			instanceList := session.Out.Contents()

			By("finding instances in all AZs", func() {
				matchExpression := regexp.MustCompile("provider\\S+\\s+(z1|z2)\\s+(\\S+)")
				knownProviders := extractAzIpsMap(matchExpression, string(instanceList))

				session, err = gexec.Start(exec.Command(boshBinaryPath,
					"-d", deploymentName,
					"run-errand", "query-all",
				), GinkgoWriter, GinkgoWriter)
				Expect(err).ToNot(HaveOccurred())
				Eventually(session, time.Minute).Should(gexec.Exit(0))

				Expect(session.Out).To(gbytes.Say("ANSWER: 3"))

				for _, ips := range knownProviders {
					for _, ip := range ips {
						Expect(string(session.Out.Contents())).To(ContainSubstring(ip))
					}
				}
			})

			By("finding instances filtering by AZ", func() {
				matchExpression := regexp.MustCompile("provider\\S+\\s+(z1)\\s+(\\S+)")
				knownProviders := extractAzIpsMap(matchExpression, string(instanceList))

				session, err = gexec.Start(exec.Command(boshBinaryPath,
					"-d", deploymentName,
					"run-errand", "query-with-az-filter",
				), GinkgoWriter, GinkgoWriter)
				Expect(err).ToNot(HaveOccurred())
				Eventually(session, time.Minute).Should(gexec.Exit(0))

				Expect(session.Out).To(gbytes.Say("ANSWER: 2"))

				for _, ips := range knownProviders {
					for _, ip := range ips {
						Expect(string(session.Out.Contents())).To(ContainSubstring(ip))
					}
				}
			})
		})
	})
})
<|endoftext|>


<commit_before>package handler

import (
	log "github.com/Sirupsen/logrus"
	"github.com/codegangsta/cli"
	"github.com/gorilla/mux"
	"github.com/ory-am/ladon/guard"
	accounts "github.com/ory-am/hydra/account/handler"
	"github.com/ory-am/hydra/jwt"
	"github.com/ory-am/hydra/middleware/host"
	middleware "github.com/ory-am/hydra/middleware/host"
	clients "github.com/ory-am/hydra/oauth/client/handler"
	connections "github.com/ory-am/hydra/oauth/connection/handler"
	oauth "github.com/ory-am/hydra/oauth/handler"
	"github.com/ory-am/hydra/oauth/provider"
	policies "github.com/ory-am/hydra/policy/handler"
	gojwt "github.com/dgrijalva/jwt-go"

	"fmt"
	"net/http"
	"strconv"

	"github.com/RangelReale/osin"
	"github.com/ory-am/common/pkg"
	"golang.org/x/net/http2"
	"crypto/tls"
)

type Core struct {
	Ctx               Context
	accountHandler    *accounts.Handler
	clientHandler     *clients.Handler
	connectionHandler *connections.Handler
	oauthHandler      *oauth.Handler
	policyHandler     *policies.Handler

	guard     guard.Guarder
	providers provider.Registry

	issuer   string
	audience string
}

func osinConfig() (conf *osin.ServerConfig, err error) {
	conf = osin.NewServerConfig()
	lifetime, err := strconv.Atoi(accessTokenLifetime)
	if err != nil {
		return nil, err
	}
	conf.AccessExpiration = int32(lifetime)

	conf.AllowedAuthorizeTypes = osin.AllowedAuthorizeType{
		osin.CODE,
		osin.TOKEN,
	}
	conf.AllowedAccessTypes = osin.AllowedAccessType{
		osin.AUTHORIZATION_CODE,
		osin.REFRESH_TOKEN,
		osin.PASSWORD,
		osin.CLIENT_CREDENTIALS,
	}
	conf.AllowGetAccessRequest = false
	conf.AllowClientSecretInParams = false
	conf.ErrorStatusCode = http.StatusInternalServerError
	conf.RedirectUriSeparator = "|"
	return conf, nil
}

func (c *Core) Start(ctx *cli.Context) error {
	// Start the database backend
	if err := c.Ctx.Start(); err != nil {
		return fmt.Errorf("Could not start context: %s", err)
	}

	private, err := jwt.LoadCertificate(jwtPrivateKey)
	if err != nil {
		return fmt.Errorf("Could not load private key: %s", err)
	}

	public, err := jwt.LoadCertificate(jwtPublicKey)
	if err != nil {
		return fmt.Errorf("Could not load public key: %s", err)
	}

	_, err = gojwt.ParseRSAPublicKeyFromPEM(public)
	if err != nil {
		return fmt.Errorf("Not a valid public key: %s", err)
	}

	_, err = gojwt.ParseRSAPrivateKeyFromPEM(private)
	if err != nil {
		return fmt.Errorf("Not a valid private key: %s", err)
	}

	osinConf, err := osinConfig()
	if err != nil {
		return fmt.Errorf("Could not configure server: %s", err)
	}

	j := jwt.New(private, public)
	m := middleware.New(c.Ctx.GetPolicies(), j)
	c.guard = new(guard.Guard)
	c.accountHandler = accounts.NewHandler(c.Ctx.GetAccounts(), m)
	c.clientHandler = clients.NewHandler(c.Ctx.GetOsins(), m)
	c.connectionHandler = connections.NewHandler(c.Ctx.GetConnections(), m)
	c.providers = provider.NewRegistry(providers)
	c.policyHandler = policies.NewHandler(c.Ctx.GetPolicies(), m, c.guard, j, c.Ctx.GetOsins())
	c.oauthHandler = &oauth.Handler{
		Accounts:       c.Ctx.GetAccounts(),
		Policies:       c.Ctx.GetPolicies(),
		Guard:          c.guard,
		Connections:    c.Ctx.GetConnections(),
		Providers:      c.providers,
		Issuer:         c.issuer,
		Audience:       c.audience,
		JWT:            j,
		OAuthConfig:    osinConf,
		OAuthStore:     c.Ctx.GetOsins(),
		States:         c.Ctx.GetStates(),
		SignUpLocation: locations["signUp"],
		SignInLocation: locations["signIn"],
		Middleware:     host.New(c.Ctx.GetPolicies(), j),
	}

	extractor := m.ExtractAuthentication
	router := mux.NewRouter()
	c.accountHandler.SetRoutes(router, extractor)
	c.connectionHandler.SetRoutes(router, extractor)
	c.clientHandler.SetRoutes(router, extractor)
	c.oauthHandler.SetRoutes(router, extractor)
	c.policyHandler.SetRoutes(router, extractor)

	// TODO un-hack this, add database check, add error response
	router.HandleFunc("/alive", func(w http.ResponseWriter, r *http.Request) {
		pkg.WriteJSON(w, &struct {
			Status string `json:"status"`
		}{
			Status: "alive",
		})
	})

	log.Infoln("Hydra started")

	if forceHTTP == "force" {
		http.Handle("/", router)
		log.Warn("You're using HTTP without TLS encryption. This is dangerously unsafe and you should not do this.")
		if err := http.ListenAndServe(listenOn, nil); err != nil {
			return fmt.Errorf("Could not serve HTTP server because %s", err)
		}
		return nil
	}

	var cert tls.Certificate
	if cert, err = tls.LoadX509KeyPair(tlsCert, tlsKey); err != nil {
		if cert, err = tls.X509KeyPair([]byte(tlsCert), []byte(tlsKey)); err != nil {
			return fmt.Errorf("Could not load or parse TLS key pair because %s", err)
		}
	}
	srv := &http.Server{
		Addr: listenOn,
		TLSConfig:      &tls.Config{
			Certificates: []tls.Certificate{
				cert,
			},
		},
	}

	http.Handle("/", router)
	http2.ConfigureServer(srv, &http2.Server{})
	if err := srv.ListenAndServeTLS("", ""); err != nil {
		return fmt.Errorf("Could not serve HTTP/2 server because %s", err)
	}

	return nil
}
<commit_msg>cli: remove http2 dependency<commit_after>package handler

import (
	log "github.com/Sirupsen/logrus"
	"github.com/codegangsta/cli"
	"github.com/gorilla/mux"
	"github.com/ory-am/ladon/guard"
	accounts "github.com/ory-am/hydra/account/handler"
	"github.com/ory-am/hydra/jwt"
	"github.com/ory-am/hydra/middleware/host"
	middleware "github.com/ory-am/hydra/middleware/host"
	clients "github.com/ory-am/hydra/oauth/client/handler"
	connections "github.com/ory-am/hydra/oauth/connection/handler"
	oauth "github.com/ory-am/hydra/oauth/handler"
	"github.com/ory-am/hydra/oauth/provider"
	policies "github.com/ory-am/hydra/policy/handler"
	gojwt "github.com/dgrijalva/jwt-go"

	"fmt"
	"net/http"
	"strconv"

	"github.com/RangelReale/osin"
	"github.com/ory-am/common/pkg"
	"crypto/tls"
)

type Core struct {
	Ctx               Context
	accountHandler    *accounts.Handler
	clientHandler     *clients.Handler
	connectionHandler *connections.Handler
	oauthHandler      *oauth.Handler
	policyHandler     *policies.Handler

	guard     guard.Guarder
	providers provider.Registry

	issuer   string
	audience string
}

func osinConfig() (conf *osin.ServerConfig, err error) {
	conf = osin.NewServerConfig()
	lifetime, err := strconv.Atoi(accessTokenLifetime)
	if err != nil {
		return nil, err
	}
	conf.AccessExpiration = int32(lifetime)

	conf.AllowedAuthorizeTypes = osin.AllowedAuthorizeType{
		osin.CODE,
		osin.TOKEN,
	}
	conf.AllowedAccessTypes = osin.AllowedAccessType{
		osin.AUTHORIZATION_CODE,
		osin.REFRESH_TOKEN,
		osin.PASSWORD,
		osin.CLIENT_CREDENTIALS,
	}
	conf.AllowGetAccessRequest = false
	conf.AllowClientSecretInParams = false
	conf.ErrorStatusCode = http.StatusInternalServerError
	conf.RedirectUriSeparator = "|"
	return conf, nil
}

func (c *Core) Start(ctx *cli.Context) error {
	// Start the database backend
	if err := c.Ctx.Start(); err != nil {
		return fmt.Errorf("Could not start context: %s", err)
	}

	private, err := jwt.LoadCertificate(jwtPrivateKey)
	if err != nil {
		return fmt.Errorf("Could not load private key: %s", err)
	}

	public, err := jwt.LoadCertificate(jwtPublicKey)
	if err != nil {
		return fmt.Errorf("Could not load public key: %s", err)
	}

	fmt.Printf("Key %s", public)

	if _, err = gojwt.ParseRSAPublicKeyFromPEM(public); err != nil {
		return fmt.Errorf("Not a valid public key: %s", err)
	} else if _, err = gojwt.ParseRSAPrivateKeyFromPEM(private); err != nil {
		return fmt.Errorf("Not a valid private key: %s", err)
	}

	osinConf, err := osinConfig()
	if err != nil {
		return fmt.Errorf("Could not configure server: %s", err)
	}

	j := jwt.New(private, public)
	m := middleware.New(c.Ctx.GetPolicies(), j)
	c.guard = new(guard.Guard)
	c.accountHandler = accounts.NewHandler(c.Ctx.GetAccounts(), m)
	c.clientHandler = clients.NewHandler(c.Ctx.GetOsins(), m)
	c.connectionHandler = connections.NewHandler(c.Ctx.GetConnections(), m)
	c.providers = provider.NewRegistry(providers)
	c.policyHandler = policies.NewHandler(c.Ctx.GetPolicies(), m, c.guard, j, c.Ctx.GetOsins())
	c.oauthHandler = &oauth.Handler{
		Accounts:       c.Ctx.GetAccounts(),
		Policies:       c.Ctx.GetPolicies(),
		Guard:          c.guard,
		Connections:    c.Ctx.GetConnections(),
		Providers:      c.providers,
		Issuer:         c.issuer,
		Audience:       c.audience,
		JWT:            j,
		OAuthConfig:    osinConf,
		OAuthStore:     c.Ctx.GetOsins(),
		States:         c.Ctx.GetStates(),
		SignUpLocation: locations["signUp"],
		SignInLocation: locations["signIn"],
		Middleware:     host.New(c.Ctx.GetPolicies(), j),
	}

	extractor := m.ExtractAuthentication
	router := mux.NewRouter()
	c.accountHandler.SetRoutes(router, extractor)
	c.connectionHandler.SetRoutes(router, extractor)
	c.clientHandler.SetRoutes(router, extractor)
	c.oauthHandler.SetRoutes(router, extractor)
	c.policyHandler.SetRoutes(router, extractor)

	// TODO un-hack this, add database check, add error response
	router.HandleFunc("/alive", func(w http.ResponseWriter, r *http.Request) {
		pkg.WriteJSON(w, &struct {
			Status string `json:"status"`
		}{
			Status: "alive",
		})
	})

	log.Infoln("Hydra initialized, starting listeners...")

	if forceHTTP == "force" {
		http.Handle("/", router)
		log.Warn("You're using HTTP without TLS encryption. This is dangerously unsafe and you should not do this.")
		if err := http.ListenAndServe(listenOn, nil); err != nil {
			return fmt.Errorf("Could not serve HTTP server because %s", err)
		}
		return nil
	}

	var cert tls.Certificate
	if cert, err = tls.LoadX509KeyPair(tlsCert, tlsKey); err != nil {
		if cert, err = tls.X509KeyPair([]byte(tlsCert), []byte(tlsKey)); err != nil {
			return fmt.Errorf("Could not load or parse TLS key pair because %s", err)
		}
	}
	srv := &http.Server{
		Addr: listenOn,
		TLSConfig:      &tls.Config{
			Certificates: []tls.Certificate{
				cert,
			},
		},
	}

	http.Handle("/", router)
	if err := srv.ListenAndServeTLS("", ""); err != nil {
		return fmt.Errorf("Could not serve HTTP/2 server because %s", err)
	}

	return nil
}
<|endoftext|>