<commit_before>// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package internal contains support packages for oauth2 package.
package internal

import (
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"mime"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"

	"golang.org/x/net/context"
)

// Token represents the crendentials used to authorize
// the requests to access protected resources on the OAuth 2.0
// provider's backend.
//
// This type is a mirror of oauth2.Token and exists to break
// an otherwise-circular dependency. Other internal packages
// should convert this Token into an oauth2.Token before use.
type Token struct {
	// AccessToken is the token that authorizes and authenticates
	// the requests.
	AccessToken string

	// TokenType is the type of token.
	// The Type method returns either this or "Bearer", the default.
	TokenType string

	// RefreshToken is a token that's used by the application
	// (as opposed to the user) to refresh the access token
	// if it expires.
	RefreshToken string

	// Expiry is the optional expiration time of the access token.
	//
	// If zero, TokenSource implementations will reuse the same
	// token forever and RefreshToken or equivalent
	// mechanisms for that TokenSource will not be used.
	Expiry time.Time

	// Raw optionally contains extra metadata from the server
	// when updating a token.
	Raw interface{}
}

// tokenJSON is the struct representing the HTTP response from OAuth2
// providers returning a token in JSON form.
type tokenJSON struct {
	AccessToken  string         `json:"access_token"`
	TokenType    string         `json:"token_type"`
	RefreshToken string         `json:"refresh_token"`
	ExpiresIn    expirationTime `json:"expires_in"` // at least PayPal returns string, while most return number
	Expires      expirationTime `json:"expires"`    // broken Facebook spelling of expires_in
}

func (e *tokenJSON) expiry() (t time.Time) {
	if v := e.ExpiresIn; v != 0 {
		return time.Now().Add(time.Duration(v) * time.Second)
	}
	if v := e.Expires; v != 0 {
		return time.Now().Add(time.Duration(v) * time.Second)
	}
	return
}

type expirationTime int32

func (e *expirationTime) UnmarshalJSON(b []byte) error {
	var n json.Number
	err := json.Unmarshal(b, &n)
	if err != nil {
		return err
	}
	i, err := n.Int64()
	if err != nil {
		return err
	}
	*e = expirationTime(i)
	return nil
}

var brokenAuthHeaderProviders = []string{
	"https://accounts.google.com/",
	"https://api.codeswholesale.com/oauth/token",
	"https://api.dropbox.com/",
	"https://api.dropboxapi.com/",
	"https://api.instagram.com/",
	"https://api.netatmo.net/",
	"https://api.odnoklassniki.ru/",
	"https://api.pushbullet.com/",
	"https://api.soundcloud.com/",
	"https://api.twitch.tv/",
	"https://app.box.com/",
	"https://connect.stripe.com/",
	"https://graph.facebook.com", // see https://github.com/golang/oauth2/issues/214
	"https://login.microsoftonline.com/",
	"https://login.salesforce.com/",
	"https://oauth.sandbox.trainingpeaks.com/",
	"https://oauth.trainingpeaks.com/",
	"https://oauth.vk.com/",
	"https://openapi.baidu.com/",
	"https://slack.com/",
	"https://test-sandbox.auth.corp.google.com",
	"https://test.salesforce.com/",
	"https://user.gini.net/",
	"https://www.douban.com/",
	"https://www.googleapis.com/",
	"https://www.linkedin.com/",
	"https://www.strava.com/oauth/",
	"https://www.wunderlist.com/oauth/",
	"https://api.patreon.com/",
	"https://sandbox.codeswholesale.com/oauth/token",
}

// brokenAuthHeaderDomains lists broken providers that issue dynamic endpoints.
var brokenAuthHeaderDomains = []string{
	".force.com",
	".okta.com",
	".oktapreview.com",
}

func RegisterBrokenAuthHeaderProvider(tokenURL string) {
	brokenAuthHeaderProviders = append(brokenAuthHeaderProviders, tokenURL)
}

// providerAuthHeaderWorks reports whether the OAuth2 server identified by the tokenURL
// implements the OAuth2 spec correctly
// See https://code.google.com/p/goauth2/issues/detail?id=31 for background.
// In summary:
// - Reddit only accepts client secret in the Authorization header
// - Dropbox accepts either it in URL param or Auth header, but not both.
// - Google only accepts URL param (not spec compliant?), not Auth header
// - Stripe only accepts client secret in Auth header with Bearer method, not Basic
func providerAuthHeaderWorks(tokenURL string) bool {
	for _, s := range brokenAuthHeaderProviders {
		if strings.HasPrefix(tokenURL, s) {
			// Some sites fail to implement the OAuth2 spec fully.
			return false
		}
	}

	if u, err := url.Parse(tokenURL); err == nil {
		for _, s := range brokenAuthHeaderDomains {
			if strings.HasSuffix(u.Host, s) {
				return false
			}
		}
	}

	// Assume the provider implements the spec properly
	// otherwise. We can add more exceptions as they're
	// discovered. We will _not_ be adding configurable hooks
	// to this package to let users select server bugs.
	return true
}

func RetrieveToken(ctx context.Context, clientID, clientSecret, tokenURL string, v url.Values) (*Token, error) {
	hc, err := ContextClient(ctx)
	if err != nil {
		return nil, err
	}
	bustedAuth := !providerAuthHeaderWorks(tokenURL)
	if bustedAuth {
		if clientID != "" {
			v.Set("client_id", clientID)
		}
		if clientSecret != "" {
			v.Set("client_secret", clientSecret)
		}
	}
	req, err := http.NewRequest("POST", tokenURL, strings.NewReader(v.Encode()))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	if !bustedAuth {
		req.SetBasicAuth(clientID, clientSecret)
	}
	r, err := hc.Do(req)
	if err != nil {
		return nil, err
	}
	defer r.Body.Close()
	body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1<<20))
	if err != nil {
		return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
	}
	if code := r.StatusCode; code < 200 || code > 299 {
		return nil, fmt.Errorf("oauth2: cannot fetch token: %v\nResponse: %s", r.Status, body)
	}

	var token *Token
	content, _, _ := mime.ParseMediaType(r.Header.Get("Content-Type"))
	switch content {
	case "application/x-www-form-urlencoded", "text/plain":
		vals, err := url.ParseQuery(string(body))
		if err != nil {
			return nil, err
		}
		token = &Token{
			AccessToken:  vals.Get("access_token"),
			TokenType:    vals.Get("token_type"),
			RefreshToken: vals.Get("refresh_token"),
			Raw:          vals,
		}
		e := vals.Get("expires_in")
		if e == "" {
			// TODO(jbd): Facebook's OAuth2 implementation is broken and
			// returns expires_in field in expires. Remove the fallback to expires,
			// when Facebook fixes their implementation.
			e = vals.Get("expires")
		}
		expires, _ := strconv.Atoi(e)
		if expires != 0 {
			token.Expiry = time.Now().Add(time.Duration(expires) * time.Second)
		}
	default:
		var tj tokenJSON
		if err = json.Unmarshal(body, &tj); err != nil {
			return nil, err
		}
		token = &Token{
			AccessToken:  tj.AccessToken,
			TokenType:    tj.TokenType,
			RefreshToken: tj.RefreshToken,
			Expiry:       tj.expiry(),
			Raw:          make(map[string]interface{}),
		}
		json.Unmarshal(body, &token.Raw) // no error checks for optional fields
	}
	// Don't overwrite `RefreshToken` with an empty value
	// if this was a token refreshing request.
	if token.RefreshToken == "" {
		token.RefreshToken = v.Get("refresh_token")
	}
	return token, nil
}
<commit_msg>Fix for auth0<commit_after>// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package internal contains support packages for oauth2 package.
package internal

import (
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"mime"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"

	"golang.org/x/net/context"
)

// Token represents the crendentials used to authorize
// the requests to access protected resources on the OAuth 2.0
// provider's backend.
//
// This type is a mirror of oauth2.Token and exists to break
// an otherwise-circular dependency. Other internal packages
// should convert this Token into an oauth2.Token before use.
type Token struct {
	// AccessToken is the token that authorizes and authenticates
	// the requests.
	AccessToken string

	// TokenType is the type of token.
	// The Type method returns either this or "Bearer", the default.
	TokenType string

	// RefreshToken is a token that's used by the application
	// (as opposed to the user) to refresh the access token
	// if it expires.
	RefreshToken string

	// Expiry is the optional expiration time of the access token.
	//
	// If zero, TokenSource implementations will reuse the same
	// token forever and RefreshToken or equivalent
	// mechanisms for that TokenSource will not be used.
	Expiry time.Time

	// Raw optionally contains extra metadata from the server
	// when updating a token.
	Raw interface{}
}

// tokenJSON is the struct representing the HTTP response from OAuth2
// providers returning a token in JSON form.
type tokenJSON struct {
	AccessToken  string         `json:"access_token"`
	TokenType    string         `json:"token_type"`
	RefreshToken string         `json:"refresh_token"`
	ExpiresIn    expirationTime `json:"expires_in"` // at least PayPal returns string, while most return number
	Expires      expirationTime `json:"expires"`    // broken Facebook spelling of expires_in
}

func (e *tokenJSON) expiry() (t time.Time) {
	if v := e.ExpiresIn; v != 0 {
		return time.Now().Add(time.Duration(v) * time.Second)
	}
	if v := e.Expires; v != 0 {
		return time.Now().Add(time.Duration(v) * time.Second)
	}
	return
}

type expirationTime int32

func (e *expirationTime) UnmarshalJSON(b []byte) error {
	var n json.Number
	err := json.Unmarshal(b, &n)
	if err != nil {
		return err
	}
	i, err := n.Int64()
	if err != nil {
		return err
	}
	*e = expirationTime(i)
	return nil
}

var brokenAuthHeaderProviders = []string{
	"https://accounts.google.com/",
	"https://api.codeswholesale.com/oauth/token",
	"https://api.dropbox.com/",
	"https://api.dropboxapi.com/",
	"https://api.instagram.com/",
	"https://api.netatmo.net/",
	"https://api.odnoklassniki.ru/",
	"https://api.pushbullet.com/",
	"https://api.soundcloud.com/",
	"https://api.twitch.tv/",
	"https://app.box.com/",
	"https://connect.stripe.com/",
	"https://graph.facebook.com", // see https://github.com/golang/oauth2/issues/214
	"https://login.microsoftonline.com/",
	"https://login.salesforce.com/",
	"https://oauth.sandbox.trainingpeaks.com/",
	"https://oauth.trainingpeaks.com/",
	"https://oauth.vk.com/",
	"https://openapi.baidu.com/",
	"https://slack.com/",
	"https://test-sandbox.auth.corp.google.com",
	"https://test.salesforce.com/",
	"https://user.gini.net/",
	"https://www.douban.com/",
	"https://www.googleapis.com/",
	"https://www.linkedin.com/",
	"https://www.strava.com/oauth/",
	"https://www.wunderlist.com/oauth/",
	"https://api.patreon.com/",
	"https://sandbox.codeswholesale.com/oauth/token",
}

// brokenAuthHeaderDomains lists broken providers that issue dynamic endpoints.
var brokenAuthHeaderDomains = []string{
	".auth0.com",
	".force.com",
	".okta.com",
	".oktapreview.com",
}

func RegisterBrokenAuthHeaderProvider(tokenURL string) {
	brokenAuthHeaderProviders = append(brokenAuthHeaderProviders, tokenURL)
}

// providerAuthHeaderWorks reports whether the OAuth2 server identified by the tokenURL
// implements the OAuth2 spec correctly
// See https://code.google.com/p/goauth2/issues/detail?id=31 for background.
// In summary:
// - Reddit only accepts client secret in the Authorization header
// - Dropbox accepts either it in URL param or Auth header, but not both.
// - Google only accepts URL param (not spec compliant?), not Auth header
// - Stripe only accepts client secret in Auth header with Bearer method, not Basic
func providerAuthHeaderWorks(tokenURL string) bool {
	for _, s := range brokenAuthHeaderProviders {
		if strings.HasPrefix(tokenURL, s) {
			// Some sites fail to implement the OAuth2 spec fully.
			return false
		}
	}

	if u, err := url.Parse(tokenURL); err == nil {
		for _, s := range brokenAuthHeaderDomains {
			if strings.HasSuffix(u.Host, s) {
				return false
			}
		}
	}

	// Assume the provider implements the spec properly
	// otherwise. We can add more exceptions as they're
	// discovered. We will _not_ be adding configurable hooks
	// to this package to let users select server bugs.
	return true
}

func RetrieveToken(ctx context.Context, clientID, clientSecret, tokenURL string, v url.Values) (*Token, error) {
	hc, err := ContextClient(ctx)
	if err != nil {
		return nil, err
	}
	bustedAuth := !providerAuthHeaderWorks(tokenURL)
	if bustedAuth {
		if clientID != "" {
			v.Set("client_id", clientID)
		}
		if clientSecret != "" {
			v.Set("client_secret", clientSecret)
		}
	}
	req, err := http.NewRequest("POST", tokenURL, strings.NewReader(v.Encode()))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	if !bustedAuth {
		req.SetBasicAuth(clientID, clientSecret)
	}
	r, err := hc.Do(req)
	if err != nil {
		return nil, err
	}
	defer r.Body.Close()
	body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1<<20))
	if err != nil {
		return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
	}
	if code := r.StatusCode; code < 200 || code > 299 {
		return nil, fmt.Errorf("oauth2: cannot fetch token: %v\nResponse: %s", r.Status, body)
	}

	var token *Token
	content, _, _ := mime.ParseMediaType(r.Header.Get("Content-Type"))
	switch content {
	case "application/x-www-form-urlencoded", "text/plain":
		vals, err := url.ParseQuery(string(body))
		if err != nil {
			return nil, err
		}
		token = &Token{
			AccessToken:  vals.Get("access_token"),
			TokenType:    vals.Get("token_type"),
			RefreshToken: vals.Get("refresh_token"),
			Raw:          vals,
		}
		e := vals.Get("expires_in")
		if e == "" {
			// TODO(jbd): Facebook's OAuth2 implementation is broken and
			// returns expires_in field in expires. Remove the fallback to expires,
			// when Facebook fixes their implementation.
			e = vals.Get("expires")
		}
		expires, _ := strconv.Atoi(e)
		if expires != 0 {
			token.Expiry = time.Now().Add(time.Duration(expires) * time.Second)
		}
	default:
		var tj tokenJSON
		if err = json.Unmarshal(body, &tj); err != nil {
			return nil, err
		}
		token = &Token{
			AccessToken:  tj.AccessToken,
			TokenType:    tj.TokenType,
			RefreshToken: tj.RefreshToken,
			Expiry:       tj.expiry(),
			Raw:          make(map[string]interface{}),
		}
		json.Unmarshal(body, &token.Raw) // no error checks for optional fields
	}
	// Don't overwrite `RefreshToken` with an empty value
	// if this was a token refreshing request.
	if token.RefreshToken == "" {
		token.RefreshToken = v.Get("refresh_token")
	}
	return token, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2017 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 openstack

import (
	"fmt"
	"net/url"
	"time"

	"github.com/gophercloud/gophercloud/openstack"
	"github.com/gophercloud/gophercloud/openstack/blockstorage/apiversions"
	volumeexpand "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions"
	"github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes"
	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach"
	"github.com/gophercloud/gophercloud/pagination"
	"k8s.io/apimachinery/pkg/util/wait"
	cpoerrors "k8s.io/cloud-provider-openstack/pkg/util/errors"

	"k8s.io/klog/v2"
)

const (
	VolumeAvailableStatus    = "available"
	VolumeInUseStatus        = "in-use"
	VolumeDeletedStatus      = "deleted"
	VolumeErrorStatus        = "error"
	VolumeCreatingStatus     = "creating"
	operationFinishInitDelay = 1 * time.Second
	operationFinishFactor    = 1.1
	operationFinishSteps     = 10
	diskAttachInitDelay      = 1 * time.Second
	diskAttachFactor         = 1.2
	diskAttachSteps          = 15
	diskDetachInitDelay      = 1 * time.Second
	diskDetachFactor         = 1.2
	diskDetachSteps          = 13
	volumeDescription        = "Created by OpenStack Cinder CSI driver"
)

var volumeErrorStates = [...]string{"error", "error_extending", "error_deleting"}

func (os *OpenStack) CheckBlockStorageAPI() error {
	_, err := apiversions.List(os.blockstorage).AllPages()
	if err != nil {
		return err
	}
	return nil
}

// CreateVolume creates a volume of given size
func (os *OpenStack) CreateVolume(name string, size int, vtype, availability string, snapshotID string, sourcevolID string, tags *map[string]string) (*volumes.Volume, error) {

	opts := &volumes.CreateOpts{
		Name:             name,
		Size:             size,
		VolumeType:       vtype,
		AvailabilityZone: availability,
		Description:      volumeDescription,
		SnapshotID:       snapshotID,
		SourceVolID:      sourcevolID,
	}
	if tags != nil {
		opts.Metadata = *tags
	}

	vol, err := volumes.Create(os.blockstorage, opts).Extract()
	if err != nil {
		return nil, err
	}

	return vol, nil
}

// ListVolumes list all the volumes
func (os *OpenStack) ListVolumes(limit int, startingToken string) ([]volumes.Volume, string, error) {
	var nextPageToken string
	var vols []volumes.Volume

	opts := volumes.ListOpts{Limit: limit, Marker: startingToken}
	err := volumes.List(os.blockstorage, opts).EachPage(func(page pagination.Page) (bool, error) {
		var err error

		vols, err = volumes.ExtractVolumes(page)
		if err != nil {
			return false, err
		}

		nextPageURL, err := page.NextPageURL()
		if err != nil {
			return false, err
		}

		if nextPageURL != "" {
			queryParams, err := url.ParseQuery(nextPageURL)
			if err != nil {
				return false, err
			}
			nextPageToken = queryParams.Get("marker")
		}

		return false, nil
	})
	if err != nil {
		return nil, nextPageToken, err
	}

	return vols, nextPageToken, nil
}

// GetVolumesByName is a wrapper around ListVolumes that creates a Name filter to act as a GetByName
// Returns a list of Volume references with the specified name
func (os *OpenStack) GetVolumesByName(n string) ([]volumes.Volume, error) {
	opts := volumes.ListOpts{Name: n}
	pages, err := volumes.List(os.blockstorage, opts).AllPages()
	if err != nil {
		return nil, err
	}

	vols, err := volumes.ExtractVolumes(pages)
	if err != nil {
		return nil, err
	}

	return vols, nil
}

// DeleteVolume delete a volume
func (os *OpenStack) DeleteVolume(volumeID string) error {
	used, err := os.diskIsUsed(volumeID)
	if err != nil {
		return err
	}
	if used {
		return fmt.Errorf("Cannot delete the volume %q, it's still attached to a node", volumeID)
	}

	err = volumes.Delete(os.blockstorage, volumeID, nil).ExtractErr()
	return err
}

// GetVolume retrieves Volume by its ID.
func (os *OpenStack) GetVolume(volumeID string) (*volumes.Volume, error) {

	vol, err := volumes.Get(os.blockstorage, volumeID).Extract()
	if err != nil {
		return nil, err
	}

	return vol, nil
}

// AttachVolume attaches given cinder volume to the compute
func (os *OpenStack) AttachVolume(instanceID, volumeID string) (string, error) {
	computeServiceClient := os.compute

	volume, err := os.GetVolume(volumeID)
	if err != nil {
		return "", err
	}

	for _, att := range volume.Attachments {
		if instanceID == att.ServerID {
			klog.V(4).Infof("Disk %s is already attached to instance %s", volumeID, instanceID)
			return volume.ID, nil
		}
	}

	if volume.Multiattach {
		// For multiattach volumes, supported compute api version is 2.60
		// Init a local thread safe copy of the compute ServiceClient
		computeServiceClient, err = openstack.NewComputeV2(os.compute.ProviderClient, os.epOpts)
		if err != nil {
			return "", err
		}
		computeServiceClient.Microversion = "2.60"
	}

	_, err = volumeattach.Create(computeServiceClient, instanceID, &volumeattach.CreateOpts{
		VolumeID: volume.ID,
	}).Extract()

	if err != nil {
		return "", fmt.Errorf("failed to attach %s volume to %s compute: %v", volumeID, instanceID, err)
	}

	return volume.ID, nil
}

// WaitDiskAttached waits for attched
func (os *OpenStack) WaitDiskAttached(instanceID string, volumeID string) error {
	backoff := wait.Backoff{
		Duration: diskAttachInitDelay,
		Factor:   diskAttachFactor,
		Steps:    diskAttachSteps,
	}

	err := wait.ExponentialBackoff(backoff, func() (bool, error) {
		attached, err := os.diskIsAttached(instanceID, volumeID)
		if err != nil && !cpoerrors.IsNotFound(err) {
			// if this is a race condition indicate the volume is deleted
			// during sleep phase, ignore the error and return attach=false
			return false, err
		}
		return attached, nil
	})

	if err == wait.ErrWaitTimeout {
		err = fmt.Errorf("Volume %q failed to be attached within the alloted time", volumeID)
	}

	return err
}

//WaitVolumeTargetStatus waits for volume to be in target state
func (os *OpenStack) WaitVolumeTargetStatus(volumeID string, tStatus []string) error {
	backoff := wait.Backoff{
		Duration: operationFinishInitDelay,
		Factor:   operationFinishFactor,
		Steps:    operationFinishSteps,
	}

	waitErr := wait.ExponentialBackoff(backoff, func() (bool, error) {
		vol, err := os.GetVolume(volumeID)
		if err != nil {
			return false, err
		}
		for _, t := range tStatus {
			if vol.Status == t {
				return true, nil
			}
		}
		for _, eState := range volumeErrorStates {
			if vol.Status == eState {
				return false, fmt.Errorf("Volume is in Error State : %s", vol.Status)
			}
		}
		return false, nil
	})

	if waitErr == wait.ErrWaitTimeout {
		waitErr = fmt.Errorf("Timeout on waiting for volume %s status to be in %v", volumeID, tStatus)
	}

	return waitErr
}

// DetachVolume detaches given cinder volume from the compute
func (os *OpenStack) DetachVolume(instanceID, volumeID string) error {
	volume, err := os.GetVolume(volumeID)
	if err != nil {
		return err
	}
	if volume.Status == VolumeAvailableStatus {
		klog.V(2).Infof("volume: %s has been detached from compute: %s ", volume.ID, instanceID)
		return nil
	}

	if volume.Status != VolumeInUseStatus {
		return fmt.Errorf("can not detach volume %s, its status is %s", volume.Name, volume.Status)
	}

	// Incase volume is of type multiattach, it could be attached to more than one instance
	for _, att := range volume.Attachments {
		if att.ServerID == instanceID {
			err = volumeattach.Delete(os.compute, instanceID, volume.ID).ExtractErr()
			if err != nil {
				return fmt.Errorf("failed to detach volume %s from compute %s : %v", volume.ID, instanceID, err)
			}
			klog.V(2).Infof("Successfully detached volume: %s from compute: %s", volume.ID, instanceID)
			return nil
		}
	}

	return fmt.Errorf("disk: %s has no attachments or not attached to compute %s", volume.ID, instanceID)
}

// WaitDiskDetached waits for detached
func (os *OpenStack) WaitDiskDetached(instanceID string, volumeID string) error {
	backoff := wait.Backoff{
		Duration: diskDetachInitDelay,
		Factor:   diskDetachFactor,
		Steps:    diskDetachSteps,
	}

	err := wait.ExponentialBackoff(backoff, func() (bool, error) {
		attached, err := os.diskIsAttached(instanceID, volumeID)
		if err != nil {
			return false, err
		}
		return !attached, nil
	})

	if err == wait.ErrWaitTimeout {
		err = fmt.Errorf("Volume %q failed to detach within the alloted time", volumeID)
	}

	return err
}

// GetAttachmentDiskPath gets device path of attached volume to the compute
func (os *OpenStack) GetAttachmentDiskPath(instanceID, volumeID string) (string, error) {
	volume, err := os.GetVolume(volumeID)
	if err != nil {
		return "", err
	}
	if volume.Status != VolumeInUseStatus {
		return "", fmt.Errorf("can not get device path of volume %s, its status is %s ", volume.Name, volume.Status)
	}

	if len(volume.Attachments) > 0 {
		for _, att := range volume.Attachments {
			if att.ServerID == instanceID {
				return att.Device, nil
			}
		}
		return "", fmt.Errorf("disk %q is not attached to compute: %q", volumeID, instanceID)
	}
	return "", fmt.Errorf("volume %s has no Attachments", volumeID)
}

// ExpandVolume expands the volume to new size
func (os *OpenStack) ExpandVolume(volumeID string, status string, newSize int) error {
	extendOpts := volumeexpand.ExtendSizeOpts{
		NewSize: newSize,
	}

	switch status {
	case VolumeInUseStatus:
		// Init a local thread safe copy of the Cinder ServiceClient
		blockstorageClient, err := openstack.NewBlockStorageV3(os.blockstorage.ProviderClient, os.epOpts)
		if err != nil {
			return err
		}

		// cinder online resize is available since 3.42 microversion
		// https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id40
		blockstorageClient.Microversion = "3.42"

		return volumeexpand.ExtendSize(blockstorageClient, volumeID, extendOpts).ExtractErr()
	case VolumeAvailableStatus:
		return volumeexpand.ExtendSize(os.blockstorage, volumeID, extendOpts).ExtractErr()
	}

	// cinder volume can not be expanded when volume status is not volumeInUseStatus or not volumeAvailableStatus
	return fmt.Errorf("volume cannot be resized, when status is %s", status)
}

//GetMaxVolLimit returns max vol limit
func (os *OpenStack) GetMaxVolLimit() int64 {
	if os.bsOpts.NodeVolumeAttachLimit > 0 && os.bsOpts.NodeVolumeAttachLimit <= 256 {
		return os.bsOpts.NodeVolumeAttachLimit
	}

	return defaultMaxVolAttachLimit
}

// diskIsAttached queries if a volume is attached to a compute instance
func (os *OpenStack) diskIsAttached(instanceID, volumeID string) (bool, error) {
	volume, err := os.GetVolume(volumeID)
	if err != nil {
		return false, err
	}
	for _, att := range volume.Attachments {
		if att.ServerID == instanceID {
			return true, nil
		}
	}
	return false, nil
}

// diskIsUsed returns true a disk is attached to any node.
func (os *OpenStack) diskIsUsed(volumeID string) (bool, error) {
	volume, err := os.GetVolume(volumeID)
	if err != nil {
		return false, err
	}

	if len(volume.Attachments) > 0 {
		return volume.Attachments[0].ServerID != "", nil
	}

	return false, nil
}

// GetBlockStorageOpts returns OpenStack block storage options
func (os *OpenStack) GetBlockStorageOpts() BlockStorageOpts {
	return os.bsOpts
}
<commit_msg>Remove unused variables (#1484)<commit_after>/*
Copyright 2017 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 openstack

import (
	"fmt"
	"net/url"
	"time"

	"github.com/gophercloud/gophercloud/openstack"
	"github.com/gophercloud/gophercloud/openstack/blockstorage/apiversions"
	volumeexpand "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions"
	"github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes"
	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach"
	"github.com/gophercloud/gophercloud/pagination"
	"k8s.io/apimachinery/pkg/util/wait"
	cpoerrors "k8s.io/cloud-provider-openstack/pkg/util/errors"

	"k8s.io/klog/v2"
)

const (
	VolumeAvailableStatus    = "available"
	VolumeInUseStatus        = "in-use"
	operationFinishInitDelay = 1 * time.Second
	operationFinishFactor    = 1.1
	operationFinishSteps     = 10
	diskAttachInitDelay      = 1 * time.Second
	diskAttachFactor         = 1.2
	diskAttachSteps          = 15
	diskDetachInitDelay      = 1 * time.Second
	diskDetachFactor         = 1.2
	diskDetachSteps          = 13
	volumeDescription        = "Created by OpenStack Cinder CSI driver"
)

var volumeErrorStates = [...]string{"error", "error_extending", "error_deleting"}

func (os *OpenStack) CheckBlockStorageAPI() error {
	_, err := apiversions.List(os.blockstorage).AllPages()
	if err != nil {
		return err
	}
	return nil
}

// CreateVolume creates a volume of given size
func (os *OpenStack) CreateVolume(name string, size int, vtype, availability string, snapshotID string, sourcevolID string, tags *map[string]string) (*volumes.Volume, error) {

	opts := &volumes.CreateOpts{
		Name:             name,
		Size:             size,
		VolumeType:       vtype,
		AvailabilityZone: availability,
		Description:      volumeDescription,
		SnapshotID:       snapshotID,
		SourceVolID:      sourcevolID,
	}
	if tags != nil {
		opts.Metadata = *tags
	}

	vol, err := volumes.Create(os.blockstorage, opts).Extract()
	if err != nil {
		return nil, err
	}

	return vol, nil
}

// ListVolumes list all the volumes
func (os *OpenStack) ListVolumes(limit int, startingToken string) ([]volumes.Volume, string, error) {
	var nextPageToken string
	var vols []volumes.Volume

	opts := volumes.ListOpts{Limit: limit, Marker: startingToken}
	err := volumes.List(os.blockstorage, opts).EachPage(func(page pagination.Page) (bool, error) {
		var err error

		vols, err = volumes.ExtractVolumes(page)
		if err != nil {
			return false, err
		}

		nextPageURL, err := page.NextPageURL()
		if err != nil {
			return false, err
		}

		if nextPageURL != "" {
			queryParams, err := url.ParseQuery(nextPageURL)
			if err != nil {
				return false, err
			}
			nextPageToken = queryParams.Get("marker")
		}

		return false, nil
	})
	if err != nil {
		return nil, nextPageToken, err
	}

	return vols, nextPageToken, nil
}

// GetVolumesByName is a wrapper around ListVolumes that creates a Name filter to act as a GetByName
// Returns a list of Volume references with the specified name
func (os *OpenStack) GetVolumesByName(n string) ([]volumes.Volume, error) {
	opts := volumes.ListOpts{Name: n}
	pages, err := volumes.List(os.blockstorage, opts).AllPages()
	if err != nil {
		return nil, err
	}

	vols, err := volumes.ExtractVolumes(pages)
	if err != nil {
		return nil, err
	}

	return vols, nil
}

// DeleteVolume delete a volume
func (os *OpenStack) DeleteVolume(volumeID string) error {
	used, err := os.diskIsUsed(volumeID)
	if err != nil {
		return err
	}
	if used {
		return fmt.Errorf("Cannot delete the volume %q, it's still attached to a node", volumeID)
	}

	err = volumes.Delete(os.blockstorage, volumeID, nil).ExtractErr()
	return err
}

// GetVolume retrieves Volume by its ID.
func (os *OpenStack) GetVolume(volumeID string) (*volumes.Volume, error) {

	vol, err := volumes.Get(os.blockstorage, volumeID).Extract()
	if err != nil {
		return nil, err
	}

	return vol, nil
}

// AttachVolume attaches given cinder volume to the compute
func (os *OpenStack) AttachVolume(instanceID, volumeID string) (string, error) {
	computeServiceClient := os.compute

	volume, err := os.GetVolume(volumeID)
	if err != nil {
		return "", err
	}

	for _, att := range volume.Attachments {
		if instanceID == att.ServerID {
			klog.V(4).Infof("Disk %s is already attached to instance %s", volumeID, instanceID)
			return volume.ID, nil
		}
	}

	if volume.Multiattach {
		// For multiattach volumes, supported compute api version is 2.60
		// Init a local thread safe copy of the compute ServiceClient
		computeServiceClient, err = openstack.NewComputeV2(os.compute.ProviderClient, os.epOpts)
		if err != nil {
			return "", err
		}
		computeServiceClient.Microversion = "2.60"
	}

	_, err = volumeattach.Create(computeServiceClient, instanceID, &volumeattach.CreateOpts{
		VolumeID: volume.ID,
	}).Extract()

	if err != nil {
		return "", fmt.Errorf("failed to attach %s volume to %s compute: %v", volumeID, instanceID, err)
	}

	return volume.ID, nil
}

// WaitDiskAttached waits for attched
func (os *OpenStack) WaitDiskAttached(instanceID string, volumeID string) error {
	backoff := wait.Backoff{
		Duration: diskAttachInitDelay,
		Factor:   diskAttachFactor,
		Steps:    diskAttachSteps,
	}

	err := wait.ExponentialBackoff(backoff, func() (bool, error) {
		attached, err := os.diskIsAttached(instanceID, volumeID)
		if err != nil && !cpoerrors.IsNotFound(err) {
			// if this is a race condition indicate the volume is deleted
			// during sleep phase, ignore the error and return attach=false
			return false, err
		}
		return attached, nil
	})

	if err == wait.ErrWaitTimeout {
		err = fmt.Errorf("Volume %q failed to be attached within the alloted time", volumeID)
	}

	return err
}

//WaitVolumeTargetStatus waits for volume to be in target state
func (os *OpenStack) WaitVolumeTargetStatus(volumeID string, tStatus []string) error {
	backoff := wait.Backoff{
		Duration: operationFinishInitDelay,
		Factor:   operationFinishFactor,
		Steps:    operationFinishSteps,
	}

	waitErr := wait.ExponentialBackoff(backoff, func() (bool, error) {
		vol, err := os.GetVolume(volumeID)
		if err != nil {
			return false, err
		}
		for _, t := range tStatus {
			if vol.Status == t {
				return true, nil
			}
		}
		for _, eState := range volumeErrorStates {
			if vol.Status == eState {
				return false, fmt.Errorf("Volume is in Error State : %s", vol.Status)
			}
		}
		return false, nil
	})

	if waitErr == wait.ErrWaitTimeout {
		waitErr = fmt.Errorf("Timeout on waiting for volume %s status to be in %v", volumeID, tStatus)
	}

	return waitErr
}

// DetachVolume detaches given cinder volume from the compute
func (os *OpenStack) DetachVolume(instanceID, volumeID string) error {
	volume, err := os.GetVolume(volumeID)
	if err != nil {
		return err
	}
	if volume.Status == VolumeAvailableStatus {
		klog.V(2).Infof("volume: %s has been detached from compute: %s ", volume.ID, instanceID)
		return nil
	}

	if volume.Status != VolumeInUseStatus {
		return fmt.Errorf("can not detach volume %s, its status is %s", volume.Name, volume.Status)
	}

	// Incase volume is of type multiattach, it could be attached to more than one instance
	for _, att := range volume.Attachments {
		if att.ServerID == instanceID {
			err = volumeattach.Delete(os.compute, instanceID, volume.ID).ExtractErr()
			if err != nil {
				return fmt.Errorf("failed to detach volume %s from compute %s : %v", volume.ID, instanceID, err)
			}
			klog.V(2).Infof("Successfully detached volume: %s from compute: %s", volume.ID, instanceID)
			return nil
		}
	}

	return fmt.Errorf("disk: %s has no attachments or not attached to compute %s", volume.ID, instanceID)
}

// WaitDiskDetached waits for detached
func (os *OpenStack) WaitDiskDetached(instanceID string, volumeID string) error {
	backoff := wait.Backoff{
		Duration: diskDetachInitDelay,
		Factor:   diskDetachFactor,
		Steps:    diskDetachSteps,
	}

	err := wait.ExponentialBackoff(backoff, func() (bool, error) {
		attached, err := os.diskIsAttached(instanceID, volumeID)
		if err != nil {
			return false, err
		}
		return !attached, nil
	})

	if err == wait.ErrWaitTimeout {
		err = fmt.Errorf("Volume %q failed to detach within the alloted time", volumeID)
	}

	return err
}

// GetAttachmentDiskPath gets device path of attached volume to the compute
func (os *OpenStack) GetAttachmentDiskPath(instanceID, volumeID string) (string, error) {
	volume, err := os.GetVolume(volumeID)
	if err != nil {
		return "", err
	}
	if volume.Status != VolumeInUseStatus {
		return "", fmt.Errorf("can not get device path of volume %s, its status is %s ", volume.Name, volume.Status)
	}

	if len(volume.Attachments) > 0 {
		for _, att := range volume.Attachments {
			if att.ServerID == instanceID {
				return att.Device, nil
			}
		}
		return "", fmt.Errorf("disk %q is not attached to compute: %q", volumeID, instanceID)
	}
	return "", fmt.Errorf("volume %s has no Attachments", volumeID)
}

// ExpandVolume expands the volume to new size
func (os *OpenStack) ExpandVolume(volumeID string, status string, newSize int) error {
	extendOpts := volumeexpand.ExtendSizeOpts{
		NewSize: newSize,
	}

	switch status {
	case VolumeInUseStatus:
		// Init a local thread safe copy of the Cinder ServiceClient
		blockstorageClient, err := openstack.NewBlockStorageV3(os.blockstorage.ProviderClient, os.epOpts)
		if err != nil {
			return err
		}

		// cinder online resize is available since 3.42 microversion
		// https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id40
		blockstorageClient.Microversion = "3.42"

		return volumeexpand.ExtendSize(blockstorageClient, volumeID, extendOpts).ExtractErr()
	case VolumeAvailableStatus:
		return volumeexpand.ExtendSize(os.blockstorage, volumeID, extendOpts).ExtractErr()
	}

	// cinder volume can not be expanded when volume status is not volumeInUseStatus or not volumeAvailableStatus
	return fmt.Errorf("volume cannot be resized, when status is %s", status)
}

//GetMaxVolLimit returns max vol limit
func (os *OpenStack) GetMaxVolLimit() int64 {
	if os.bsOpts.NodeVolumeAttachLimit > 0 && os.bsOpts.NodeVolumeAttachLimit <= 256 {
		return os.bsOpts.NodeVolumeAttachLimit
	}

	return defaultMaxVolAttachLimit
}

// diskIsAttached queries if a volume is attached to a compute instance
func (os *OpenStack) diskIsAttached(instanceID, volumeID string) (bool, error) {
	volume, err := os.GetVolume(volumeID)
	if err != nil {
		return false, err
	}
	for _, att := range volume.Attachments {
		if att.ServerID == instanceID {
			return true, nil
		}
	}
	return false, nil
}

// diskIsUsed returns true a disk is attached to any node.
func (os *OpenStack) diskIsUsed(volumeID string) (bool, error) {
	volume, err := os.GetVolume(volumeID)
	if err != nil {
		return false, err
	}

	if len(volume.Attachments) > 0 {
		return volume.Attachments[0].ServerID != "", nil
	}

	return false, nil
}

// GetBlockStorageOpts returns OpenStack block storage options
func (os *OpenStack) GetBlockStorageOpts() BlockStorageOpts {
	return os.bsOpts
}
<|endoftext|>


<commit_before>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build darwin

package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"strings"
	"testing"

	"v.io/jiri/jiri"
	"v.io/jiri/tool"
)

var (
	checkExportedSymbols = []string{"swift_io_v_v23_V_nativeInitGlobal", "swift_io_v_v23_context_VContext_nativeWithCancel"}
	checkSharedTypes     = []string{"SwiftByteArray", "SwiftByteArrayArray", "GoContextHandle"}
)

func resetVars() {
	buildCgo = false
	buildFramework = false

	if sh != nil {
		sh.Cleanup()
	}
	sh = newShell()

	flagBuildMode = buildModeArchive
	flagBuildDirCgo = ""
	flagOutDirSwift = sh.MakeTempDir()
	flagReleaseMode = false
	flagTargetArch = targetArchAll

	targetArchs = []string{} // gets set by parseBuildFlags()
	parseBuildFlags()
}

func TestMain(m *testing.M) {
	flag.Parse()
	resetVars()

	// Ensure we have our necessary go profiles installed
	installProfiles()

	ret := m.Run()
	if sh != nil {
		sh.Cleanup()
	}
	os.Exit(ret)
}

func installProfiles() {
	targets := []string{"arm64-ios", "amd64-ios"}
	for _, target := range targets {
		sh.Cmd("jiri-v23-profile", "install", "-target="+target, "base").Run()
	}
}

func initForTest(t *testing.T) (*jiri.X, func()) {
	resetVars()

	// Capture JIRI_ROOT using a relative path.  We need the real JIRI_ROOT as
	// the real of jiri-swift needs the proper profiles installed and itself calls
	// out to jiri via GOSH.
	root, err := filepath.Abs(filepath.Join("..", "..", "..", "..", "..", "..", ".."))
	if err != nil {
		t.Fatal(err)
	}
	// Sanity check that's correct
	if _, err := os.Stat(filepath.Join(root, "release", "swift")); err != nil {
		t.Fatal("Real JIRI_ROOT was not properly set: ", root)
	}

	// Set TMPDIR to something unique, so the iOS builds do not clobber any
	// other builds running concurrently.
	oldTempDir := os.Getenv("TMPDIR")
	tempDir, err := ioutil.TempDir("", "jiri-swift-test-")
	if err != nil {
		t.Fatal(err)
	}
	if err := os.Setenv("TMPDIR", tempDir); err != nil {
		t.Fatal(err)
	}
	cleanup := func() {
		if err := os.Setenv("TMPDIR", oldTempDir); err != nil {
			t.Fatal(err)
		}
		if err := os.RemoveAll(tempDir); err != nil {
			t.Fatal(err)
		}
	}

	verboseFlag := true
	jirix := &jiri.X{Context: tool.NewContext(tool.ContextOpts{Verbose: &verboseFlag}), Root: root}
	// Clean before testing
	runClean(jirix, []string{})
	return jirix, cleanup
}

func TestParseBuildFlags(t *testing.T) {
	resetVars()
	// Test shared only working on amd64
	flagBuildMode = buildModeShared // should fail on all
	flagTargetArch = targetArchAll
	if err := parseBuildFlags(); err == nil {
		t.Errorf("Expected error in building all in shared mode")
	}
	flagTargetArch = targetArchArm
	if err := parseBuildFlags(); err == nil {
		t.Errorf("Expected error in building arm in shared mode")
	}
	flagTargetArch = targetArchArm64
	if err := parseBuildFlags(); err == nil {
		t.Errorf("Expected error in building arm64 in shared mode")
	}
	flagTargetArch = targetArchAmd64
	if err := parseBuildFlags(); err != nil {
		t.Errorf("Unexpected error setting to build amd64 in shared mode: %v", err)
	}

	flagBuildMode = buildModeArchive // should work on all
	flagTargetArch = targetArchAll
	if err := parseBuildFlags(); err != nil {
		t.Errorf("Unexpected error setting to build all in archive mode: %v", err)
	}
	flagTargetArch = targetArchArm
	if err := parseBuildFlags(); err == nil {
		t.Errorf("Expected 32-bit arm to fail as unsupported in archive mode: %v", err)
	}
	flagTargetArch = targetArchArm64
	if err := parseBuildFlags(); err != nil {
		t.Errorf("Unexpected error setting to build arm64 in archive mode: %v", err)
	}
	flagTargetArch = targetArchAmd64
	if err := parseBuildFlags(); err != nil {
		t.Errorf("Unexpected error setting to build amd64 in archive mode: %v", err)
	}
}

func TestParseBuildArgs(t *testing.T) {
	jirix, cleanup := initForTest(t)
	defer cleanup()
	// Default case -- no args
	if err := parseBuildArgs(jirix, []string{}); err != nil {
		t.Error(err)
		return
	}
	if !buildCgo || !buildFramework {
		t.Error("Default no args case didn't result in building cgo & the framework")
		return
	}
	// Cgo binary
	resetVars()
	if err := parseBuildArgs(jirix, []string{"build-cgo"}); err != nil {
		t.Error(err)
		return
	}
	if !buildCgo || buildFramework {
		t.Error("Should only build the cgo binary")
		return
	}
	// Cgo binary + framework
	resetVars()
	if err := parseBuildArgs(jirix, []string{"build-cgo", "build-framework"}); err != nil {
		t.Error(err)
		return
	}
	if !buildCgo || !buildFramework {
		t.Error("Should the cgo binary & framework")
		return
	}
	// Framework requires universal
	resetVars()
	flagTargetArch = targetArchAmd64
	if err := parseBuildArgs(jirix, []string{"build-cgo", "build-framework"}); err == nil {
		t.Error("Expected error building framework for 1 architecture")
		return
	}
}

func TestCgoBuildForSimulator64(t *testing.T) {
	jirix, cleanup := initForTest(t)
	defer cleanup()
	if err := testCgoBuildForArch(jirix, targetArchAmd64, buildModeArchive); err != nil {
		t.Error(err)
	}
	if err := testCgoBuildForArch(jirix, targetArchAmd64, buildModeShared); err != nil {
		t.Error(err)
	}
}

func TestCgoBuildForArm(t *testing.T) {
	jirix, cleanup := initForTest(t)
	defer cleanup()
	// Expect error for ARM currently as of Go 1.5
	if err := testCgoBuildForArch(jirix, targetArchArm, buildModeArchive); err == nil {
		t.Error("Expected error for building unsupported 32-bit arm")
	}
}

func TestCgoBuildForArm64(t *testing.T) {
	jirix, cleanup := initForTest(t)
	defer cleanup()
	if err := testCgoBuildForArch(jirix, targetArchArm64, buildModeArchive); err != nil {
		t.Error(err)
	}
}

func TestCgoBuildForAll(t *testing.T) {
	jirix, cleanup := initForTest(t)
	defer cleanup()
	if err := testCgoBuildForArch(jirix, targetArchAll, buildModeArchive); err != nil {
		t.Error(err)
	}
}

func testCgoBuildForArch(jirix *jiri.X, arch string, buildMode string) error {
	resetVars()
	buildCgo = true
	flagBuildMode = buildMode
	flagTargetArch = arch
	if err := parseBuildFlags(); err != nil {
		return err
	}
	if err := runBuildCgo(jirix); err != nil {
		return err
	}
	if err := verifyCgoBuild(jirix); err != nil {
		return err
	}
	return nil
}

func verifyCgoBuild(jirix *jiri.X) error {
	// Verify library exists
	for _, targetArch := range targetArchs {
		binaryPath, err := cgoBinaryPath(jirix, targetArch, flagBuildMode)
		if err != nil {
			return err
		}
		if !pathExists(binaryPath) {
			return fmt.Errorf("Could not find binary at %v", binaryPath)
		} else {
			// Verify library is built for iPhone only
			if err := verifyCgoBinaryForIOS(binaryPath); err != nil {
				return err
			}
			// Verify exported symbols are present
			if err := verifyCgoBinaryExports(binaryPath); err != nil {
				return err
			}
			// Verify target architecture
			verifyCgoBinaryArchOrPanic(binaryPath, targetArch)
		}
	}
	// Verify shared header exists
	if err := verifyCgoSharedHeaders(jirix); err != nil {
		return err
	}
	// Verify generated header (simple sanity check)
	if err := verifyCgoGeneratedHeader(jirix); err != nil {
		return err
	}
	return nil
}

func cgoBinaryPath(jirix *jiri.X, arch string, buildMode string) (string, error) {
	binaryPath := path.Join(getSwiftTargetDir(jirix), fmt.Sprintf("%v_%v", frameworkBinaryName, arch))
	switch buildMode {
	case buildModeArchive:
		binaryPath = binaryPath + ".a"
	case buildModeShared:
		binaryPath = binaryPath + ".dylib"
	default:
		return "", fmt.Errorf("Unsupported build mode %v", buildMode)
	}
	return binaryPath, nil
}

func verifyCgoBinaryForIOS(binaryPath string) error {
	stdout := sh.Cmd("otool", "-l", binaryPath).Stdout()
	if strings.Contains(stdout, "LC_VERSION_MIN_MACOSX") {
		return fmt.Errorf("Binary contains LC_VERSION_MIN_MACOSX indicating an OS-X build binary")
	}
	if !strings.Contains(stdout, "LC_VERSION_MIN_IPHONEOS") {
		return fmt.Errorf("Binary is missing LC_VERSION_MIN_IPHONEOS so it's not clear that it's built for the iOS platform")
	}
	return nil
}

// verifyCgoBinaryExports looks at the symbols in the library to look for cgo-wrapper exported functions
func verifyCgoBinaryExports(binaryPath string) error {
	stdout := sh.Cmd("otool", "-l", binaryPath).Stdout()
	// Test a couple of key functions to make sure we're getting our exports
	for _, symbol := range checkExportedSymbols {
		if !strings.Contains(stdout, symbol) {
			fmt.Errorf("Missing %v in %v export table", symbol, binaryPath)
		}
	}
	return nil
}

func verifyCgoSharedHeaders(jirix *jiri.X) error {
	goTypesPath := path.Join(getSwiftTargetDir(jirix), "go_types.h")
	if !pathExists(goTypesPath) {
		return fmt.Errorf("Missing go_types.h at %v", goTypesPath)
	}
	bytes, err := ioutil.ReadFile(goTypesPath)
	if err != nil {
		return err
	}
	goTypes := string(bytes)
	for _, typedef := range checkSharedTypes {
		if !strings.Contains(goTypes, typedef) {
			return fmt.Errorf("Missing shared typedef of %v in %v", typedef, goTypesPath)
		}
	}
	return nil
}

func verifyCgoGeneratedHeader(jirix *jiri.X) error {
	cgoExportsPath := path.Join(getSwiftTargetDir(jirix), "cgo_exports.h")
	if !pathExists(cgoExportsPath) {
		return fmt.Errorf("Missing cgo_exports.h at %v", cgoExportsPath)
	}
	bytes, err := ioutil.ReadFile(cgoExportsPath)
	if err != nil {
		return err
	}
	cgoExports := string(bytes)
	for _, symbol := range checkExportedSymbols {
		if !strings.Contains(cgoExports, symbol) {
			return fmt.Errorf("Missing symbol %v in %v", symbol, cgoExportsPath)
		}
	}
	if !strings.Contains(cgoExports, "#ifdef __LP64__") {
		return fmt.Errorf("Missing __LP64__ guard for 32/64-bit cleaness in %v", cgoExportsPath)
	}
	if strings.Count(cgoExports, "_check_for_32_bit_pointer_matching_GoInt") != 1 {
		return fmt.Errorf("32-bit check should only occur once %v", cgoExportsPath)
	}
	if strings.Count(cgoExports, "_check_for_64_bit_pointer_matching_GoInt") != 1 {
		return fmt.Errorf("64-bit check should only occur once %v", cgoExportsPath)
	}
	return nil
}

func TestUniversalFrameworkBuilds(t *testing.T) {
	jirix, cleanup := initForTest(t)
	defer cleanup()

	flagTargetArch = targetArchAll
	if err := parseBuildFlags(); err != nil {
		t.Error(err)
		return
	}
	// Make sure v23 exports exist
	if err := runBuildCgo(jirix); err != nil {
		t.Error(err)
		return
	}
	if err := runBuildFramework(jirix); err != nil {
		t.Error(err)
		return
	}
	binaryPath := filepath.Join(flagOutDirSwift, frameworkName, frameworkBinaryName)
	if err := verifyCgoBinaryForIOS(binaryPath); err != nil {
		t.Error(err)
		return
	}
	for _, targetArch := range targetArchs {
		appleArch, _ := appleArchFromGoArch(targetArch)
		sh.Cmd("lipo", binaryPath, "-verify_arch", appleArch).Run()
		if !pathExists(filepath.Join(flagOutDirSwift, frameworkName, "Modules", frameworkBinaryName+".swiftmodule", appleArch+".swiftdoc")) {
			t.Errorf("Missing swift moduledoc for architecture %v", targetArch)
		}
		if !pathExists(filepath.Join(flagOutDirSwift, frameworkName, "Modules", frameworkBinaryName+".swiftmodule", appleArch+".swiftmodule")) {
			t.Errorf("Missing swift module for architecture %v", targetArch)
		}
	}
}
<commit_msg>TBR: Revert "jiri-swift: Set TMPDIR before running swift tests."<commit_after>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build darwin

package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"strings"
	"testing"

	"v.io/jiri/jiri"
	"v.io/jiri/tool"
)

var (
	checkExportedSymbols = []string{"swift_io_v_v23_V_nativeInitGlobal", "swift_io_v_v23_context_VContext_nativeWithCancel"}
	checkSharedTypes     = []string{"SwiftByteArray", "SwiftByteArrayArray", "GoContextHandle"}
)

func resetVars() {
	buildCgo = false
	buildFramework = false

	if sh != nil {
		sh.Cleanup()
	}
	sh = newShell()

	flagBuildMode = buildModeArchive
	flagBuildDirCgo = ""
	flagOutDirSwift = sh.MakeTempDir()
	flagReleaseMode = false
	flagTargetArch = targetArchAll

	targetArchs = []string{} // gets set by parseBuildFlags()
	parseBuildFlags()
}

func TestMain(m *testing.M) {
	flag.Parse()
	resetVars()

	// Ensure we have our necessary go profiles installed
	installProfiles()

	ret := m.Run()
	if sh != nil {
		sh.Cleanup()
	}
	os.Exit(ret)
}

func installProfiles() {
	targets := []string{"arm64-ios", "amd64-ios"}
	for _, target := range targets {
		sh.Cmd("jiri-v23-profile", "install", "-target="+target, "base").Run()
	}
}

func initForTest(t *testing.T) *jiri.X {
	resetVars()

	// Capture JIRI_ROOT using a relative path.  We need the real JIRI_ROOT as
	// the real of jiri-swift needs the proper profiles installed and itself calls
	// out to jiri via GOSH.
	root, err := filepath.Abs(filepath.Join("..", "..", "..", "..", "..", "..", ".."))
	if err != nil {
		t.Fatal(err)
	}
	// Sanity check that's correct
	if _, err := os.Stat(filepath.Join(root, "release", "swift")); err != nil {
		t.Fatal("Real JIRI_ROOT was not properly set: ", root)
	}

	verboseFlag := true
	jirix := &jiri.X{Context: tool.NewContext(tool.ContextOpts{Verbose: &verboseFlag}), Root: root}
	// Clean before testing
	runClean(jirix, []string{})
	return jirix
}

func TestParseBuildFlags(t *testing.T) {
	resetVars()
	// Test shared only working on amd64
	flagBuildMode = buildModeShared // should fail on all
	flagTargetArch = targetArchAll
	if err := parseBuildFlags(); err == nil {
		t.Errorf("Expected error in building all in shared mode")
	}
	flagTargetArch = targetArchArm
	if err := parseBuildFlags(); err == nil {
		t.Errorf("Expected error in building arm in shared mode")
	}
	flagTargetArch = targetArchArm64
	if err := parseBuildFlags(); err == nil {
		t.Errorf("Expected error in building arm64 in shared mode")
	}
	flagTargetArch = targetArchAmd64
	if err := parseBuildFlags(); err != nil {
		t.Errorf("Unexpected error setting to build amd64 in shared mode: %v", err)
	}

	flagBuildMode = buildModeArchive // should work on all
	flagTargetArch = targetArchAll
	if err := parseBuildFlags(); err != nil {
		t.Errorf("Unexpected error setting to build all in archive mode: %v", err)
	}
	flagTargetArch = targetArchArm
	if err := parseBuildFlags(); err == nil {
		t.Errorf("Expected 32-bit arm to fail as unsupported in archive mode: %v", err)
	}
	flagTargetArch = targetArchArm64
	if err := parseBuildFlags(); err != nil {
		t.Errorf("Unexpected error setting to build arm64 in archive mode: %v", err)
	}
	flagTargetArch = targetArchAmd64
	if err := parseBuildFlags(); err != nil {
		t.Errorf("Unexpected error setting to build amd64 in archive mode: %v", err)
	}
}

func TestParseBuildArgs(t *testing.T) {
	jirix := initForTest(t)
	// Default case -- no args
	if err := parseBuildArgs(jirix, []string{}); err != nil {
		t.Error(err)
		return
	}
	if !buildCgo || !buildFramework {
		t.Error("Default no args case didn't result in building cgo & the framework")
		return
	}
	// Cgo binary
	resetVars()
	if err := parseBuildArgs(jirix, []string{"build-cgo"}); err != nil {
		t.Error(err)
		return
	}
	if !buildCgo || buildFramework {
		t.Error("Should only build the cgo binary")
		return
	}
	// Cgo binary + framework
	resetVars()
	if err := parseBuildArgs(jirix, []string{"build-cgo", "build-framework"}); err != nil {
		t.Error(err)
		return
	}
	if !buildCgo || !buildFramework {
		t.Error("Should the cgo binary & framework")
		return
	}
	// Framework requires universal
	resetVars()
	flagTargetArch = targetArchAmd64
	if err := parseBuildArgs(jirix, []string{"build-cgo", "build-framework"}); err == nil {
		t.Error("Expected error building framework for 1 architecture")
		return
	}
}

func TestCgoBuildForSimulator64(t *testing.T) {
	jirix := initForTest(t)
	if err := testCgoBuildForArch(jirix, targetArchAmd64, buildModeArchive); err != nil {
		t.Error(err)
	}
	if err := testCgoBuildForArch(jirix, targetArchAmd64, buildModeShared); err != nil {
		t.Error(err)
	}
}

func TestCgoBuildForArm(t *testing.T) {
	jirix := initForTest(t)
	// Expect error for ARM currently as of Go 1.5
	if err := testCgoBuildForArch(jirix, targetArchArm, buildModeArchive); err == nil {
		t.Error("Expected error for building unsupported 32-bit arm")
	}
}

func TestCgoBuildForArm64(t *testing.T) {
	jirix := initForTest(t)
	if err := testCgoBuildForArch(jirix, targetArchArm64, buildModeArchive); err != nil {
		t.Error(err)
	}
}

func TestCgoBuildForAll(t *testing.T) {
	jirix := initForTest(t)
	if err := testCgoBuildForArch(jirix, targetArchAll, buildModeArchive); err != nil {
		t.Error(err)
	}
}

func testCgoBuildForArch(jirix *jiri.X, arch string, buildMode string) error {
	resetVars()
	buildCgo = true
	flagBuildMode = buildMode
	flagTargetArch = arch
	if err := parseBuildFlags(); err != nil {
		return err
	}
	if err := runBuildCgo(jirix); err != nil {
		return err
	}
	if err := verifyCgoBuild(jirix); err != nil {
		return err
	}
	return nil
}

func verifyCgoBuild(jirix *jiri.X) error {
	// Verify library exists
	for _, targetArch := range targetArchs {
		binaryPath, err := cgoBinaryPath(jirix, targetArch, flagBuildMode)
		if err != nil {
			return err
		}
		if !pathExists(binaryPath) {
			return fmt.Errorf("Could not find binary at %v", binaryPath)
		} else {
			// Verify library is built for iPhone only
			if err := verifyCgoBinaryForIOS(binaryPath); err != nil {
				return err
			}
			// Verify exported symbols are present
			if err := verifyCgoBinaryExports(binaryPath); err != nil {
				return err
			}
			// Verify target architecture
			verifyCgoBinaryArchOrPanic(binaryPath, targetArch)
		}
	}
	// Verify shared header exists
	if err := verifyCgoSharedHeaders(jirix); err != nil {
		return err
	}
	// Verify generated header (simple sanity check)
	if err := verifyCgoGeneratedHeader(jirix); err != nil {
		return err
	}
	return nil
}

func cgoBinaryPath(jirix *jiri.X, arch string, buildMode string) (string, error) {
	binaryPath := path.Join(getSwiftTargetDir(jirix), fmt.Sprintf("%v_%v", frameworkBinaryName, arch))
	switch buildMode {
	case buildModeArchive:
		binaryPath = binaryPath + ".a"
	case buildModeShared:
		binaryPath = binaryPath + ".dylib"
	default:
		return "", fmt.Errorf("Unsupported build mode %v", buildMode)
	}
	return binaryPath, nil
}

func verifyCgoBinaryForIOS(binaryPath string) error {
	stdout := sh.Cmd("otool", "-l", binaryPath).Stdout()
	if strings.Contains(stdout, "LC_VERSION_MIN_MACOSX") {
		return fmt.Errorf("Binary contains LC_VERSION_MIN_MACOSX indicating an OS-X build binary")
	}
	if !strings.Contains(stdout, "LC_VERSION_MIN_IPHONEOS") {
		return fmt.Errorf("Binary is missing LC_VERSION_MIN_IPHONEOS so it's not clear that it's built for the iOS platform")
	}
	return nil
}

// verifyCgoBinaryExports looks at the symbols in the library to look for cgo-wrapper exported functions
func verifyCgoBinaryExports(binaryPath string) error {
	stdout := sh.Cmd("otool", "-l", binaryPath).Stdout()
	// Test a couple of key functions to make sure we're getting our exports
	for _, symbol := range checkExportedSymbols {
		if !strings.Contains(stdout, symbol) {
			fmt.Errorf("Missing %v in %v export table", symbol, binaryPath)
		}
	}
	return nil
}

func verifyCgoSharedHeaders(jirix *jiri.X) error {
	goTypesPath := path.Join(getSwiftTargetDir(jirix), "go_types.h")
	if !pathExists(goTypesPath) {
		return fmt.Errorf("Missing go_types.h at %v", goTypesPath)
	}
	bytes, err := ioutil.ReadFile(goTypesPath)
	if err != nil {
		return err
	}
	goTypes := string(bytes)
	for _, typedef := range checkSharedTypes {
		if !strings.Contains(goTypes, typedef) {
			return fmt.Errorf("Missing shared typedef of %v in %v", typedef, goTypesPath)
		}
	}
	return nil
}

func verifyCgoGeneratedHeader(jirix *jiri.X) error {
	cgoExportsPath := path.Join(getSwiftTargetDir(jirix), "cgo_exports.h")
	if !pathExists(cgoExportsPath) {
		return fmt.Errorf("Missing cgo_exports.h at %v", cgoExportsPath)
	}
	bytes, err := ioutil.ReadFile(cgoExportsPath)
	if err != nil {
		return err
	}
	cgoExports := string(bytes)
	for _, symbol := range checkExportedSymbols {
		if !strings.Contains(cgoExports, symbol) {
			return fmt.Errorf("Missing symbol %v in %v", symbol, cgoExportsPath)
		}
	}
	if !strings.Contains(cgoExports, "#ifdef __LP64__") {
		return fmt.Errorf("Missing __LP64__ guard for 32/64-bit cleaness in %v", cgoExportsPath)
	}
	if strings.Count(cgoExports, "_check_for_32_bit_pointer_matching_GoInt") != 1 {
		return fmt.Errorf("32-bit check should only occur once %v", cgoExportsPath)
	}
	if strings.Count(cgoExports, "_check_for_64_bit_pointer_matching_GoInt") != 1 {
		return fmt.Errorf("64-bit check should only occur once %v", cgoExportsPath)
	}
	return nil
}

func TestUniversalFrameworkBuilds(t *testing.T) {
	jirix := initForTest(t)
	flagTargetArch = targetArchAll
	if err := parseBuildFlags(); err != nil {
		t.Error(err)
		return
	}
	// Make sure v23 exports exist
	if err := runBuildCgo(jirix); err != nil {
		t.Error(err)
		return
	}
	if err := runBuildFramework(jirix); err != nil {
		t.Error(err)
		return
	}
	binaryPath := filepath.Join(flagOutDirSwift, frameworkName, frameworkBinaryName)
	if err := verifyCgoBinaryForIOS(binaryPath); err != nil {
		t.Error(err)
		return
	}
	for _, targetArch := range targetArchs {
		appleArch, _ := appleArchFromGoArch(targetArch)
		sh.Cmd("lipo", binaryPath, "-verify_arch", appleArch).Run()
		if !pathExists(filepath.Join(flagOutDirSwift, frameworkName, "Modules", frameworkBinaryName+".swiftmodule", appleArch+".swiftdoc")) {
			t.Errorf("Missing swift moduledoc for architecture %v", targetArch)
		}
		if !pathExists(filepath.Join(flagOutDirSwift, frameworkName, "Modules", frameworkBinaryName+".swiftmodule", appleArch+".swiftmodule")) {
			t.Errorf("Missing swift module for architecture %v", targetArch)
		}
	}
}
<|endoftext|>


<commit_before>package session

import (
	"context"
	"errors"
	"net/http"
	"time"

	"github.com/herb-go/herb/cache"
)

var (
	//ErrTokenNotValidated raised when the given token is not validated(for example: token is empty string)
	ErrTokenNotValidated = errors.New("Token not validated")
	//ErrRequestTokenNotFound raised when token is not found in context.You should use cookiemiddle or headermiddle or your our function to install the token.
	ErrRequestTokenNotFound = errors.New("Request token not found.Did you forget use middleware?")
	//ErrMustRegistePtr raised when the registerd interface is not a poioter to struct.
	ErrMustRegistePtr = errors.New("Must registe struct pointer")
	//ErrFeatureNotSupported raised when fearture is not supoprted.
	ErrFeatureNotSupported = errors.New("Feature is not supported")
)

//ContextKey string type used in Context key
type ContextKey string

var defaultTokenContextName = ContextKey("token")

//MustRegisterField registe filed to store.
//registered field can be used directly with request to load or save the token Session.
//Parameter Key filed name.
//Parameter v should be pointer to empty data model which data filled in.
//Return a new Token field.
//Panic if any error raised.
// func MustRegisterField(s Store, Key string, v interface{}) *TokenField {
// 	tf, err := s.RegisterField(Key, v)
// 	if err != nil {
// 		panic(err)
// 	}
// 	return tf
// }

//Store Basic token storce interface
type Driver interface {
	GetSessionToken(ts *Session) (token string, err error)
	GenerateToken(owner string) (token string, err error)
	Load(v *Session) error
	Save(t *Session, ttl time.Duration) error
	Delete(token string) error
	Close() error
}
type Store struct {
	Driver               Driver
	TokenLifetime        time.Duration //Token initial expired time.Token life time can be update when accessed if UpdateActiveInterval is greater than 0.
	TokenMaxLifetime     time.Duration //Token max life time.Token can't live more than TokenMaxLifetime if TokenMaxLifetime if greater than 0.
	TokenContextName     ContextKey    //Name in request context store the token  data.Default Session is "token".
	CookieName           string        //Cookie name used in CookieMiddleware.Default Session is "herb-session".
	CookiePath           string        //Cookie path used in cookieMiddleware.Default Session is "/".
	CookieSecure         bool          //Cookie secure value used in cookie middleware.
	AutoGenerate         bool          //Whether auto generate token when guset visit.Default Session is false.
	UpdateActiveInterval time.Duration //The interval between who token active time update.If less than or equal to 0,the token life time will not be refreshed.
	DefaultSessionFlag   Flag          //Default flag when creating session.
}

func New() *Store {
	return &Store{
		TokenContextName:     defaultTokenContextName,
		CookieName:           defaultCookieName,
		CookiePath:           defaultCookiePath,
		UpdateActiveInterval: defaultUpdateActiveInterval,
		TokenMaxLifetime:     defaultTokenMaxLifetime,
	}
}

func (s *Store) Init(option Option) error {
	return option.ApplyTo(s)
}

//Close Close cachestore and return any error if raised
func (s *Store) Close() error {
	return s.Driver.Close()
}

//GenerateToken generate new token name with given prefix.
//Return the new token name and error.
func (s *Store) GenerateToken(prefix string) (token string, err error) {
	return s.Driver.GenerateToken(prefix)
}

//GenerateSession generate new token data with given token.
//Return a new Session and error.
func (s *Store) GenerateSession(token string) (ts *Session, err error) {
	ts = NewSession(token, s)
	ts.tokenChanged = true
	return ts, nil
}

//LoadSession Load Session form the Session.token.
//Return any error if raised
func (s *Store) LoadSession(v *Session) error {
	token := v.token
	if token == "" {
		return cache.ErrKeyUnavailable
	}
	err := s.Driver.Load(v)
	if err != nil {
		return err
	}
	v.Store = s
	if v.ExpiredAt > 0 && v.ExpiredAt < time.Now().Unix() {
		return ErrDataNotFound
	}
	if s.TokenMaxLifetime > 0 && time.Unix(v.CreatedTime, 0).Add(s.TokenMaxLifetime).Before(time.Now()) {
		return ErrDataNotFound
	}
	return err
}

//SaveSession Save Session if necessary.
//Return any error raised.
func (s *Store) SaveSession(t *Session) error {
	if s.UpdateActiveInterval > 0 {
		nextUpdateTime := time.Unix(t.LastActiveTime, 0).Add(s.UpdateActiveInterval)
		if nextUpdateTime.Before(time.Now()) {
			t.LastActiveTime = time.Now().Unix()
			t.updated = true
		}
	}
	if t.updated && t.token != "" {
		err := s.save(t)
		if err != nil {
			return err
		}
		t.updated = false
	}
	if t.tokenChanged && t.oldToken != "" {
		err := s.DeleteToken(t.oldToken)
		if err != nil {
			return err
		}
	}
	t.oldToken = t.token
	return nil
}
func (s *Store) save(ts *Session) error {
	token := ts.token
	if token == "" {
		return cache.ErrKeyUnavailable
	}
	if ts.ExpiredAt > 0 && ts.ExpiredAt < time.Now().Unix() {
		return nil
	}
	if s.TokenMaxLifetime > 0 && time.Unix(ts.CreatedTime, 0).Add(s.TokenMaxLifetime).Before(time.Now()) {
		return nil
	}
	if s.TokenLifetime >= 0 {
		ts.ExpiredAt = time.Now().Add(s.TokenLifetime).Unix()
	} else {
		ts.ExpiredAt = -1
	}

	err := s.Driver.Save(ts, s.TokenLifetime)
	if err != nil {
		return err
	}
	ts.loaded = true
	return nil
}

//DeleteToken delete the token with given name.
//Return any error if raised.
func (s *Store) DeleteToken(token string) error {

	return s.Driver.Delete(token)
}

//GetSession get the token data with give token .
//Return the Session
func (s *Store) GetSession(token string) (ts *Session) {
	ts = NewSession(token, s)
	ts.oldToken = token
	return
}

//GetSessionToken Get the token string from token data.
//Return token and any error raised.
func (s *Store) GetSessionToken(ts *Session) (token string, err error) {
	return s.Driver.GetSessionToken(ts)
}
func (s *Store) MustGetSessionToken(ts *Session) (token string) {
	token, err := s.Driver.GetSessionToken(ts)
	if err != nil {
		panic(err)
	}
	return
}
func (s *Store) RegenerateToken(prefix string) (ts *Session, err error) {
	ts = NewSession("", s)
	err = ts.RegenerateToken(prefix)
	return
}

//Install install the give token to request.
//Session will be stored in request context which named by TokenContextName of store.
//You should use this func when use your own token binding func instead of CookieMiddleware or HeaderMiddleware
//Return the loaded Session and any error raised.
func (s *Store) Install(r *http.Request, token string) (ts *Session, err error) {
	ts = s.GetSession(token)

	if (token == "" || token == clientStoreNewToken) && s.AutoGenerate == true {
		err = ts.RegenerateToken("")
		if err != nil {
			return
		}
	}

	ctx := context.WithValue(r.Context(), s.TokenContextName, ts)
	*r = *r.WithContext(ctx)
	return
}

func (s *Store) AutoGenerateMiddleware() func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
		var ts = s.MustGetRequestSession(r)
		if ts.token == "" || ts.token == clientStoreNewToken {
			err := ts.RegenerateToken("")
			if err != nil {
				return
			}
			ctx := context.WithValue(r.Context(), s.TokenContextName, ts)
			*r = *r.WithContext(ctx)
		}
		next(w, r)
	}
}

//CookieMiddleware return a Middleware which install the token which special by cookie.
//This middleware will save token after request finished if the token changed,and update cookie if necessary.
func (s *Store) CookieMiddleware() func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
		var token string
		cookie, err := r.Cookie(s.CookieName)
		if err == http.ErrNoCookie {
			err = nil
			token = ""
		} else if err != nil {
			panic(err)
		} else {
			token = cookie.Value
		}
		_, err = s.Install(r, token)
		if err != nil {
			panic(err)
		}
		cw := cookieResponseWriter{
			ResponseWriter: w,
			r:              r,
			store:          s,
			written:        false,
		}
		next(&cw, r)
	}
}

//HeaderMiddleware return a Middleware which install the token which special by Header with given name.
//this middleware will save token after request finished if the token changed.
func (s *Store) HeaderMiddleware(Name string) func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
		var token = r.Header.Get(Name)
		_, err := s.Install(r, token)
		if err != nil {
			panic(err)
		}
		next(w, r)
		err = s.SaveRequestSession(r)
		if err != nil {
			panic(err)
		}
	}
}

//GetRequestSession get stored  token data from request.
//Return the stoed token data and any error raised.
func (s *Store) GetRequestSession(r *http.Request) (ts *Session, err error) {
	var ok bool
	t := r.Context().Value(s.TokenContextName)
	if t != nil {
		ts, ok = t.(*Session)
		if ok == false {
			return ts, ErrDataTypeWrong
		}
		return ts, nil
	}
	return ts, ErrRequestTokenNotFound
}
func (s *Store) MustRegenerateRequsetSession(r *http.Request, prefix string) (ts *Session) {
	var err error
	ts, err = s.GetRequestSession(r)
	if err != nil {
		panic(err)
	}
	err = ts.RegenerateToken(prefix)
	if err != nil {
		panic(err)
	}
	return ts
}
func (s *Store) Set(r *http.Request, fieldName string, v interface{}) (err error) {
	ts, err := s.GetRequestSession(r)
	if err != nil {
		return
	}
	return ts.Set(fieldName, v)
}

func (s *Store) Get(r *http.Request, fieldName string, v interface{}) (err error) {
	ts, err := s.GetRequestSession(r)
	if err != nil {
		return
	}
	return ts.Get(fieldName, v)
}

func (s *Store) Del(r *http.Request, fieldName string) (err error) {
	ts, err := s.GetRequestSession(r)
	if err != nil {
		return
	}
	return ts.Del(fieldName)
}
func (s *Store) ExpiredAt(r *http.Request) (ExpiredAt int64, err error) {
	ts, err := s.GetRequestSession(r)
	if err != nil {
		return
	}
	return ts.ExpiredAt, nil
}

func (s *Store) Field(name string) *Field {
	return &Field{Name: name, Store: s}
}

//SaveRequestSession save the request token data.
func (s *Store) SaveRequestSession(r *http.Request) error {
	ts, err := s.GetRequestSession(r)
	if err != nil {
		return err
	}
	err = ts.Save()
	return err
}

//DestoryMiddleware return a middleware clear the token in request.
func (s *Store) DestoryMiddleware() func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
		v := s.MustGetRequestSession(r)
		v.SetToken("")
		next(w, r)
	}
}

//MustGetRequestSession get stored  token data from request.
//Panic if any error raised.
func (s Store) MustGetRequestSession(r *http.Request) (v *Session) {
	v, err := s.GetRequestSession(r)
	if err != nil {
		panic(err)
	}
	return v
}

//MustRegenerateRequestToken Regenerate the token name and data with give owner,and save to request.
//Panic if any error raised.
func (s Store) MustRegenerateRequestToken(r *http.Request, owner string) *Session {
	v := s.MustGetRequestSession(r)
	err := v.RegenerateToken(owner)
	if err != nil {
		panic(err)
	}
	return v
}

func (s Store) IsNotFound(err error) bool {
	return err == ErrDataNotFound
}
<commit_msg>update<commit_after>package session

import (
	"context"
	"errors"
	"net/http"
	"time"

	"github.com/herb-go/herb/cache"
)

var (
	//ErrTokenNotValidated raised when the given token is not validated(for example: token is empty string)
	ErrTokenNotValidated = errors.New("Token not validated")
	//ErrRequestTokenNotFound raised when token is not found in context.You should use cookiemiddle or headermiddle or your our function to install the token.
	ErrRequestTokenNotFound = errors.New("Request token not found.Did you forget use middleware?")
	//ErrMustRegistePtr raised when the registerd interface is not a poioter to struct.
	ErrMustRegistePtr = errors.New("Must registe struct pointer")
	//ErrFeatureNotSupported raised when fearture is not supoprted.
	ErrFeatureNotSupported = errors.New("Feature is not supported")
)

//ContextKey string type used in Context key
type ContextKey string

var defaultTokenContextName = ContextKey("token")

const (
	//StoreModeCookie which store session token in cookie.
	StoreModeCookie = "cookie"
	//StoreModeHeader which store session token in header.
	StoreModeHeader = "header"
)

//Driver store driver.
type Driver interface {
	GetSessionToken(ts *Session) (token string, err error)
	GenerateToken(owner string) (token string, err error)
	Load(v *Session) error
	Save(t *Session, ttl time.Duration) error
	Delete(token string) error
	Close() error
}

//Store Basic token store interface
type Store struct {
	Driver               Driver
	TokenLifetime        time.Duration //Token initial expired time.Token life time can be update when accessed if UpdateActiveInterval is greater than 0.
	TokenMaxLifetime     time.Duration //Token max life time.Token can't live more than TokenMaxLifetime if TokenMaxLifetime if greater than 0.
	TokenContextName     ContextKey    //Name in request context store the token  data.Default Session is "token".
	CookieName           string        //Cookie name used in CookieMiddleware.Default Session is "herb-session".
	CookiePath           string        //Cookie path used in cookieMiddleware.Default Session is "/".
	CookieSecure         bool          //Cookie secure value used in cookie middleware.
	AutoGenerate         bool          //Whether auto generate token when guset visit.Default Session is false.
	Mode                 string        //Mode used in auto install middleware.
	UpdateActiveInterval time.Duration //The interval between who token active time update.If less than or equal to 0,the token life time will not be refreshed.
	DefaultSessionFlag   Flag          //Default flag when creating session.
}

func New() *Store {
	return &Store{
		TokenContextName:     defaultTokenContextName,
		CookieName:           defaultCookieName,
		CookiePath:           defaultCookiePath,
		UpdateActiveInterval: defaultUpdateActiveInterval,
		TokenMaxLifetime:     defaultTokenMaxLifetime,
	}
}

func (s *Store) Init(option Option) error {
	return option.ApplyTo(s)
}

//Close Close cachestore and return any error if raised
func (s *Store) Close() error {
	return s.Driver.Close()
}

//GenerateToken generate new token name with given prefix.
//Return the new token name and error.
func (s *Store) GenerateToken(prefix string) (token string, err error) {
	return s.Driver.GenerateToken(prefix)
}

//GenerateSession generate new token data with given token.
//Return a new Session and error.
func (s *Store) GenerateSession(token string) (ts *Session, err error) {
	ts = NewSession(token, s)
	ts.tokenChanged = true
	return ts, nil
}

//LoadSession Load Session form the Session.token.
//Return any error if raised
func (s *Store) LoadSession(v *Session) error {
	token := v.token
	if token == "" {
		return cache.ErrKeyUnavailable
	}
	err := s.Driver.Load(v)
	if err != nil {
		return err
	}
	v.Store = s
	if v.ExpiredAt > 0 && v.ExpiredAt < time.Now().Unix() {
		return ErrDataNotFound
	}
	if s.TokenMaxLifetime > 0 && time.Unix(v.CreatedTime, 0).Add(s.TokenMaxLifetime).Before(time.Now()) {
		return ErrDataNotFound
	}
	return err
}

//SaveSession Save Session if necessary.
//Return any error raised.
func (s *Store) SaveSession(t *Session) error {
	if s.UpdateActiveInterval > 0 {
		nextUpdateTime := time.Unix(t.LastActiveTime, 0).Add(s.UpdateActiveInterval)
		if nextUpdateTime.Before(time.Now()) {
			t.LastActiveTime = time.Now().Unix()
			t.updated = true
		}
	}
	if t.updated && t.token != "" {
		err := s.save(t)
		if err != nil {
			return err
		}
		t.updated = false
	}
	if t.tokenChanged && t.oldToken != "" {
		err := s.DeleteToken(t.oldToken)
		if err != nil {
			return err
		}
	}
	t.oldToken = t.token
	return nil
}
func (s *Store) save(ts *Session) error {
	token := ts.token
	if token == "" {
		return cache.ErrKeyUnavailable
	}
	if ts.ExpiredAt > 0 && ts.ExpiredAt < time.Now().Unix() {
		return nil
	}
	if s.TokenMaxLifetime > 0 && time.Unix(ts.CreatedTime, 0).Add(s.TokenMaxLifetime).Before(time.Now()) {
		return nil
	}
	if s.TokenLifetime >= 0 {
		ts.ExpiredAt = time.Now().Add(s.TokenLifetime).Unix()
	} else {
		ts.ExpiredAt = -1
	}

	err := s.Driver.Save(ts, s.TokenLifetime)
	if err != nil {
		return err
	}
	ts.loaded = true
	return nil
}

//DeleteToken delete the token with given name.
//Return any error if raised.
func (s *Store) DeleteToken(token string) error {

	return s.Driver.Delete(token)
}

//GetSession get the token data with give token .
//Return the Session
func (s *Store) GetSession(token string) (ts *Session) {
	ts = NewSession(token, s)
	ts.oldToken = token
	return
}

//GetSessionToken Get the token string from token data.
//Return token and any error raised.
func (s *Store) GetSessionToken(ts *Session) (token string, err error) {
	return s.Driver.GetSessionToken(ts)
}
func (s *Store) MustGetSessionToken(ts *Session) (token string) {
	token, err := s.Driver.GetSessionToken(ts)
	if err != nil {
		panic(err)
	}
	return
}
func (s *Store) RegenerateToken(prefix string) (ts *Session, err error) {
	ts = NewSession("", s)
	err = ts.RegenerateToken(prefix)
	return
}

//Install install the give token to request.
//Session will be stored in request context which named by TokenContextName of store.
//You should use this func when use your own token binding func instead of CookieMiddleware or HeaderMiddleware
//Return the loaded Session and any error raised.
func (s *Store) Install(r *http.Request, token string) (ts *Session, err error) {
	ts = s.GetSession(token)

	if (token == "" || token == clientStoreNewToken) && s.AutoGenerate == true {
		err = ts.RegenerateToken("")
		if err != nil {
			return
		}
	}

	ctx := context.WithValue(r.Context(), s.TokenContextName, ts)
	*r = *r.WithContext(ctx)
	return
}

func (s *Store) AutoGenerateMiddleware() func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
		var ts = s.MustGetRequestSession(r)
		if ts.token == "" || ts.token == clientStoreNewToken {
			err := ts.RegenerateToken("")
			if err != nil {
				return
			}
			ctx := context.WithValue(r.Context(), s.TokenContextName, ts)
			*r = *r.WithContext(ctx)
		}
		next(w, r)
	}
}

//CookieMiddleware return a Middleware which install the token which special by cookie.
//This middleware will save token after request finished if the token changed,and update cookie if necessary.
func (s *Store) CookieMiddleware() func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
		var token string
		cookie, err := r.Cookie(s.CookieName)
		if err == http.ErrNoCookie {
			err = nil
			token = ""
		} else if err != nil {
			panic(err)
		} else {
			token = cookie.Value
		}
		_, err = s.Install(r, token)
		if err != nil {
			panic(err)
		}
		cw := cookieResponseWriter{
			ResponseWriter: w,
			r:              r,
			store:          s,
			written:        false,
		}
		next(&cw, r)
	}
}

//HeaderMiddleware return a Middleware which install the token which special by Header with given name.
//This middleware will save token after request finished if the token changed.
func (s *Store) HeaderMiddleware(Name string) func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
		var token = r.Header.Get(Name)
		_, err := s.Install(r, token)
		if err != nil {
			panic(err)
		}
		next(w, r)
		err = s.SaveRequestSession(r)
		if err != nil {
			panic(err)
		}
	}
}

//InstallMiddleware middleware which auto install session depand on store mode.
//Cookie middleware will be installed if no valid store mode given.
func (s *Store) InstallMiddleware() func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	switch s.Mode {
	case StoreModeHeader:
		return s.HeaderMiddleware(s.CookieName)
	}
	return s.CookieMiddleware()
}

//GetRequestSession get stored  token data from request.
//Return the stoed token data and any error raised.
func (s *Store) GetRequestSession(r *http.Request) (ts *Session, err error) {
	var ok bool
	t := r.Context().Value(s.TokenContextName)
	if t != nil {
		ts, ok = t.(*Session)
		if ok == false {
			return ts, ErrDataTypeWrong
		}
		return ts, nil
	}
	return ts, ErrRequestTokenNotFound
}
func (s *Store) MustRegenerateRequsetSession(r *http.Request, prefix string) (ts *Session) {
	var err error
	ts, err = s.GetRequestSession(r)
	if err != nil {
		panic(err)
	}
	err = ts.RegenerateToken(prefix)
	if err != nil {
		panic(err)
	}
	return ts
}
func (s *Store) Set(r *http.Request, fieldName string, v interface{}) (err error) {
	ts, err := s.GetRequestSession(r)
	if err != nil {
		return
	}
	return ts.Set(fieldName, v)
}

func (s *Store) Get(r *http.Request, fieldName string, v interface{}) (err error) {
	ts, err := s.GetRequestSession(r)
	if err != nil {
		return
	}
	return ts.Get(fieldName, v)
}

func (s *Store) Del(r *http.Request, fieldName string) (err error) {
	ts, err := s.GetRequestSession(r)
	if err != nil {
		return
	}
	return ts.Del(fieldName)
}
func (s *Store) ExpiredAt(r *http.Request) (ExpiredAt int64, err error) {
	ts, err := s.GetRequestSession(r)
	if err != nil {
		return
	}
	return ts.ExpiredAt, nil
}

func (s *Store) Field(name string) *Field {
	return &Field{Name: name, Store: s}
}

//SaveRequestSession save the request token data.
func (s *Store) SaveRequestSession(r *http.Request) error {
	ts, err := s.GetRequestSession(r)
	if err != nil {
		return err
	}
	err = ts.Save()
	return err
}

//DestoryMiddleware return a middleware clear the token in request.
func (s *Store) DestoryMiddleware() func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
		v := s.MustGetRequestSession(r)
		v.SetToken("")
		next(w, r)
	}
}

//MustGetRequestSession get stored  token data from request.
//Panic if any error raised.
func (s Store) MustGetRequestSession(r *http.Request) (v *Session) {
	v, err := s.GetRequestSession(r)
	if err != nil {
		panic(err)
	}
	return v
}

//MustRegenerateRequestToken Regenerate the token name and data with give owner,and save to request.
//Panic if any error raised.
func (s Store) MustRegenerateRequestToken(r *http.Request, owner string) *Session {
	v := s.MustGetRequestSession(r)
	err := v.RegenerateToken(owner)
	if err != nil {
		panic(err)
	}
	return v
}

func (s Store) IsNotFoundError(err error) bool {
	return err == ErrDataNotFound
}
<|endoftext|>


<commit_before>/*
 * Copyright 2018 The Kythe 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.
 */

// Package cmakecmd extracts from a CMake-based repository.
package cmakecmd // import "kythe.io/kythe/go/extractors/config/runextractor/cmakecmd"

import (
	"context"
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"

	"kythe.io/kythe/go/extractors/config/runextractor/compdb"
	"kythe.io/kythe/go/util/cmdutil"

	"github.com/google/subcommands"
	"github.com/google/uuid"
)

type cmakeCommand struct {
	cmdutil.Info

	extractor string
	buildDir  string
	sourceDir string
}

// New creates a new subcommand for running cmake extraction.
func New() subcommands.Command {
	return &cmakeCommand{
		Info: cmdutil.NewInfo("cmake", "extract a repo build with CMake", `documight`),
	}
}

// SetFlags implements the subcommands interface and provides command-specific
// flags for cmake extraction.
func (c *cmakeCommand) SetFlags(fs *flag.FlagSet) {
	fs.StringVar(&c.extractor, "extractor", "", "A required path to the extractor binary to use.")
	fs.StringVar(&c.sourceDir, "sourcedir", ".", "A required path to the repository root. Defaults to the current directory.")
	fs.StringVar(&c.buildDir, "builddir", "", "An optional path to the directory in which to build. If empty, defaults to a unique subdirectory of sourcedir.")
}

func (c *cmakeCommand) verifyFlags() error {
	for _, key := range []string{"KYTHE_CORPUS", "KYTHE_ROOT_DIRECTORY", "KYTHE_OUTPUT_DIRECTORY"} {
		if os.Getenv(key) == "" {
			return fmt.Errorf("required %s not set", key)
		}
	}
	if c.extractor == "" {
		return fmt.Errorf("required -extractor not set")
	}
	if c.sourceDir == "" {
		return fmt.Errorf("required -sourcedir not set")
	}
	return nil
}

// Execute implements the subcommands interface and runs cmake extraction.
func (c *cmakeCommand) Execute(ctx context.Context, fs *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
	if err := c.verifyFlags(); err != nil {
		return c.Fail("Incorrect flags: %v", err)
	}
	// Since we have to change our working directory, resolve all of our paths early.
	extractor, err := filepath.Abs(c.extractor)
	if err != nil {
		return c.Fail("Unable to resolve path to extractor: %v", err)
	}
	sourceDir, err := filepath.Abs(c.sourceDir)
	if err != nil {
		return c.Fail("Unable to resolve source directory: %v", err)
	}
	var buildDir string
	if c.buildDir == "" {
		// sourceDir is already an absolute directory
		buildDir = filepath.Join(sourceDir, "build-"+uuid.New().String())
		if err := os.Mkdir(buildDir, 0755); err != nil {
			// Unlike below, we need to fail if the "unique" directory exists.
			return c.Fail("Unable to create build directory: %v", err)
		}
		// Only clean up the build directory if it was unspecified.
		defer cleanBuild(buildDir)
	} else {
		buildDir, err = filepath.Abs(c.buildDir)
		if err != nil {
			return c.Fail("Unable to resolve build directory: %v", err)
		}
	}

	// Create the build directory if it doesn't already exist.
	if err := os.MkdirAll(buildDir, 0755); err != nil {
		return c.Fail("Unable to create build directory: %v", err)
	}

	if err := runIn(exec.CommandContext(ctx, "cmake", "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON", sourceDir), buildDir); err != nil {
		return c.Fail("Error configuring cmake: %v", err)
	}

	if err := runIn(exec.CommandContext(ctx, "cmake", "--build", "."), buildDir); err != nil {
		return c.Fail("Error building repository: %v", err)
	}

	if err := compdb.ExtractCompilations(ctx, extractor, filepath.Join(buildDir, "compile_commands.json")); err != nil {
		return c.Fail("Error extracting repository: %v", err)
	}
	return subcommands.ExitSuccess
}

// cleanBuild removes dir and all files beneath it, logging an error if it fails.
func cleanBuild(dir string) {
	if err := os.RemoveAll(dir); err != nil {
		log.Printf("unable to remove build directory: %v", err)
	}
}

// runIn changes the cmd working directory to dir and call Run.
func runIn(cmd *exec.Cmd, dir string) error {
	cmd.Dir = dir
	return cmd.Run()
}
<commit_msg>feat(runextractor): pipe underlying cmake stderr to stderr (#4178)<commit_after>/*
 * Copyright 2018 The Kythe 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.
 */

// Package cmakecmd extracts from a CMake-based repository.
package cmakecmd // import "kythe.io/kythe/go/extractors/config/runextractor/cmakecmd"

import (
	"context"
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"

	"kythe.io/kythe/go/extractors/config/runextractor/compdb"
	"kythe.io/kythe/go/util/cmdutil"

	"github.com/google/subcommands"
	"github.com/google/uuid"
)

type cmakeCommand struct {
	cmdutil.Info

	extractor string
	buildDir  string
	sourceDir string
}

// New creates a new subcommand for running cmake extraction.
func New() subcommands.Command {
	return &cmakeCommand{
		Info: cmdutil.NewInfo("cmake", "extract a repo build with CMake", `documight`),
	}
}

// SetFlags implements the subcommands interface and provides command-specific
// flags for cmake extraction.
func (c *cmakeCommand) SetFlags(fs *flag.FlagSet) {
	fs.StringVar(&c.extractor, "extractor", "", "A required path to the extractor binary to use.")
	fs.StringVar(&c.sourceDir, "sourcedir", ".", "A required path to the repository root. Defaults to the current directory.")
	fs.StringVar(&c.buildDir, "builddir", "", "An optional path to the directory in which to build. If empty, defaults to a unique subdirectory of sourcedir.")
}

func (c *cmakeCommand) verifyFlags() error {
	for _, key := range []string{"KYTHE_CORPUS", "KYTHE_ROOT_DIRECTORY", "KYTHE_OUTPUT_DIRECTORY"} {
		if os.Getenv(key) == "" {
			return fmt.Errorf("required %s not set", key)
		}
	}
	if c.extractor == "" {
		return fmt.Errorf("required -extractor not set")
	}
	if c.sourceDir == "" {
		return fmt.Errorf("required -sourcedir not set")
	}
	return nil
}

// Execute implements the subcommands interface and runs cmake extraction.
func (c *cmakeCommand) Execute(ctx context.Context, fs *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
	if err := c.verifyFlags(); err != nil {
		return c.Fail("Incorrect flags: %v", err)
	}
	// Since we have to change our working directory, resolve all of our paths early.
	extractor, err := filepath.Abs(c.extractor)
	if err != nil {
		return c.Fail("Unable to resolve path to extractor: %v", err)
	}
	sourceDir, err := filepath.Abs(c.sourceDir)
	if err != nil {
		return c.Fail("Unable to resolve source directory: %v", err)
	}
	var buildDir string
	if c.buildDir == "" {
		// sourceDir is already an absolute directory
		buildDir = filepath.Join(sourceDir, "build-"+uuid.New().String())
		if err := os.Mkdir(buildDir, 0755); err != nil {
			// Unlike below, we need to fail if the "unique" directory exists.
			return c.Fail("Unable to create build directory: %v", err)
		}
		// Only clean up the build directory if it was unspecified.
		defer cleanBuild(buildDir)
	} else {
		buildDir, err = filepath.Abs(c.buildDir)
		if err != nil {
			return c.Fail("Unable to resolve build directory: %v", err)
		}
	}

	// Create the build directory if it doesn't already exist.
	if err := os.MkdirAll(buildDir, 0755); err != nil {
		return c.Fail("Unable to create build directory: %v", err)
	}

	if err := runIn(exec.CommandContext(ctx, "cmake", "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON", sourceDir), buildDir); err != nil {
		return c.Fail("Error configuring cmake: %v", err)
	}

	if err := runIn(exec.CommandContext(ctx, "cmake", "--build", "."), buildDir); err != nil {
		return c.Fail("Error building repository: %v", err)
	}

	if err := compdb.ExtractCompilations(ctx, extractor, filepath.Join(buildDir, "compile_commands.json")); err != nil {
		return c.Fail("Error extracting repository: %v", err)
	}
	return subcommands.ExitSuccess
}

// cleanBuild removes dir and all files beneath it, logging an error if it fails.
func cleanBuild(dir string) {
	if err := os.RemoveAll(dir); err != nil {
		log.Printf("unable to remove build directory: %v", err)
	}
}

// runIn changes the cmd working directory to dir and calls Run. The cmd's
// stderr is forwarded to stderr.
func runIn(cmd *exec.Cmd, dir string) error {
	cmd.Dir = dir
	cmd.Stderr = os.Stderr
	return cmd.Run()
}
<|endoftext|>


<commit_before>package db_test

import (
	"fmt"

	. "db"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	// sql drivers
	_ "github.com/mattn/go-sqlite3"
)

var _ = Describe("ORM", func() {
	Describe("Initializing the schema", func() {
		Context("With a new database", func() {
			var db *DB

			BeforeEach(func() {
				db = &DB{
					Driver: "sqlite3",
					DSN:    ":memory:",
				}

				Ω(db.Connect()).ShouldNot(HaveOccurred())
				Ω(db.Connected()).Should(BeTrue())
			})

			It("should succeed", func() {
				orm, err := NewORM(db)
				Ω(err).ShouldNot(HaveOccurred())
				Ω(orm).ShouldNot(BeNil())
			})

			It("should not create tables until Setup() is called", func() {
				orm, err := NewORM(db)
				Ω(err).ShouldNot(HaveOccurred())
				Ω(orm).ShouldNot(BeNil())

				Ω(db.ExecOnce("SELECT * FROM schema_info")).
					Should(HaveOccurred())
			})

			It("should create tables during Setup()", func() {
				orm, err := NewORM(db)
				Ω(err).ShouldNot(HaveOccurred())
				Ω(orm).ShouldNot(BeNil())

				Ω(orm.Setup()).ShouldNot(HaveOccurred())

				Ω(db.ExecOnce("SELECT * FROM schema_info")).
					ShouldNot(HaveOccurred())
			})

			It("should set the version number in schema_info", func() {
				orm, err := NewORM(db)
				Ω(err).ShouldNot(HaveOccurred())
				Ω(orm).ShouldNot(BeNil())

				Ω(orm.Setup()).ShouldNot(HaveOccurred())

				Ω(db.Cache("schema-version", `SELECT version FROM schema_info`)).
					ShouldNot(HaveOccurred())

				r, err := db.Query("schema-version")
				Ω(err).ShouldNot(HaveOccurred())
				Ω(r).ShouldNot(BeNil())
				Ω(r.Next()).Should(BeTrue())

				var v int
				Ω(r.Scan(&v)).ShouldNot(HaveOccurred())
				Ω(v).Should(Equal(1))
			})

			It("creates the correct tables", func() {
				orm, err := NewORM(db)
				Ω(err).ShouldNot(HaveOccurred())
				Ω(orm).ShouldNot(BeNil())

				Ω(orm.Setup()).ShouldNot(HaveOccurred())

				tableExists := func(table string) {
					sql := fmt.Sprintf("SELECT * FROM %s", table)
					Ω(db.ExecOnce(sql)).ShouldNot(HaveOccurred())
				}

				tableExists("targets")
				tableExists("stores")
				tableExists("schedules")
				tableExists("retention")
				tableExists("jobs")
				tableExists("archives")
				tableExists("tasks")
			})
		})
	})
	Describe("Retrieving Jobs", func() {
		var db *DB
		var orm *ORM
		BeforeEach(func() {
			db = &DB{
				Driver: "sqlite3",
				DSN:    ":memory:",
			}

			//Read as "ShouldNot(HaveErrored())"
			Ω(db.Connect()).ShouldNot(HaveOccurred())
			Ω(db.Connected()).Should(BeTrue())

			// New ORM for all contexts
			orm, err := NewORM(db)
			Ω(err).ShouldNot(HaveOccurred())
			Ω(orm).ShouldNot(BeNil())
			Ω(orm.Setup()).ShouldNot(HaveOccurred())
		})
		Context("With an empty database", func() {
			It("should return an empty list of jobs", func() {
				jobs, err := orm.GetAllJobs()
				Ω(err).ShouldNot(HaveOccurred())
				Ω(len(jobs)).Should(Equal(0))
			})
		})
	})
})
<commit_msg>Fix scope issues with := assignment operator<commit_after>package db_test

import (
	"fmt"

	. "db"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	// sql drivers
	_ "github.com/mattn/go-sqlite3"
)

var _ = Describe("ORM", func() {
	Describe("Initializing the schema", func() {
		Context("With a new database", func() {
			var db *DB

			BeforeEach(func() {
				db = &DB{
					Driver: "sqlite3",
					DSN:    ":memory:",
				}

				Ω(db.Connect()).ShouldNot(HaveOccurred())
				Ω(db.Connected()).Should(BeTrue())
			})

			It("should succeed", func() {
				orm, err := NewORM(db)
				Ω(err).ShouldNot(HaveOccurred())
				Ω(orm).ShouldNot(BeNil())
			})

			It("should not create tables until Setup() is called", func() {
				orm, err := NewORM(db)
				Ω(err).ShouldNot(HaveOccurred())
				Ω(orm).ShouldNot(BeNil())

				Ω(db.ExecOnce("SELECT * FROM schema_info")).
					Should(HaveOccurred())
			})

			It("should create tables during Setup()", func() {
				orm, err := NewORM(db)
				Ω(err).ShouldNot(HaveOccurred())
				Ω(orm).ShouldNot(BeNil())

				Ω(orm.Setup()).ShouldNot(HaveOccurred())

				Ω(db.ExecOnce("SELECT * FROM schema_info")).
					ShouldNot(HaveOccurred())
			})

			It("should set the version number in schema_info", func() {
				orm, err := NewORM(db)
				Ω(err).ShouldNot(HaveOccurred())
				Ω(orm).ShouldNot(BeNil())

				Ω(orm.Setup()).ShouldNot(HaveOccurred())

				Ω(db.Cache("schema-version", `SELECT version FROM schema_info`)).
					ShouldNot(HaveOccurred())

				r, err := db.Query("schema-version")
				Ω(err).ShouldNot(HaveOccurred())
				Ω(r).ShouldNot(BeNil())
				Ω(r.Next()).Should(BeTrue())

				var v int
				Ω(r.Scan(&v)).ShouldNot(HaveOccurred())
				Ω(v).Should(Equal(1))
			})

			It("creates the correct tables", func() {
				orm, err := NewORM(db)
				Ω(err).ShouldNot(HaveOccurred())
				Ω(orm).ShouldNot(BeNil())

				Ω(orm.Setup()).ShouldNot(HaveOccurred())

				tableExists := func(table string) {
					sql := fmt.Sprintf("SELECT * FROM %s", table)
					Ω(db.ExecOnce(sql)).ShouldNot(HaveOccurred())
				}

				tableExists("targets")
				tableExists("stores")
				tableExists("schedules")
				tableExists("retention")
				tableExists("jobs")
				tableExists("archives")
				tableExists("tasks")
			})
		})
	})
	Describe("Retrieving Jobs", func() {
		var db *DB
		var orm *ORM
		BeforeEach(func() {
			db = &DB{
				Driver: "sqlite3",
				DSN:    ":memory:",
			}

			//Read as "ShouldNot(HaveErrored())"
			Ω(db.Connect()).ShouldNot(HaveOccurred())
			Ω(db.Connected()).Should(BeTrue())

			// New ORM for all contexts
			var err error
			orm, err = NewORM(db)
			Ω(err).ShouldNot(HaveOccurred())
			Ω(orm).ShouldNot(BeNil())
			Ω(orm.Setup()).ShouldNot(HaveOccurred())
		})
		Context("With an empty database", func() {
			It("should return an empty list of jobs", func() {
				Ω(orm).ShouldNot(BeNil())
				jobs, err := orm.GetAllJobs()
				Ω(err).ShouldNot(HaveOccurred())
				Ω(len(jobs)).Should(Equal(0))
			})
		})
	})
})
<|endoftext|>