<commit_before>package google

import (
	"fmt"
	"strconv"

	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
	"google.golang.org/api/compute/v1"
)

func resourceComputeSslCertificate() *schema.Resource {
	return &schema.Resource{
		Create: resourceComputeSslCertificateCreate,
		Read:   resourceComputeSslCertificateRead,
		Delete: resourceComputeSslCertificateDelete,

		Schema: map[string]*schema.Schema{
			"certificate": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"name": &schema.Schema{
				Type:          schema.TypeString,
				Optional:      true,
				Computed:      true,
				ForceNew:      true,
				ConflictsWith: []string{"name_prefix"},
				ValidateFunc:  validateGCPName,
			},

			"name_prefix": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
					// https://cloud.google.com/compute/docs/reference/latest/sslCertificates#resource
					// uuid is 26 characters, limit the prefix to 37.
					value := v.(string)
					if len(value) > 37 {
						errors = append(errors, fmt.Errorf(
							"%q cannot be longer than 37 characters, name is limited to 63", k))
					}
					return
				},
			},

			"private_key": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"description": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
			},

			"id": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},

			"project": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
			},

			"self_link": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},
		},
	}
}

func resourceComputeSslCertificateCreate(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)

	project, err := getProject(d, config)
	if err != nil {
		return err
	}

	var certName string
	if v, ok := d.GetOk("name"); ok {
		certName = v.(string)
	} else if v, ok := d.GetOk("name_prefix"); ok {
		certName = resource.PrefixedUniqueId(v.(string))
	} else {
		certName = resource.UniqueId()
	}

	// Build the certificate parameter
	cert := &compute.SslCertificate{
		Name:        certName,
		Certificate: d.Get("certificate").(string),
		PrivateKey:  d.Get("private_key").(string),
	}

	if v, ok := d.GetOk("description"); ok {
		cert.Description = v.(string)
	}

	op, err := config.clientCompute.SslCertificates.Insert(
		project, cert).Do()

	if err != nil {
		return fmt.Errorf("Error creating ssl certificate: %s", err)
	}

	err = computeOperationWaitGlobal(config, op, project, "Creating SslCertificate")
	if err != nil {
		return err
	}

	d.SetId(cert.Name)

	return resourceComputeSslCertificateRead(d, meta)
}

func resourceComputeSslCertificateRead(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)

	project, err := getProject(d, config)
	if err != nil {
		return err
	}

	cert, err := config.clientCompute.SslCertificates.Get(
		project, d.Id()).Do()
	if err != nil {
		return handleNotFoundError(err, d, fmt.Sprintf("SSL Certificate %q", d.Get("name").(string)))
	}

	d.Set("self_link", cert.SelfLink)
	d.Set("id", strconv.FormatUint(cert.Id, 10))

	return nil
}

func resourceComputeSslCertificateDelete(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)

	project, err := getProject(d, config)
	if err != nil {
		return err
	}

	op, err := config.clientCompute.SslCertificates.Delete(
		project, d.Id()).Do()
	if err != nil {
		return fmt.Errorf("Error deleting ssl certificate: %s", err)
	}

	err = computeOperationWaitGlobal(config, op, project, "Deleting SslCertificate")
	if err != nil {
		return err
	}

	d.SetId("")
	return nil
}
<commit_msg>google_compute_ssl_certificate: mark private_key as sensitive (#220)<commit_after>package google

import (
	"fmt"
	"strconv"

	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
	"google.golang.org/api/compute/v1"
)

func resourceComputeSslCertificate() *schema.Resource {
	return &schema.Resource{
		Create: resourceComputeSslCertificateCreate,
		Read:   resourceComputeSslCertificateRead,
		Delete: resourceComputeSslCertificateDelete,

		Schema: map[string]*schema.Schema{
			"certificate": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"name": &schema.Schema{
				Type:          schema.TypeString,
				Optional:      true,
				Computed:      true,
				ForceNew:      true,
				ConflictsWith: []string{"name_prefix"},
				ValidateFunc:  validateGCPName,
			},

			"name_prefix": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
					// https://cloud.google.com/compute/docs/reference/latest/sslCertificates#resource
					// uuid is 26 characters, limit the prefix to 37.
					value := v.(string)
					if len(value) > 37 {
						errors = append(errors, fmt.Errorf(
							"%q cannot be longer than 37 characters, name is limited to 63", k))
					}
					return
				},
			},

			"private_key": &schema.Schema{
				Type:      schema.TypeString,
				Required:  true,
				ForceNew:  true,
				Sensitive: true,
			},

			"description": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
			},

			"id": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},

			"project": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
			},

			"self_link": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},
		},
	}
}

func resourceComputeSslCertificateCreate(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)

	project, err := getProject(d, config)
	if err != nil {
		return err
	}

	var certName string
	if v, ok := d.GetOk("name"); ok {
		certName = v.(string)
	} else if v, ok := d.GetOk("name_prefix"); ok {
		certName = resource.PrefixedUniqueId(v.(string))
	} else {
		certName = resource.UniqueId()
	}

	// Build the certificate parameter
	cert := &compute.SslCertificate{
		Name:        certName,
		Certificate: d.Get("certificate").(string),
		PrivateKey:  d.Get("private_key").(string),
	}

	if v, ok := d.GetOk("description"); ok {
		cert.Description = v.(string)
	}

	op, err := config.clientCompute.SslCertificates.Insert(
		project, cert).Do()

	if err != nil {
		return fmt.Errorf("Error creating ssl certificate: %s", err)
	}

	err = computeOperationWaitGlobal(config, op, project, "Creating SslCertificate")
	if err != nil {
		return err
	}

	d.SetId(cert.Name)

	return resourceComputeSslCertificateRead(d, meta)
}

func resourceComputeSslCertificateRead(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)

	project, err := getProject(d, config)
	if err != nil {
		return err
	}

	cert, err := config.clientCompute.SslCertificates.Get(
		project, d.Id()).Do()
	if err != nil {
		return handleNotFoundError(err, d, fmt.Sprintf("SSL Certificate %q", d.Get("name").(string)))
	}

	d.Set("self_link", cert.SelfLink)
	d.Set("id", strconv.FormatUint(cert.Id, 10))

	return nil
}

func resourceComputeSslCertificateDelete(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)

	project, err := getProject(d, config)
	if err != nil {
		return err
	}

	op, err := config.clientCompute.SslCertificates.Delete(
		project, d.Id()).Do()
	if err != nil {
		return fmt.Errorf("Error deleting ssl certificate: %s", err)
	}

	err = computeOperationWaitGlobal(config, op, project, "Deleting SslCertificate")
	if err != nil {
		return err
	}

	d.SetId("")
	return nil
}
<|endoftext|>


<commit_before>package prelude

const jsmapping = `
var $jsObjectPtr, $jsErrorPtr;

var $needsExternalization = function(t) {
  switch (t.kind) {
    case $kindBool:
    case $kindInt:
    case $kindInt8:
    case $kindInt16:
    case $kindInt32:
    case $kindUint:
    case $kindUint8:
    case $kindUint16:
    case $kindUint32:
    case $kindUintptr:
    case $kindFloat32:
    case $kindFloat64:
      return false;
    default:
      return t !== $jsObjectPtr;
  }
};

var $externalize = function(v, t) {
  if (t === $jsObjectPtr) {
    return v;
  }
  switch (t.kind) {
  case $kindBool:
  case $kindInt:
  case $kindInt8:
  case $kindInt16:
  case $kindInt32:
  case $kindUint:
  case $kindUint8:
  case $kindUint16:
  case $kindUint32:
  case $kindUintptr:
  case $kindFloat32:
  case $kindFloat64:
    return v;
  case $kindInt64:
  case $kindUint64:
    return $flatten64(v);
  case $kindArray:
    if ($needsExternalization(t.elem)) {
      return $mapArray(v, function(e) { return $externalize(e, t.elem); });
    }
    return v;
  case $kindFunc:
    return $externalizeFunction(v, t, false);
  case $kindInterface:
    if (v === $ifaceNil) {
      return null;
    }
    if (v.constructor === $jsObjectPtr) {
      return v.$val.object;
    }
    return $externalize(v.$val, v.constructor);
  case $kindMap:
    var m = {};
    var keys = $keys(v);
    for (var i = 0; i < keys.length; i++) {
      var entry = v[keys[i]];
      m[$externalize(entry.k, t.key)] = $externalize(entry.v, t.elem);
    }
    return m;
  case $kindPtr:
    if (v === t.nil) {
      return null;
    }
    return $externalize(v.$get(), t.elem);
  case $kindSlice:
    if ($needsExternalization(t.elem)) {
      return $mapArray($sliceToArray(v), function(e) { return $externalize(e, t.elem); });
    }
    return $sliceToArray(v);
  case $kindString:
    if (v.search(/^[\x00-\x7F]*$/) !== -1) {
      return v;
    }
    var s = "", r;
    for (var i = 0; i < v.length; i += r[1]) {
      r = $decodeRune(v, i);
      var c = r[0];
      if (c > 0xFFFF) {
        var h = Math.floor((c - 0x10000) / 0x400) + 0xD800;
        var l = (c - 0x10000) % 0x400 + 0xDC00;
        s += String.fromCharCode(h, l);
        continue;
      }
      s += String.fromCharCode(c);
    }
    return s;
  case $kindStruct:
    var timePkg = $packages["time"];
    if (timePkg !== undefined && v.constructor === timePkg.Time.ptr) {
      var milli = $div64(v.UnixNano(), new $Int64(0, 1000000));
      return new Date($flatten64(milli));
    }

    var noJsObject = {};
    var searchJsObject = function(v, t) {
      if (t === $jsObjectPtr) {
        return v;
      }
      switch (t.kind) {
      case $kindPtr:
        if (v === t.nil) {
          return noJsObject;
        }
        return searchJsObject(v.$get(), t.elem);
      case $kindStruct:
        var f = t.fields[0];
        return searchJsObject(v[f.prop], f.typ);
      case $kindInterface:
        return searchJsObject(v.$val, v.constructor);
      default:
        return noJsObject;
      }
    };
    var o = searchJsObject(v, t);
    if (o !== noJsObject) {
      return o;
    }

    o = {};
    for (var i = 0; i < t.fields.length; i++) {
      var f = t.fields[i];
      if (!f.exported) {
        continue;
      }
      o[f.name] = $externalize(v[f.prop], f.typ);
    }
    return o;
  }
  $throwRuntimeError("cannot externalize " + t.string);
};

var $externalizeFunction = function(v, t, passThis) {
  if (v === $throwNilPointerError) {
    return null;
  }
  if (v.$externalizeWrapper === undefined) {
    $checkForDeadlock = false;
    v.$externalizeWrapper = function() {
      var args = [];
      for (var i = 0; i < t.params.length; i++) {
        if (t.variadic && i === t.params.length - 1) {
          var vt = t.params[i].elem, varargs = [];
          for (var j = i; j < arguments.length; j++) {
            varargs.push($internalize(arguments[j], vt));
          }
          args.push(new (t.params[i])(varargs));
          break;
        }
        args.push($internalize(arguments[i], t.params[i]));
      }
      var canBlock = $curGoroutine.canBlock;
      $curGoroutine.canBlock = false;
      try {
        var result = v.apply(passThis ? this : undefined, args);
      } finally {
        $curGoroutine.canBlock = canBlock;
      }
      switch (t.results.length) {
      case 0:
        return;
      case 1:
        return $externalize(result, t.results[0]);
      default:
        for (var i = 0; i < t.results.length; i++) {
          result[i] = $externalize(result[i], t.results[i]);
        }
        return result;
      }
    };
  }
  return v.$externalizeWrapper;
};

var $internalize = function(v, t, recv) {
  if (t === $jsObjectPtr) {
    return v;
  }
  if (t === $jsObjectPtr.elem) {
    $throwRuntimeError("cannot internalize js.Object, use *js.Object instead");
  }
  if (v && v.__internal_object__ !== undefined) {
    return $assertType(v.__internal_object__, t, false);
  }
  var timePkg = $packages["time"];
  if (timePkg !== undefined && t === timePkg.Time) {
    if (!(v !== null && v !== undefined && v.constructor === Date)) {
      $throwRuntimeError("cannot internalize time.Time from " + typeof v + ", must be Date");
    }
    return timePkg.Unix(new $Int64(0, 0), new $Int64(0, v.getTime() * 1000000));
  }
  switch (t.kind) {
  case $kindBool:
    return !!v;
  case $kindInt:
    return parseInt(v);
  case $kindInt8:
    return parseInt(v) << 24 >> 24;
  case $kindInt16:
    return parseInt(v) << 16 >> 16;
  case $kindInt32:
    return parseInt(v) >> 0;
  case $kindUint:
    return parseInt(v);
  case $kindUint8:
    return parseInt(v) << 24 >>> 24;
  case $kindUint16:
    return parseInt(v) << 16 >>> 16;
  case $kindUint32:
  case $kindUintptr:
    return parseInt(v) >>> 0;
  case $kindInt64:
  case $kindUint64:
    return new t(0, v);
  case $kindFloat32:
  case $kindFloat64:
    return parseFloat(v);
  case $kindArray:
    if (v.length !== t.len) {
      $throwRuntimeError("got array with wrong size from JavaScript native");
    }
    return $mapArray(v, function(e) { return $internalize(e, t.elem); });
  case $kindFunc:
    return function() {
      var args = [];
      for (var i = 0; i < t.params.length; i++) {
        if (t.variadic && i === t.params.length - 1) {
          var vt = t.params[i].elem, varargs = arguments[i];
          for (var j = 0; j < varargs.$length; j++) {
            args.push($externalize(varargs.$array[varargs.$offset + j], vt));
          }
          break;
        }
        args.push($externalize(arguments[i], t.params[i]));
      }
      var result = v.apply(recv, args);
      switch (t.results.length) {
      case 0:
        return;
      case 1:
        return $internalize(result, t.results[0]);
      default:
        for (var i = 0; i < t.results.length; i++) {
          result[i] = $internalize(result[i], t.results[i]);
        }
        return result;
      }
    };
  case $kindInterface:
    if (t.methods.length !== 0) {
      $throwRuntimeError("cannot internalize " + t.string);
    }
    if (v === null) {
      return $ifaceNil;
    }
    if (v === undefined) {
      return new $jsObjectPtr(undefined);
    }
    switch (v.constructor) {
    case Int8Array:
      return new ($sliceType($Int8))(v);
    case Int16Array:
      return new ($sliceType($Int16))(v);
    case Int32Array:
      return new ($sliceType($Int))(v);
    case Uint8Array:
      return new ($sliceType($Uint8))(v);
    case Uint16Array:
      return new ($sliceType($Uint16))(v);
    case Uint32Array:
      return new ($sliceType($Uint))(v);
    case Float32Array:
      return new ($sliceType($Float32))(v);
    case Float64Array:
      return new ($sliceType($Float64))(v);
    case Array:
      return $internalize(v, $sliceType($emptyInterface));
    case Boolean:
      return new $Bool(!!v);
    case Date:
      if (timePkg === undefined) {
        /* time package is not present, internalize as &js.Object{Date} so it can be externalized into original Date. */
        return new $jsObjectPtr(v);
      }
      return new timePkg.Time($internalize(v, timePkg.Time));
    case Function:
      var funcType = $funcType([$sliceType($emptyInterface)], [$jsObjectPtr], true);
      return new funcType($internalize(v, funcType));
    case Number:
      return new $Float64(parseFloat(v));
    case String:
      return new $String($internalize(v, $String));
    default:
      if ($global.Node && v instanceof $global.Node) {
        return new $jsObjectPtr(v);
      }
      var mapType = $mapType($String, $emptyInterface);
      return new mapType($internalize(v, mapType));
    }
  case $kindMap:
    var m = {};
    var keys = $keys(v);
    for (var i = 0; i < keys.length; i++) {
      var k = $internalize(keys[i], t.key);
      m[t.key.keyFor(k)] = { k: k, v: $internalize(v[keys[i]], t.elem) };
    }
    return m;
  case $kindPtr:
    if (t.elem.kind === $kindStruct) {
      return $internalize(v, t.elem);
    }
  case $kindSlice:
    return new t($mapArray(v, function(e) { return $internalize(e, t.elem); }));
  case $kindString:
    v = String(v);
    if (v.search(/^[\x00-\x7F]*$/) !== -1) {
      return v;
    }
    var s = "";
    var i = 0;
    while (i < v.length) {
      var h = v.charCodeAt(i);
      if (0xD800 <= h && h <= 0xDBFF) {
        var l = v.charCodeAt(i + 1);
        var c = (h - 0xD800) * 0x400 + l - 0xDC00 + 0x10000;
        s += $encodeRune(c);
        i += 2;
        continue;
      }
      s += $encodeRune(h);
      i++;
    }
    return s;
  case $kindStruct:
    var noJsObject = {};
    var searchJsObject = function(t) {
      if (t === $jsObjectPtr) {
        return v;
      }
      if (t === $jsObjectPtr.elem) {
        $throwRuntimeError("cannot internalize js.Object, use *js.Object instead");
      }
      switch (t.kind) {
      case $kindPtr:
        return searchJsObject(t.elem);
      case $kindStruct:
        var f = t.fields[0];
        var o = searchJsObject(f.typ);
        if (o !== noJsObject) {
          var n = new t.ptr();
          n[f.prop] = o;
          return n;
        }
        return noJsObject;
      default:
        return noJsObject;
      }
    };
    var o = searchJsObject(t);
    if (o !== noJsObject) {
      return o;
    }
  }
  $throwRuntimeError("cannot internalize " + t.string);
};
`
<commit_msg>compiler/prelude: Optimize ASCII string detection. (#628)<commit_after>package prelude

const jsmapping = `
var $jsObjectPtr, $jsErrorPtr;

var $needsExternalization = function(t) {
  switch (t.kind) {
    case $kindBool:
    case $kindInt:
    case $kindInt8:
    case $kindInt16:
    case $kindInt32:
    case $kindUint:
    case $kindUint8:
    case $kindUint16:
    case $kindUint32:
    case $kindUintptr:
    case $kindFloat32:
    case $kindFloat64:
      return false;
    default:
      return t !== $jsObjectPtr;
  }
};

var $externalize = function(v, t) {
  if (t === $jsObjectPtr) {
    return v;
  }
  switch (t.kind) {
  case $kindBool:
  case $kindInt:
  case $kindInt8:
  case $kindInt16:
  case $kindInt32:
  case $kindUint:
  case $kindUint8:
  case $kindUint16:
  case $kindUint32:
  case $kindUintptr:
  case $kindFloat32:
  case $kindFloat64:
    return v;
  case $kindInt64:
  case $kindUint64:
    return $flatten64(v);
  case $kindArray:
    if ($needsExternalization(t.elem)) {
      return $mapArray(v, function(e) { return $externalize(e, t.elem); });
    }
    return v;
  case $kindFunc:
    return $externalizeFunction(v, t, false);
  case $kindInterface:
    if (v === $ifaceNil) {
      return null;
    }
    if (v.constructor === $jsObjectPtr) {
      return v.$val.object;
    }
    return $externalize(v.$val, v.constructor);
  case $kindMap:
    var m = {};
    var keys = $keys(v);
    for (var i = 0; i < keys.length; i++) {
      var entry = v[keys[i]];
      m[$externalize(entry.k, t.key)] = $externalize(entry.v, t.elem);
    }
    return m;
  case $kindPtr:
    if (v === t.nil) {
      return null;
    }
    return $externalize(v.$get(), t.elem);
  case $kindSlice:
    if ($needsExternalization(t.elem)) {
      return $mapArray($sliceToArray(v), function(e) { return $externalize(e, t.elem); });
    }
    return $sliceToArray(v);
  case $kindString:
    if ($isASCII(v)) {
      return v;
    }
    var s = "", r;
    for (var i = 0; i < v.length; i += r[1]) {
      r = $decodeRune(v, i);
      var c = r[0];
      if (c > 0xFFFF) {
        var h = Math.floor((c - 0x10000) / 0x400) + 0xD800;
        var l = (c - 0x10000) % 0x400 + 0xDC00;
        s += String.fromCharCode(h, l);
        continue;
      }
      s += String.fromCharCode(c);
    }
    return s;
  case $kindStruct:
    var timePkg = $packages["time"];
    if (timePkg !== undefined && v.constructor === timePkg.Time.ptr) {
      var milli = $div64(v.UnixNano(), new $Int64(0, 1000000));
      return new Date($flatten64(milli));
    }

    var noJsObject = {};
    var searchJsObject = function(v, t) {
      if (t === $jsObjectPtr) {
        return v;
      }
      switch (t.kind) {
      case $kindPtr:
        if (v === t.nil) {
          return noJsObject;
        }
        return searchJsObject(v.$get(), t.elem);
      case $kindStruct:
        var f = t.fields[0];
        return searchJsObject(v[f.prop], f.typ);
      case $kindInterface:
        return searchJsObject(v.$val, v.constructor);
      default:
        return noJsObject;
      }
    };
    var o = searchJsObject(v, t);
    if (o !== noJsObject) {
      return o;
    }

    o = {};
    for (var i = 0; i < t.fields.length; i++) {
      var f = t.fields[i];
      if (!f.exported) {
        continue;
      }
      o[f.name] = $externalize(v[f.prop], f.typ);
    }
    return o;
  }
  $throwRuntimeError("cannot externalize " + t.string);
};

var $externalizeFunction = function(v, t, passThis) {
  if (v === $throwNilPointerError) {
    return null;
  }
  if (v.$externalizeWrapper === undefined) {
    $checkForDeadlock = false;
    v.$externalizeWrapper = function() {
      var args = [];
      for (var i = 0; i < t.params.length; i++) {
        if (t.variadic && i === t.params.length - 1) {
          var vt = t.params[i].elem, varargs = [];
          for (var j = i; j < arguments.length; j++) {
            varargs.push($internalize(arguments[j], vt));
          }
          args.push(new (t.params[i])(varargs));
          break;
        }
        args.push($internalize(arguments[i], t.params[i]));
      }
      var canBlock = $curGoroutine.canBlock;
      $curGoroutine.canBlock = false;
      try {
        var result = v.apply(passThis ? this : undefined, args);
      } finally {
        $curGoroutine.canBlock = canBlock;
      }
      switch (t.results.length) {
      case 0:
        return;
      case 1:
        return $externalize(result, t.results[0]);
      default:
        for (var i = 0; i < t.results.length; i++) {
          result[i] = $externalize(result[i], t.results[i]);
        }
        return result;
      }
    };
  }
  return v.$externalizeWrapper;
};

var $internalize = function(v, t, recv) {
  if (t === $jsObjectPtr) {
    return v;
  }
  if (t === $jsObjectPtr.elem) {
    $throwRuntimeError("cannot internalize js.Object, use *js.Object instead");
  }
  if (v && v.__internal_object__ !== undefined) {
    return $assertType(v.__internal_object__, t, false);
  }
  var timePkg = $packages["time"];
  if (timePkg !== undefined && t === timePkg.Time) {
    if (!(v !== null && v !== undefined && v.constructor === Date)) {
      $throwRuntimeError("cannot internalize time.Time from " + typeof v + ", must be Date");
    }
    return timePkg.Unix(new $Int64(0, 0), new $Int64(0, v.getTime() * 1000000));
  }
  switch (t.kind) {
  case $kindBool:
    return !!v;
  case $kindInt:
    return parseInt(v);
  case $kindInt8:
    return parseInt(v) << 24 >> 24;
  case $kindInt16:
    return parseInt(v) << 16 >> 16;
  case $kindInt32:
    return parseInt(v) >> 0;
  case $kindUint:
    return parseInt(v);
  case $kindUint8:
    return parseInt(v) << 24 >>> 24;
  case $kindUint16:
    return parseInt(v) << 16 >>> 16;
  case $kindUint32:
  case $kindUintptr:
    return parseInt(v) >>> 0;
  case $kindInt64:
  case $kindUint64:
    return new t(0, v);
  case $kindFloat32:
  case $kindFloat64:
    return parseFloat(v);
  case $kindArray:
    if (v.length !== t.len) {
      $throwRuntimeError("got array with wrong size from JavaScript native");
    }
    return $mapArray(v, function(e) { return $internalize(e, t.elem); });
  case $kindFunc:
    return function() {
      var args = [];
      for (var i = 0; i < t.params.length; i++) {
        if (t.variadic && i === t.params.length - 1) {
          var vt = t.params[i].elem, varargs = arguments[i];
          for (var j = 0; j < varargs.$length; j++) {
            args.push($externalize(varargs.$array[varargs.$offset + j], vt));
          }
          break;
        }
        args.push($externalize(arguments[i], t.params[i]));
      }
      var result = v.apply(recv, args);
      switch (t.results.length) {
      case 0:
        return;
      case 1:
        return $internalize(result, t.results[0]);
      default:
        for (var i = 0; i < t.results.length; i++) {
          result[i] = $internalize(result[i], t.results[i]);
        }
        return result;
      }
    };
  case $kindInterface:
    if (t.methods.length !== 0) {
      $throwRuntimeError("cannot internalize " + t.string);
    }
    if (v === null) {
      return $ifaceNil;
    }
    if (v === undefined) {
      return new $jsObjectPtr(undefined);
    }
    switch (v.constructor) {
    case Int8Array:
      return new ($sliceType($Int8))(v);
    case Int16Array:
      return new ($sliceType($Int16))(v);
    case Int32Array:
      return new ($sliceType($Int))(v);
    case Uint8Array:
      return new ($sliceType($Uint8))(v);
    case Uint16Array:
      return new ($sliceType($Uint16))(v);
    case Uint32Array:
      return new ($sliceType($Uint))(v);
    case Float32Array:
      return new ($sliceType($Float32))(v);
    case Float64Array:
      return new ($sliceType($Float64))(v);
    case Array:
      return $internalize(v, $sliceType($emptyInterface));
    case Boolean:
      return new $Bool(!!v);
    case Date:
      if (timePkg === undefined) {
        /* time package is not present, internalize as &js.Object{Date} so it can be externalized into original Date. */
        return new $jsObjectPtr(v);
      }
      return new timePkg.Time($internalize(v, timePkg.Time));
    case Function:
      var funcType = $funcType([$sliceType($emptyInterface)], [$jsObjectPtr], true);
      return new funcType($internalize(v, funcType));
    case Number:
      return new $Float64(parseFloat(v));
    case String:
      return new $String($internalize(v, $String));
    default:
      if ($global.Node && v instanceof $global.Node) {
        return new $jsObjectPtr(v);
      }
      var mapType = $mapType($String, $emptyInterface);
      return new mapType($internalize(v, mapType));
    }
  case $kindMap:
    var m = {};
    var keys = $keys(v);
    for (var i = 0; i < keys.length; i++) {
      var k = $internalize(keys[i], t.key);
      m[t.key.keyFor(k)] = { k: k, v: $internalize(v[keys[i]], t.elem) };
    }
    return m;
  case $kindPtr:
    if (t.elem.kind === $kindStruct) {
      return $internalize(v, t.elem);
    }
  case $kindSlice:
    return new t($mapArray(v, function(e) { return $internalize(e, t.elem); }));
  case $kindString:
    v = String(v);
    if ($isASCII(v)) {
      return v;
    }
    var s = "";
    var i = 0;
    while (i < v.length) {
      var h = v.charCodeAt(i);
      if (0xD800 <= h && h <= 0xDBFF) {
        var l = v.charCodeAt(i + 1);
        var c = (h - 0xD800) * 0x400 + l - 0xDC00 + 0x10000;
        s += $encodeRune(c);
        i += 2;
        continue;
      }
      s += $encodeRune(h);
      i++;
    }
    return s;
  case $kindStruct:
    var noJsObject = {};
    var searchJsObject = function(t) {
      if (t === $jsObjectPtr) {
        return v;
      }
      if (t === $jsObjectPtr.elem) {
        $throwRuntimeError("cannot internalize js.Object, use *js.Object instead");
      }
      switch (t.kind) {
      case $kindPtr:
        return searchJsObject(t.elem);
      case $kindStruct:
        var f = t.fields[0];
        var o = searchJsObject(f.typ);
        if (o !== noJsObject) {
          var n = new t.ptr();
          n[f.prop] = o;
          return n;
        }
        return noJsObject;
      default:
        return noJsObject;
      }
    };
    var o = searchJsObject(t);
    if (o !== noJsObject) {
      return o;
    }
  }
  $throwRuntimeError("cannot internalize " + t.string);
};

/* $isASCII reports whether string s contains only ASCII characters. */
var $isASCII = function(s) {
  for (var i = 0; i < s.length; i++) {
    if (s.charCodeAt(i) >= 128) {
      return false;
    }
  }
  return true;
};
`
<|endoftext|>


<commit_before>package recording

import (
	"bufio"
	"context"
	"errors"
	"log"
	"net/http"
	"os"
	"time"
)

var (
	ErrMismatchWrite = errors.New("recording: did not write the same number of bytes that were read")
)

// Recording ...
type Recording struct {
	ctx    context.Context
	url    string
	fname  string
	fout   *os.File
	cancel context.CancelFunc

	Debug bool
	Err   error
}

// New creates a new Recording of the given URL to the given filename for output.
func New(url, fname string) (*Recording, error) {
	fout, err := os.Create(fname)
	if err != nil {
		return nil, err
	}

	ctx, cancel := context.WithTimeout(context.Background(), 4*time.Hour)

	r := &Recording{
		ctx:    ctx,
		url:    url,
		fname:  fname,
		fout:   fout,
		cancel: cancel,
	}

	return r, nil
}

func (r *Recording) Cancel() {
	r.cancel()
}

func (r *Recording) Done() <-chan struct{} {
	return r.ctx.Done()
}

// OutputFilename gets the output filename originally passed into New.
func (r *Recording) OutputFilename() string {
	return r.fname
}

// Start blockingly starts the recording and returns the error if one is encountered while streaming.
// This should be stopped in another goroutine.
func (r *Recording) Start() error {
	resp, err := http.Get(r.url)
	if err != nil {
		return err
	}

	defer resp.Body.Close()
	defer r.fout.Close()
	defer r.cancel()

	reader := bufio.NewReader(resp.Body)

	c := time.NewTicker(5 * time.Second)
	defer c.Stop()

	buf := make([]byte, 65536)

	for {
		time.Sleep(250 * time.Millisecond)

		select {
		case <-r.ctx.Done():
			return nil
		case <-c.C:
			if r.Debug {
				log.Println("Syncing file")
			}
			err := r.fout.Sync()
			if err != nil {
				r.Err = err
				return err
			}
		default:
			nr, err := reader.Read(buf)
			if err != nil {
				r.Err = err
				return err
			}

			if r.Debug {
				log.Printf("%d bytes read", nr)
			}

			buf = buf[:nr]

			_, err = r.fout.Write(buf)
			if err != nil {
				r.Err = err
				return err
			}
		}
	}
}
<commit_msg>recording: sync on done<commit_after>package recording

import (
	"bufio"
	"context"
	"errors"
	"log"
	"net/http"
	"os"
	"time"
)

var (
	ErrMismatchWrite = errors.New("recording: did not write the same number of bytes that were read")
)

// Recording ...
type Recording struct {
	ctx    context.Context
	url    string
	fname  string
	fout   *os.File
	cancel context.CancelFunc

	Debug bool
	Err   error
}

// New creates a new Recording of the given URL to the given filename for output.
func New(url, fname string) (*Recording, error) {
	fout, err := os.Create(fname)
	if err != nil {
		return nil, err
	}

	ctx, cancel := context.WithTimeout(context.Background(), 4*time.Hour)

	r := &Recording{
		ctx:    ctx,
		url:    url,
		fname:  fname,
		fout:   fout,
		cancel: cancel,
	}

	return r, nil
}

func (r *Recording) Cancel() {
	r.cancel()
}

func (r *Recording) Done() <-chan struct{} {
	return r.ctx.Done()
}

// OutputFilename gets the output filename originally passed into New.
func (r *Recording) OutputFilename() string {
	return r.fname
}

// Start blockingly starts the recording and returns the error if one is encountered while streaming.
// This should be stopped in another goroutine.
func (r *Recording) Start() error {
	resp, err := http.Get(r.url)
	if err != nil {
		return err
	}

	defer resp.Body.Close()
	defer r.fout.Close()
	defer r.cancel()

	reader := bufio.NewReader(resp.Body)

	c := time.NewTicker(5 * time.Second)
	defer c.Stop()

	buf := make([]byte, 65536)

	for {
		time.Sleep(250 * time.Millisecond)

		select {
		case <-r.ctx.Done():
			r.fout.Sync()
			return nil
		case <-c.C:
			if r.Debug {
				log.Println("Syncing file")
			}
			err := r.fout.Sync()
			if err != nil {
				r.Err = err
				return err
			}
		default:
			nr, err := reader.Read(buf)
			if err != nil {
				r.Err = err
				return err
			}

			if r.Debug {
				log.Printf("%d bytes read", nr)
			}

			buf = buf[:nr]

			_, err = r.fout.Write(buf)
			if err != nil {
				r.Err = err
				return err
			}
		}
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2017 Cisco and/or its affiliates.
//
// 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 nametoidx

import (
	"testing"

	"github.com/ligato/cn-infra/logging/logroot"
	"github.com/ligato/vpp-agent/idxvpp"
	"github.com/onsi/gomega"
	"strconv"
)

const (
	idx1 = 1
	idx2 = 2
	idx3 = 3
)

var (
	eth0 MappingName = "eth0"
	eth1 MappingName = "eth1"
	eth2 MappingName = "eth2"
)

func InMemory(reloaded bool) (idxvpp.NameToIdxRW, error) {
	return NewNameToIdx(logroot.Logger(), "plugin1", "test", nil), nil
}

func Test01UnregisteredMapsToNothing(t *testing.T) {
	Given(t).NameToIdx(InMemory, nil).
		When().Name(eth1).IsUnRegistered().
		Then().Name(eth1).MapsToNothing().
		And().Notification(eth1, Write).IsNotExpected()
}

func Test02RegisteredReturnsIdx(t *testing.T) {
	Given(t).NameToIdx(InMemory, nil).
		When().Name(eth1).IsRegistered(idx1).
		Then().Name(eth1).MapsTo(idx1).
		And().Notification(eth1, Write).IsExpectedFor(idx1)
}

func Test03RegFirstThenUnreg(t *testing.T) {
	Given(t).NameToIdx(InMemory, map[MappingName]MappingIdx{eth1: idx1}).
		When().Name(eth1).IsUnRegistered().
		Then().Name(eth1).MapsToNothing().
		And().Notification(eth1, Del).IsExpectedFor(idx1)
}

func Test03Eth0RegPlusEth1Unreg(t *testing.T) {
	Given(t).NameToIdx(InMemory, map[MappingName]MappingIdx{eth0: idx1, eth1: idx2}).
		When().Name(eth1).IsUnRegistered().
		Then().Name(eth1).MapsToNothing().
		And().Notification(eth1, Del).IsExpectedFor(idx2).
		And().Name(eth0).MapsTo(idx1).
		And().Notification(eth0, Write).IsNotExpected() //because watch is registered after given keyword
}

func Test04RegTwiceSameNameWithDifferentIdx(t *testing.T) {
	Given(t).NameToIdx(InMemory, nil).
		When().Name(eth1).IsRegistered(idx1).
		Then().Name(eth1).MapsTo(idx1). //Notif eth1, idx1
		And().Notification(eth1, Write).IsExpectedFor(idx1).
		When().Name(eth1).IsRegistered(idx2).
		Then().Name(eth1).MapsTo(idx2). //Notif eth1, idx1
		And().Notification(eth1, Write).IsExpectedFor(idx2)
}

const (
	flagMetaKey = "flag"
	valsMetaKey = "vals"
)

type metaInformation struct {
	flag bool
	vals []string
}

func createIdx(meta interface{}) map[string][]string {
	typed, ok := meta.(*metaInformation)
	if !ok {
		return nil
	}

	return map[string][]string{
		flagMetaKey: {strconv.FormatBool(typed.flag)},
		valsMetaKey: typed.vals,
	}
}

func TestIndexedMetadata(t *testing.T) {
	gomega.RegisterTestingT(t)
	idxm := NewNameToIdx(logroot.Logger(), "plugin", "title", createIdx)

	res := idxm.LookupNameByMetadata(flagMetaKey, "true")
	gomega.Expect(res).To(gomega.BeNil())

	meta1 := &metaInformation{
		flag: true,
		vals: []string{"abc", "def", "xyz"},
	}
	meta2 := &metaInformation{
		flag: false,
		vals: []string{"abc", "klm", "opq"},
	}
	meta3 := &metaInformation{
		flag: true,
		vals: []string{"jkl"},
	}

	idxm.RegisterName(string(eth0), idx1, meta1)
	idxm.RegisterName(string(eth1), idx2, meta2)
	idxm.RegisterName(string(eth2), idx3, meta3)

	res = idxm.LookupNameByMetadata(flagMetaKey, "false")
	gomega.Expect(res).NotTo(gomega.BeNil())
	gomega.Expect(res[0]).To(gomega.BeEquivalentTo(eth1))

	res = idxm.LookupNameByMetadata(flagMetaKey, "true")
	gomega.Expect(len(res)).To(gomega.BeEquivalentTo(2))
	gomega.Expect(res).To(gomega.ContainElement(string(eth0)))
	gomega.Expect(res).To(gomega.ContainElement(string(eth2)))

	res = idxm.LookupNameByMetadata(valsMetaKey, "abc")
	gomega.Expect(len(res)).To(gomega.BeEquivalentTo(2))
	gomega.Expect(res).To(gomega.ContainElement(string(eth0)))
	gomega.Expect(res).To(gomega.ContainElement(string(eth1)))

	res = idxm.LookupNameByMetadata(valsMetaKey, "jkl")
	gomega.Expect(len(res)).To(gomega.BeEquivalentTo(1))
	gomega.Expect(res[0]).To(gomega.BeEquivalentTo(eth2))

	idxm.UnregisterName(string(eth0))
	res = idxm.LookupNameByMetadata(flagMetaKey, "true")
	gomega.Expect(len(res)).To(gomega.BeEquivalentTo(1))
	gomega.Expect(res[0]).To(gomega.BeEquivalentTo(eth2))

}

func TestOldIndexRemove(t *testing.T) {
	gomega.RegisterTestingT(t)
	idxm := NewNameToIdx(logroot.Logger(), "plugin", "title", nil)

	idxm.RegisterName(string(eth0), idx1, nil)

	idx, _, found := idxm.LookupIdx(string(eth0))
	gomega.Expect(found).To(gomega.BeTrue())
	gomega.Expect(idx).To(gomega.BeEquivalentTo(idx1))

	name, _, found := idxm.LookupName(idx1)
	gomega.Expect(found).To(gomega.BeTrue())
	gomega.Expect(name).To(gomega.BeEquivalentTo(string(name)))

	idxm.RegisterName(string(eth0), idx2, nil)

	idx, _, found = idxm.LookupIdx(string(eth0))
	gomega.Expect(found).To(gomega.BeTrue())
	gomega.Expect(idx).To(gomega.BeEquivalentTo(idx2))

	name, _, found = idxm.LookupName(idx2)
	gomega.Expect(found).To(gomega.BeTrue())
	gomega.Expect(name).To(gomega.BeEquivalentTo(string(name)))

	name, _, found = idxm.LookupName(idx1)
	gomega.Expect(found).To(gomega.BeFalse())
	gomega.Expect(name).To(gomega.BeEquivalentTo(""))
}
<commit_msg>fix idx test<commit_after>// Copyright (c) 2017 Cisco and/or its affiliates.
//
// 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 nametoidx

import (
	"testing"

	"github.com/ligato/cn-infra/logging/logroot"
	"github.com/ligato/vpp-agent/idxvpp"
	"github.com/onsi/gomega"
	"strconv"
)

const (
	idx1 = 1
	idx2 = 2
	idx3 = 3
)

var (
	eth0 MappingName = "eth0"
	eth1 MappingName = "eth1"
	eth2 MappingName = "eth2"
)

func InMemory(reloaded bool) (idxvpp.NameToIdxRW, error) {
	return NewNameToIdx(logroot.StandardLogger(), "plugin1", "test", nil), nil
}

func Test01UnregisteredMapsToNothing(t *testing.T) {
	Given(t).NameToIdx(InMemory, nil).
		When().Name(eth1).IsUnRegistered().
		Then().Name(eth1).MapsToNothing().
		And().Notification(eth1, Write).IsNotExpected()
}

func Test02RegisteredReturnsIdx(t *testing.T) {
	Given(t).NameToIdx(InMemory, nil).
		When().Name(eth1).IsRegistered(idx1).
		Then().Name(eth1).MapsTo(idx1).
		And().Notification(eth1, Write).IsExpectedFor(idx1)
}

func Test03RegFirstThenUnreg(t *testing.T) {
	Given(t).NameToIdx(InMemory, map[MappingName]MappingIdx{eth1: idx1}).
		When().Name(eth1).IsUnRegistered().
		Then().Name(eth1).MapsToNothing().
		And().Notification(eth1, Del).IsExpectedFor(idx1)
}

func Test03Eth0RegPlusEth1Unreg(t *testing.T) {
	Given(t).NameToIdx(InMemory, map[MappingName]MappingIdx{eth0: idx1, eth1: idx2}).
		When().Name(eth1).IsUnRegistered().
		Then().Name(eth1).MapsToNothing().
		And().Notification(eth1, Del).IsExpectedFor(idx2).
		And().Name(eth0).MapsTo(idx1).
		And().Notification(eth0, Write).IsNotExpected() //because watch is registered after given keyword
}

func Test04RegTwiceSameNameWithDifferentIdx(t *testing.T) {
	Given(t).NameToIdx(InMemory, nil).
		When().Name(eth1).IsRegistered(idx1).
		Then().Name(eth1).MapsTo(idx1). //Notif eth1, idx1
		And().Notification(eth1, Write).IsExpectedFor(idx1).
		When().Name(eth1).IsRegistered(idx2).
		Then().Name(eth1).MapsTo(idx2). //Notif eth1, idx1
		And().Notification(eth1, Write).IsExpectedFor(idx2)
}

const (
	flagMetaKey = "flag"
	valsMetaKey = "vals"
)

type metaInformation struct {
	flag bool
	vals []string
}

func createIdx(meta interface{}) map[string][]string {
	typed, ok := meta.(*metaInformation)
	if !ok {
		return nil
	}

	return map[string][]string{
		flagMetaKey: {strconv.FormatBool(typed.flag)},
		valsMetaKey: typed.vals,
	}
}

func TestIndexedMetadata(t *testing.T) {
	gomega.RegisterTestingT(t)
	idxm := NewNameToIdx(logroot.StandardLogger(), "plugin", "title", createIdx)

	res := idxm.LookupNameByMetadata(flagMetaKey, "true")
	gomega.Expect(res).To(gomega.BeNil())

	meta1 := &metaInformation{
		flag: true,
		vals: []string{"abc", "def", "xyz"},
	}
	meta2 := &metaInformation{
		flag: false,
		vals: []string{"abc", "klm", "opq"},
	}
	meta3 := &metaInformation{
		flag: true,
		vals: []string{"jkl"},
	}

	idxm.RegisterName(string(eth0), idx1, meta1)
	idxm.RegisterName(string(eth1), idx2, meta2)
	idxm.RegisterName(string(eth2), idx3, meta3)

	res = idxm.LookupNameByMetadata(flagMetaKey, "false")
	gomega.Expect(res).NotTo(gomega.BeNil())
	gomega.Expect(res[0]).To(gomega.BeEquivalentTo(eth1))

	res = idxm.LookupNameByMetadata(flagMetaKey, "true")
	gomega.Expect(len(res)).To(gomega.BeEquivalentTo(2))
	gomega.Expect(res).To(gomega.ContainElement(string(eth0)))
	gomega.Expect(res).To(gomega.ContainElement(string(eth2)))

	res = idxm.LookupNameByMetadata(valsMetaKey, "abc")
	gomega.Expect(len(res)).To(gomega.BeEquivalentTo(2))
	gomega.Expect(res).To(gomega.ContainElement(string(eth0)))
	gomega.Expect(res).To(gomega.ContainElement(string(eth1)))

	res = idxm.LookupNameByMetadata(valsMetaKey, "jkl")
	gomega.Expect(len(res)).To(gomega.BeEquivalentTo(1))
	gomega.Expect(res[0]).To(gomega.BeEquivalentTo(eth2))

	idxm.UnregisterName(string(eth0))
	res = idxm.LookupNameByMetadata(flagMetaKey, "true")
	gomega.Expect(len(res)).To(gomega.BeEquivalentTo(1))
	gomega.Expect(res[0]).To(gomega.BeEquivalentTo(eth2))

}

func TestOldIndexRemove(t *testing.T) {
	gomega.RegisterTestingT(t)
	idxm := NewNameToIdx(logroot.StandardLogger(), "plugin", "title", nil)

	idxm.RegisterName(string(eth0), idx1, nil)

	idx, _, found := idxm.LookupIdx(string(eth0))
	gomega.Expect(found).To(gomega.BeTrue())
	gomega.Expect(idx).To(gomega.BeEquivalentTo(idx1))

	name, _, found := idxm.LookupName(idx1)
	gomega.Expect(found).To(gomega.BeTrue())
	gomega.Expect(name).To(gomega.BeEquivalentTo(string(name)))

	idxm.RegisterName(string(eth0), idx2, nil)

	idx, _, found = idxm.LookupIdx(string(eth0))
	gomega.Expect(found).To(gomega.BeTrue())
	gomega.Expect(idx).To(gomega.BeEquivalentTo(idx2))

	name, _, found = idxm.LookupName(idx2)
	gomega.Expect(found).To(gomega.BeTrue())
	gomega.Expect(name).To(gomega.BeEquivalentTo(string(name)))

	name, _, found = idxm.LookupName(idx1)
	gomega.Expect(found).To(gomega.BeFalse())
	gomega.Expect(name).To(gomega.BeEquivalentTo(""))
}
<|endoftext|>


<commit_before>package dataProcess

import (
	"../autils"
	"database/sql"
	"github.com/gin-gonic/gin"
	"net/http"
	"strconv"
	"time"
)

type bsRowsInfo struct {
	Type string `json:"type"`
	Num  int    `json:"num"`
	Rate string `json:"rate"`
}

type browsersData struct {
	Columns []tStruct    `json:"columns"`
	Rows    []bsRowsInfo `json:"rows"`
}

// 作弊请求数据处理
func BrowswersCount(c *gin.Context, db *sql.DB) {
	position := "left"
	cd := browsersData{}

	date := c.Query("date")
	if date == "" {
		date = autils.GetCurrentData(time.Now().AddDate(0, 0, -1))
	}

	q, _ := c.Get("conditions")
	sDate := autils.AnaSigleDate(q)
	s := date
	if sDate != "" {
		s = sDate
	}
	cd.Columns = []tStruct{{
		"浏览器",
		"type",
		position,
	}, {
		"请求数",
		"num",
		position,
	}}

	infos, total := getBrowsersInfo(db, s)

	for i, v := range infos {
		infos[i].Rate = strconv.FormatFloat(float64(v.Num)/float64(total), 'f', 2, 64)
	}

	cd.Rows = infos

	c.JSON(http.StatusOK, gin.H{
		"status": 0,
		"msg":    "ok",
		"data":   cd,
	})
}

func getBrowsersInfo(db *sql.DB, date string) ([]bsRowsInfo, int) {
	sqlStr := "select type, num from browsers where date = '" + date + "' order by num desc"
	rows, err := db.Query(sqlStr)
	autils.ErrHadle(err)

	var name string
	var num int
	var total int
	cri := bsRowsInfo{}
	criArr := []bsRowsInfo{}
	for rows.Next() {
		err := rows.Scan(&name, &num)
		autils.ErrHadle(err)
		cri.Type = name
		cri.Num = num
		criArr = append(criArr, cri)
		total += num
	}
	err = rows.Err()
	autils.ErrHadle(err)

	defer rows.Close()
	return criArr, total
}
<commit_msg>update rate.<commit_after>package dataProcess

import (
	"../autils"
	"database/sql"
	"github.com/gin-gonic/gin"
	"net/http"
	"strconv"
	"time"
)

type bsRowsInfo struct {
	Type string `json:"type"`
	Num  int    `json:"num"`
	Rate string `json:"rate"`
}

type browsersData struct {
	Columns []tStruct    `json:"columns"`
	Rows    []bsRowsInfo `json:"rows"`
}

// 作弊请求数据处理
func BrowswersCount(c *gin.Context, db *sql.DB) {
	position := "left"
	cd := browsersData{}

	date := c.Query("date")
	if date == "" {
		date = autils.GetCurrentData(time.Now().AddDate(0, 0, -1))
	}

	q, _ := c.Get("conditions")
	sDate := autils.AnaSigleDate(q)
	s := date
	if sDate != "" {
		s = sDate
	}
	cd.Columns = []tStruct{{
		"浏览器",
		"type",
		position,
	}, {
		"请求数",
		"num",
		position,
	}}

	infos, total := getBrowsersInfo(db, s)

	for i, v := range infos {
		infos[i].Rate = strconv.FormatFloat(float64(v.Num)/float64(total)*100, 'f', 4, 64)
	}

	cd.Rows = infos

	c.JSON(http.StatusOK, gin.H{
		"status": 0,
		"msg":    "ok",
		"data":   cd,
	})
}

func getBrowsersInfo(db *sql.DB, date string) ([]bsRowsInfo, int) {
	sqlStr := "select type, num from browsers where date = '" + date + "' order by num desc"
	rows, err := db.Query(sqlStr)
	autils.ErrHadle(err)

	var name string
	var num int
	var total int
	cri := bsRowsInfo{}
	criArr := []bsRowsInfo{}
	for rows.Next() {
		err := rows.Scan(&name, &num)
		autils.ErrHadle(err)
		cri.Type = name
		cri.Num = num
		criArr = append(criArr, cri)
		total += num
	}
	err = rows.Err()
	autils.ErrHadle(err)

	defer rows.Close()
	return criArr, total
}
<|endoftext|>


<commit_before>package native

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"syscall"
	"time"

	"github.com/itchio/httpkit/neterr"

	"github.com/itchio/pelican"

	"github.com/itchio/dash"

	"github.com/itchio/butler/butlerd/messages"
	"github.com/itchio/butler/filtering"
	"github.com/itchio/butler/installer"
	"github.com/itchio/butler/mansion"

	"github.com/itchio/butler/butlerd"
	"github.com/itchio/butler/cmd/elevate"
	"github.com/itchio/butler/cmd/wipe"
	"github.com/itchio/butler/endpoints/launch"
	"github.com/itchio/smaug/runner"
	"github.com/pkg/errors"
)

func Register() {
	launch.RegisterLauncher(launch.LaunchStrategyNative, &Launcher{})
}

type Launcher struct{}

var _ launch.Launcher = (*Launcher)(nil)

func (l *Launcher) Do(params launch.LauncherParams) error {
	consumer := params.RequestContext.Consumer
	installFolder := params.InstallFolder

	cwd := installFolder
	_, err := filepath.Rel(installFolder, params.FullTargetPath)
	if err == nil {
		// if it's relative, set the cwd to the folder the
		// target is in
		cwd = filepath.Dir(params.FullTargetPath)
	}

	_, err = os.Stat(params.FullTargetPath)
	if err != nil {
		return errors.WithStack(err)
	}

	err = configureTargetIfNeeded(params)
	if err != nil {
		consumer.Warnf("Could not configure launch target: %s", err.Error())
	}

	err = fillPeInfoIfNeeded(params)
	if err != nil {
		consumer.Warnf("Could not determine PE info: %s", err.Error())
	}

	err = handlePrereqs(params)
	if err != nil {
		if be, ok := butlerd.AsButlerdError(err); ok {
			switch butlerd.Code(be.RpcErrorCode()) {
			case butlerd.CodeOperationAborted, butlerd.CodeOperationCancelled:
				return be
			}
		}

		consumer.Warnf("While handling prereqs: %+v", err)

		if neterr.IsNetworkError(err) {
			err = butlerd.CodeNetworkDisconnected
		}

		r, err := messages.PrereqsFailed.Call(params.RequestContext, butlerd.PrereqsFailedParams{
			Error:      err.Error(),
			ErrorStack: fmt.Sprintf("%+v", err),
		})
		if err != nil {
			return errors.WithStack(err)
		}

		if r.Continue {
			// continue!
			consumer.Warnf("Continuing after prereqs failure because user told us to")
		} else {
			// abort
			consumer.Warnf("Giving up after prereqs failure because user asked us to")
			return errors.WithStack(butlerd.CodeOperationAborted)
		}
	}

	envMap := make(map[string]string)
	for k, v := range params.Env {
		envMap[k] = v
	}

	// give the app its own temporary directory
	tempDir := filepath.Join(params.InstallFolder, ".itch", "temp")
	err = os.MkdirAll(tempDir, 0755)
	if err != nil {
		consumer.Warnf("Could not make temporary directory: %s", err.Error())
	} else {
		defer wipe.Do(consumer, tempDir)
		envMap["TMP"] = tempDir
		envMap["TEMP"] = tempDir
		consumer.Infof("Giving app temp dir (%s)", tempDir)
	}

	var envKeys []string
	for k := range envMap {
		envKeys = append(envKeys, k)
	}
	consumer.Infof("Environment variables passed: %s", strings.Join(envKeys, ", "))

	// TODO: sanitize environment somewhat?
	envBlock := os.Environ()
	for k, v := range envMap {
		envBlock = append(envBlock, fmt.Sprintf("%s=%s", k, v))
	}

	const maxLines = 40
	stdout := newOutputCollector(maxLines)
	stderr := newOutputCollector(maxLines)

	fullTargetPath := params.FullTargetPath
	name := params.FullTargetPath
	args := params.Args

	if params.Candidate != nil && params.Candidate.Flavor == dash.FlavorLove {
		// TODO: add prereqs when that happens
		args = append([]string{name}, args...)
		name = "love"
		fullTargetPath = "love"
	}

	runParams := &runner.RunnerParams{
		Consumer: consumer,
		Ctx:      params.Ctx,

		Sandbox: params.Sandbox,

		FullTargetPath: fullTargetPath,

		Name:   name,
		Dir:    cwd,
		Args:   args,
		Env:    envBlock,
		Stdout: stdout,
		Stderr: stderr,

		InstallFolder: params.InstallFolder,
		Runtime:       params.Runtime,

		AttachParams:   l.AttachParams(params),
		FirejailParams: l.FirejailParams(params),
		FujiParams:     l.FujiParams(params),
	}

	run, err := runner.GetRunner(runParams)
	if err != nil {
		return errors.WithStack(err)
	}

	err = run.Prepare()
	if err != nil {
		return errors.WithStack(err)
	}

	err = func() error {
		startTime := time.Now()

		messages.LaunchRunning.Notify(params.RequestContext, butlerd.LaunchRunningNotification{})
		exitCode, err := interpretRunError(run.Run())
		messages.LaunchExited.Notify(params.RequestContext, butlerd.LaunchExitedNotification{})
		if err != nil {
			return errors.WithStack(err)
		}

		runDuration := time.Since(startTime)
		err = params.RecordPlayTime(runDuration)
		if err != nil {
			return errors.WithStack(err)
		}

		if exitCode != 0 {
			var signedExitCode = int64(exitCode)
			if runtime.GOOS == "windows" {
				// Windows uses 32-bit unsigned integers as exit codes, although the
				// command interpreter treats them as signed. If a process fails
				// initialization, a Windows system error code may be returned.
				signedExitCode = int64(int32(signedExitCode))

				// The line above turns `4294967295` into -1
			}

			exeName := filepath.Base(params.FullTargetPath)
			msg := fmt.Sprintf("Exit code 0x%x (%d) for (%s)", uint32(exitCode), signedExitCode, exeName)
			consumer.Warnf(msg)

			if runDuration.Seconds() > 10 {
				consumer.Warnf("That's after running for %s, ignoring non-zero exit code", runDuration)
			} else {
				return errors.New(msg)
			}
		}

		return nil
	}()

	if err != nil {
		consumer.Errorf("Had error: %s", err.Error())
		if len(stderr.Lines()) == 0 {
			consumer.Errorf("No messages for standard error")
			consumer.Errorf("→ Standard error: empty")
		} else {
			consumer.Errorf("→ Standard error ================")
			for _, l := range stderr.Lines() {
				consumer.Errorf("  %s", l)
			}
			consumer.Errorf("=================================")
		}

		if len(stdout.Lines()) == 0 {
			consumer.Errorf("→ Standard output: empty")
		} else {
			consumer.Errorf("→ Standard output ===============")
			for _, l := range stdout.Lines() {
				consumer.Errorf("  %s", l)
			}
			consumer.Errorf("=================================")
		}
		consumer.Errorf("Relaying launch failure.")
		return errors.WithStack(err)
	}

	return nil
}

func (l *Launcher) FirejailParams(params launch.LauncherParams) runner.FirejailParams {
	name := fmt.Sprintf("firejail-%s", params.Runtime.Arch())
	binaryPath := filepath.Join(params.PrereqsDir, name, "firejail")
	return runner.FirejailParams{
		BinaryPath: binaryPath,
	}
}

func (l *Launcher) FujiParams(params launch.LauncherParams) runner.FujiParams {
	consumer := params.RequestContext.Consumer

	return runner.FujiParams{
		Settings: mansion.GetFujiSettings(),
		PerformElevatedSetup: func() error {
			r, err := messages.AllowSandboxSetup.Call(params.RequestContext, butlerd.AllowSandboxSetupParams{})
			if err != nil {
				return errors.WithStack(err)
			}

			if !r.Allow {
				return errors.WithStack(butlerd.CodeOperationAborted)
			}
			consumer.Infof("Proceeding with sandbox setup...")

			res, err := installer.RunSelf(&installer.RunSelfParams{
				Consumer: consumer,
				Args: []string{
					"--elevate",
					"fuji",
					"setup",
				},
			})
			if err != nil {
				return errors.WithStack(err)
			}

			if res.ExitCode != 0 {
				if res.ExitCode == elevate.ExitCodeAccessDenied {
					return errors.WithStack(butlerd.CodeOperationAborted)
				}
			}

			err = installer.CheckExitCode(res.ExitCode, err)
			if err != nil {
				return errors.WithStack(err)
			}
			return nil
		},
	}
}

func (l *Launcher) AttachParams(params launch.LauncherParams) runner.AttachParams {
	return runner.AttachParams{
		BringWindowToForeground: func(hwnd int64) {
			setWindowForeground(hwnd)
		},
	}
}

func configureTargetIfNeeded(params launch.LauncherParams) error {
	if params.Candidate != nil {
		// already configured
		return nil
	}

	v, err := dash.Configure(params.FullTargetPath, &dash.ConfigureParams{
		Consumer: params.RequestContext.Consumer,
		Filter:   filtering.FilterPaths,
	})
	if err != nil {
		return errors.WithStack(err)
	}

	if len(v.Candidates) == 0 {
		return errors.Errorf("0 candidates after configure")
	}

	params.Candidate = v.Candidates[0]
	return nil
}

func fillPeInfoIfNeeded(params launch.LauncherParams) error {
	c := params.Candidate
	if c == nil {
		// no candidate for some reason?
		return nil
	}

	if c.Flavor != dash.FlavorNativeWindows {
		// not an .exe, ignore
		return nil
	}

	var err error
	f, err := os.Open(params.FullTargetPath)
	if err != nil {
		return errors.WithStack(err)
	}
	defer f.Close()

	params.PeInfo, err = pelican.Probe(f, &pelican.ProbeParams{
		Consumer: params.RequestContext.Consumer,
	})
	if err != nil {
		return errors.WithStack(err)
	}

	return nil
}

func interpretRunError(err error) (int, error) {
	if err != nil {
		if exitError, ok := AsExitError(err); ok {
			if status, ok := exitError.Sys().(syscall.WaitStatus); ok {
				return status.ExitStatus(), nil
			}
		}

		return 127, err
	}

	return 0, nil
}

type causer interface {
	Cause() error
}

func AsExitError(err error) (*exec.ExitError, bool) {
	if err == nil {
		return nil, false
	}

	if se, ok := err.(causer); ok {
		return AsExitError(se.Cause())
	}

	if ee, ok := err.(*exec.ExitError); ok {
		return ee, true
	}

	return nil, false
}
<commit_msg>Set ITCHIO_SANDBOX to 1 if we're launching with the sandbox<commit_after>package native

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"syscall"
	"time"

	"github.com/itchio/httpkit/neterr"

	"github.com/itchio/pelican"

	"github.com/itchio/dash"

	"github.com/itchio/butler/butlerd/messages"
	"github.com/itchio/butler/filtering"
	"github.com/itchio/butler/installer"
	"github.com/itchio/butler/mansion"

	"github.com/itchio/butler/butlerd"
	"github.com/itchio/butler/cmd/elevate"
	"github.com/itchio/butler/cmd/wipe"
	"github.com/itchio/butler/endpoints/launch"
	"github.com/itchio/smaug/runner"
	"github.com/pkg/errors"
)

func Register() {
	launch.RegisterLauncher(launch.LaunchStrategyNative, &Launcher{})
}

type Launcher struct{}

var _ launch.Launcher = (*Launcher)(nil)

func (l *Launcher) Do(params launch.LauncherParams) error {
	consumer := params.RequestContext.Consumer
	installFolder := params.InstallFolder

	cwd := installFolder
	_, err := filepath.Rel(installFolder, params.FullTargetPath)
	if err == nil {
		// if it's relative, set the cwd to the folder the
		// target is in
		cwd = filepath.Dir(params.FullTargetPath)
	}

	_, err = os.Stat(params.FullTargetPath)
	if err != nil {
		return errors.WithStack(err)
	}

	err = configureTargetIfNeeded(params)
	if err != nil {
		consumer.Warnf("Could not configure launch target: %s", err.Error())
	}

	err = fillPeInfoIfNeeded(params)
	if err != nil {
		consumer.Warnf("Could not determine PE info: %s", err.Error())
	}

	err = handlePrereqs(params)
	if err != nil {
		if be, ok := butlerd.AsButlerdError(err); ok {
			switch butlerd.Code(be.RpcErrorCode()) {
			case butlerd.CodeOperationAborted, butlerd.CodeOperationCancelled:
				return be
			}
		}

		consumer.Warnf("While handling prereqs: %+v", err)

		if neterr.IsNetworkError(err) {
			err = butlerd.CodeNetworkDisconnected
		}

		r, err := messages.PrereqsFailed.Call(params.RequestContext, butlerd.PrereqsFailedParams{
			Error:      err.Error(),
			ErrorStack: fmt.Sprintf("%+v", err),
		})
		if err != nil {
			return errors.WithStack(err)
		}

		if r.Continue {
			// continue!
			consumer.Warnf("Continuing after prereqs failure because user told us to")
		} else {
			// abort
			consumer.Warnf("Giving up after prereqs failure because user asked us to")
			return errors.WithStack(butlerd.CodeOperationAborted)
		}
	}

	envMap := make(map[string]string)
	for k, v := range params.Env {
		envMap[k] = v
	}

	// give the app its own temporary directory
	tempDir := filepath.Join(params.InstallFolder, ".itch", "temp")
	err = os.MkdirAll(tempDir, 0755)
	if err != nil {
		consumer.Warnf("Could not make temporary directory: %s", err.Error())
	} else {
		defer wipe.Do(consumer, tempDir)
		envMap["TMP"] = tempDir
		envMap["TEMP"] = tempDir
		consumer.Infof("Giving app temp dir (%s)", tempDir)
	}

	if params.Sandbox {
		envMap["ITCHIO_SANDBOX"] = "1"
	}

	var envKeys []string
	for k := range envMap {
		envKeys = append(envKeys, k)
	}
	consumer.Infof("Environment variables passed: %s", strings.Join(envKeys, ", "))

	// TODO: sanitize environment somewhat?
	envBlock := os.Environ()
	for k, v := range envMap {
		envBlock = append(envBlock, fmt.Sprintf("%s=%s", k, v))

	const maxLines = 40
	stdout := newOutputCollector(maxLines)
	stderr := newOutputCollector(maxLines)

	fullTargetPath := params.FullTargetPath
	name := params.FullTargetPath
	args := params.Args

	if params.Candidate != nil && params.Candidate.Flavor == dash.FlavorLove {
		// TODO: add prereqs when that happens
		args = append([]string{name}, args...)
		name = "love"
		fullTargetPath = "love"
	}

	runParams := &runner.RunnerParams{
		Consumer: consumer,
		Ctx:      params.Ctx,

		Sandbox: params.Sandbox,

		FullTargetPath: fullTargetPath,

		Name:   name,
		Dir:    cwd,
		Args:   args,
		Env:    envBlock,
		Stdout: stdout,
		Stderr: stderr,

		InstallFolder: params.InstallFolder,
		Runtime:       params.Runtime,

		AttachParams:   l.AttachParams(params),
		FirejailParams: l.FirejailParams(params),
		FujiParams:     l.FujiParams(params),
	}

	run, err := runner.GetRunner(runParams)
	if err != nil {
		return errors.WithStack(err)
	}

	err = run.Prepare()
	if err != nil {
		return errors.WithStack(err)
	}

	err = func() error {
		startTime := time.Now()

		messages.LaunchRunning.Notify(params.RequestContext, butlerd.LaunchRunningNotification{})
		exitCode, err := interpretRunError(run.Run())
		messages.LaunchExited.Notify(params.RequestContext, butlerd.LaunchExitedNotification{})
		if err != nil {
			return errors.WithStack(err)
		}

		runDuration := time.Since(startTime)
		err = params.RecordPlayTime(runDuration)
		if err != nil {
			return errors.WithStack(err)
		}

		if exitCode != 0 {
			var signedExitCode = int64(exitCode)
			if runtime.GOOS == "windows" {
				// Windows uses 32-bit unsigned integers as exit codes, although the
				// command interpreter treats them as signed. If a process fails
				// initialization, a Windows system error code may be returned.
				signedExitCode = int64(int32(signedExitCode))

				// The line above turns `4294967295` into -1
			}

			exeName := filepath.Base(params.FullTargetPath)
			msg := fmt.Sprintf("Exit code 0x%x (%d) for (%s)", uint32(exitCode), signedExitCode, exeName)
			consumer.Warnf(msg)

			if runDuration.Seconds() > 10 {
				consumer.Warnf("That's after running for %s, ignoring non-zero exit code", runDuration)
			} else {
				return errors.New(msg)
			}
		}

		return nil
	}()

	if err != nil {
		consumer.Errorf("Had error: %s", err.Error())
		if len(stderr.Lines()) == 0 {
			consumer.Errorf("No messages for standard error")
			consumer.Errorf("→ Standard error: empty")
		} else {
			consumer.Errorf("→ Standard error ================")
			for _, l := range stderr.Lines() {
				consumer.Errorf("  %s", l)
			}
			consumer.Errorf("=================================")
		}

		if len(stdout.Lines()) == 0 {
			consumer.Errorf("→ Standard output: empty")
		} else {
			consumer.Errorf("→ Standard output ===============")
			for _, l := range stdout.Lines() {
				consumer.Errorf("  %s", l)
			}
			consumer.Errorf("=================================")
		}
		consumer.Errorf("Relaying launch failure.")
		return errors.WithStack(err)
	}

	return nil
}

func (l *Launcher) FirejailParams(params launch.LauncherParams) runner.FirejailParams {
	name := fmt.Sprintf("firejail-%s", params.Runtime.Arch())
	binaryPath := filepath.Join(params.PrereqsDir, name, "firejail")
	return runner.FirejailParams{
		BinaryPath: binaryPath,
	}
}

func (l *Launcher) FujiParams(params launch.LauncherParams) runner.FujiParams {
	consumer := params.RequestContext.Consumer

	return runner.FujiParams{
		Settings: mansion.GetFujiSettings(),
		PerformElevatedSetup: func() error {
			r, err := messages.AllowSandboxSetup.Call(params.RequestContext, butlerd.AllowSandboxSetupParams{})
			if err != nil {
				return errors.WithStack(err)
			}

			if !r.Allow {
				return errors.WithStack(butlerd.CodeOperationAborted)
			}
			consumer.Infof("Proceeding with sandbox setup...")

			res, err := installer.RunSelf(&installer.RunSelfParams{
				Consumer: consumer,
				Args: []string{
					"--elevate",
					"fuji",
					"setup",
				},
			})
			if err != nil {
				return errors.WithStack(err)
			}

			if res.ExitCode != 0 {
				if res.ExitCode == elevate.ExitCodeAccessDenied {
					return errors.WithStack(butlerd.CodeOperationAborted)
				}
			}

			err = installer.CheckExitCode(res.ExitCode, err)
			if err != nil {
				return errors.WithStack(err)
			}
			return nil
		},
	}
}

func (l *Launcher) AttachParams(params launch.LauncherParams) runner.AttachParams {
	return runner.AttachParams{
		BringWindowToForeground: func(hwnd int64) {
			setWindowForeground(hwnd)
		},
	}
}

func configureTargetIfNeeded(params launch.LauncherParams) error {
	if params.Candidate != nil {
		// already configured
		return nil
	}

	v, err := dash.Configure(params.FullTargetPath, &dash.ConfigureParams{
		Consumer: params.RequestContext.Consumer,
		Filter:   filtering.FilterPaths,
	})
	if err != nil {
		return errors.WithStack(err)
	}

	if len(v.Candidates) == 0 {
		return errors.Errorf("0 candidates after configure")
	}

	params.Candidate = v.Candidates[0]
	return nil
}

func fillPeInfoIfNeeded(params launch.LauncherParams) error {
	c := params.Candidate
	if c == nil {
		// no candidate for some reason?
		return nil
	}

	if c.Flavor != dash.FlavorNativeWindows {
		// not an .exe, ignore
		return nil
	}

	var err error
	f, err := os.Open(params.FullTargetPath)
	if err != nil {
		return errors.WithStack(err)
	}
	defer f.Close()

	params.PeInfo, err = pelican.Probe(f, &pelican.ProbeParams{
		Consumer: params.RequestContext.Consumer,
	})
	if err != nil {
		return errors.WithStack(err)
	}

	return nil
}

func interpretRunError(err error) (int, error) {
	if err != nil {
		if exitError, ok := AsExitError(err); ok {
			if status, ok := exitError.Sys().(syscall.WaitStatus); ok {
				return status.ExitStatus(), nil
			}
		}

		return 127, err
	}

	return 0, nil
}

type causer interface {
	Cause() error
}

func AsExitError(err error) (*exec.ExitError, bool) {
	if err == nil {
		return nil, false
	}

	if se, ok := err.(causer); ok {
		return AsExitError(se.Cause())
	}

	if ee, ok := err.(*exec.ExitError); ok {
		return ee, true
	}

	return nil, false
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"log"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"github.com/coreos/go-etcd/etcd"
	"github.com/spf13/cobra"
)

type serviceAnnouncement struct {
	Path     string
	etcd     *etcd.Client
	Data     string
	TTL      uint64
	Interval time.Duration
	Check    string
}

// XXX: when process exits should we remove the key from etcd? configurable via flag?

func runAnnounce(cmd *cobra.Command, args []string) {

	if len(args) != 1 {
		log.Fatal("need a service name")
	}

	if announceTTL != 0 && announceTTL < announceInterval {
		log.Fatal("announce ttl must be greater than interval")
	}

	svc := strings.ToLower(args[0])

	// need better validation of name
	if len(svc) == 0 {
		log.Fatal("empty service name")
	}

	name, err := getNodeName()
	if err != nil {
		log.Fatal(err)
	}

	data, err := json.Marshal(&record{
		Port:     uint16(announcePort),
		Weight:   uint16(announceWeight),
		Target:   name,
		Priority: uint16(announcePriority),
	})

	if err != nil {
		log.Fatal("json failure: %s", err)
	}

	a := &serviceAnnouncement{
		Check:    announceCheck,
		Data:     string(data),
		Interval: time.Duration(announceInterval) * time.Second,
		Path:     filepath.Join("/", etcdPrefix, "services", svc, name),
		TTL:      uint64(announceTTL),
		etcd:     etcd.NewClient(([]string{etcdAddress})),
	}

	a.announce()
	for _ = range time.Tick(a.Interval) {
		a.announce()
	}
}

//TODO: run check command
func (a *serviceAnnouncement) announce() {

	if a.Check != "" {
		// should we wrap in a timeout?
		c := exec.Command("/bin/sh", "-c", a.Check)
		output, err := c.CombinedOutput()
		if err != nil {
			// should failure immediately remove the entry or should we let ttl timeout?
			// do rise/fall style checks?
			log.Printf("failed to run '%s' : %s : '%s'", a.Check, err, output)
			return
		}
	}

	_, err := a.etcd.Set(a.Path, a.Data, a.TTL)
	if err != nil {
		log.Printf("failed to set %s : %s", a.Path, err)
	}
}
<commit_msg>use path rather than filepath.<commit_after>package main

import (
	"encoding/json"
	"log"
	"os/exec"
	"path"
	"strings"
	"time"

	"github.com/coreos/go-etcd/etcd"
	"github.com/spf13/cobra"
)

type serviceAnnouncement struct {
	Path     string
	etcd     *etcd.Client
	Data     string
	TTL      uint64
	Interval time.Duration
	Check    string
}

// XXX: when process exits should we remove the key from etcd? configurable via flag?

func runAnnounce(cmd *cobra.Command, args []string) {

	if len(args) != 1 {
		log.Fatal("need a service name")
	}

	if announceTTL != 0 && announceTTL < announceInterval {
		log.Fatal("announce ttl must be greater than interval")
	}

	svc := strings.ToLower(args[0])

	// need better validation of name
	if len(svc) == 0 {
		log.Fatal("empty service name")
	}

	name, err := getNodeName()
	if err != nil {
		log.Fatal(err)
	}

	data, err := json.Marshal(&record{
		Port:     uint16(announcePort),
		Weight:   uint16(announceWeight),
		Target:   name,
		Priority: uint16(announcePriority),
	})

	if err != nil {
		log.Fatal("json failure: %s", err)
	}

	a := &serviceAnnouncement{
		Check:    announceCheck,
		Data:     string(data),
		Interval: time.Duration(announceInterval) * time.Second,
		Path:     path.Join("/", etcdPrefix, "services", svc, name),
		TTL:      uint64(announceTTL),
		etcd:     etcd.NewClient(([]string{etcdAddress})),
	}

	a.announce()
	for _ = range time.Tick(a.Interval) {
		a.announce()
	}
}

//TODO: run check command
func (a *serviceAnnouncement) announce() {

	if a.Check != "" {
		// should we wrap in a timeout?
		c := exec.Command("/bin/sh", "-c", a.Check)
		output, err := c.CombinedOutput()
		if err != nil {
			// should failure immediately remove the entry or should we let ttl timeout?
			// do rise/fall style checks?
			log.Printf("failed to run '%s' : %s : '%s'", a.Check, err, output)
			return
		}
	}

	_, err := a.etcd.Set(a.Path, a.Data, a.TTL)
	if err != nil {
		log.Printf("failed to set %s : %s", a.Path, err)
	}
}
<|endoftext|>


<commit_before>package api

import (
	"fmt"
	"sync"
	"time"
)

const (
	// DefaultLockSessionName is the Session Name we assign if none is provided
	DefaultLockSessionName = "Consul API Lock"

	// DefaultLockSessionTTL is the default session TTL if no Session is provided
	// when creating a new Lock. This is used because we do not have another
	// other check to depend upon.
	DefaultLockSessionTTL = "15s"

	// DefaultLockWaitTime is how long we block for at a time to check if lock
	// acquisition is possible. This affects the minimum time it takes to cancel
	// a Lock acquisition.
	DefaultLockWaitTime = 15 * time.Second

	// DefaultLockRetryTime is how long we wait after a failed lock acquisition
	// before attempting to do the lock again. This is so that once a lock-delay
	// is in effect, we do not hot loop retrying the acquisition.
	DefaultLockRetryTime = 5 * time.Second

	// DefaultMonitorRetryTime is how long we wait after a failed monitor check
	// of a lock (500 response code). This allows the monitor to ride out brief
	// periods of unavailability, subject to the MonitorRetries setting in the
	// lock options which is by default set to 0, disabling this feature. This
	// affects locks and semaphores.
	DefaultMonitorRetryTime = 2 * time.Second

	// LockFlagValue is a magic flag we set to indicate a key
	// is being used for a lock. It is used to detect a potential
	// conflict with a semaphore.
	LockFlagValue = 0x2ddccbc058a50c18
)

var (
	// ErrLockHeld is returned if we attempt to double lock
	ErrLockHeld = fmt.Errorf("Lock already held")

	// ErrLockNotHeld is returned if we attempt to unlock a lock
	// that we do not hold.
	ErrLockNotHeld = fmt.Errorf("Lock not held")

	// ErrLockInUse is returned if we attempt to destroy a lock
	// that is in use.
	ErrLockInUse = fmt.Errorf("Lock in use")

	// ErrLockConflict is returned if the flags on a key
	// used for a lock do not match expectation
	ErrLockConflict = fmt.Errorf("Existing key does not match lock use")
)

// Lock is used to implement client-side leader election. It is follows the
// algorithm as described here: https://www.consul.io/docs/guides/leader-election.html.
type Lock struct {
	c    *Client
	opts *LockOptions

	isHeld       bool
	sessionRenew chan struct{}
	lockSession  string
	l            sync.Mutex
}

// LockOptions is used to parameterize the Lock behavior.
type LockOptions struct {
	Key              string        // Must be set and have write permissions
	Value            []byte        // Optional, value to associate with the lock
	Session          string        // Optional, created if not specified
	SessionOpts      *SessionEntry // Optional, options to use when creating a session
	SessionName      string        // Optional, defaults to DefaultLockSessionName (ignored if SessionOpts is given)
	SessionTTL       string        // Optional, defaults to DefaultLockSessionTTL (ignored if SessionOpts is given)
	MonitorRetries   int           // Optional, defaults to 0 which means no retries
	MonitorRetryTime time.Duration // Optional, defaults to DefaultMonitorRetryTime
	LockWaitTime     time.Duration // Optional, defaults to DefaultLockWaitTime
	LockTryOnce      bool          // Optional, defaults to false which means try forever
	Namespace        string        `json:",omitempty"` // Optional, defaults to API client config, namespace of ACL token, or "default" namespace
}

// LockKey returns a handle to a lock struct which can be used
// to acquire and release the mutex. The key used must have
// write permissions.
func (c *Client) LockKey(key string) (*Lock, error) {
	opts := &LockOptions{
		Key: key,
	}
	return c.LockOpts(opts)
}

// LockOpts returns a handle to a lock struct which can be used
// to acquire and release the mutex. The key used must have
// write permissions.
func (c *Client) LockOpts(opts *LockOptions) (*Lock, error) {
	if opts.Key == "" {
		return nil, fmt.Errorf("missing key")
	}
	if opts.SessionName == "" {
		opts.SessionName = DefaultLockSessionName
	}
	if opts.SessionTTL == "" {
		opts.SessionTTL = DefaultLockSessionTTL
	} else {
		if _, err := time.ParseDuration(opts.SessionTTL); err != nil {
			return nil, fmt.Errorf("invalid SessionTTL: %v", err)
		}
	}
	if opts.MonitorRetryTime == 0 {
		opts.MonitorRetryTime = DefaultMonitorRetryTime
	}
	if opts.LockWaitTime == 0 {
		opts.LockWaitTime = DefaultLockWaitTime
	}
	l := &Lock{
		c:    c,
		opts: opts,
	}
	return l, nil
}

// Lock attempts to acquire the lock and blocks while doing so.
// Providing a non-nil stopCh can be used to abort the lock attempt.
// Returns a channel that is closed if our lock is lost or an error.
// This channel could be closed at any time due to session invalidation,
// communication errors, operator intervention, etc. It is NOT safe to
// assume that the lock is held until Unlock() unless the Session is specifically
// created without any associated health checks. By default Consul sessions
// prefer liveness over safety and an application must be able to handle
// the lock being lost.
func (l *Lock) Lock(stopCh <-chan struct{}) (<-chan struct{}, error) {
	// Hold the lock as we try to acquire
	l.l.Lock()
	defer l.l.Unlock()

	// Check if we already hold the lock
	if l.isHeld {
		return nil, ErrLockHeld
	}

	wOpts := WriteOptions{
		Namespace: l.opts.Namespace,
	}

	// Check if we need to create a session first
	l.lockSession = l.opts.Session
	if l.lockSession == "" {
		s, err := l.createSession()
		if err != nil {
			return nil, fmt.Errorf("failed to create session: %v", err)
		}

		l.sessionRenew = make(chan struct{})
		l.lockSession = s

		session := l.c.Session()
		go session.RenewPeriodic(l.opts.SessionTTL, s, &wOpts, l.sessionRenew)

		// If we fail to acquire the lock, cleanup the session
		defer func() {
			if !l.isHeld {
				close(l.sessionRenew)
				l.sessionRenew = nil
			}
		}()
	}

	// Setup the query options
	kv := l.c.KV()
	qOpts := QueryOptions{
		WaitTime:  l.opts.LockWaitTime,
		Namespace: l.opts.Namespace,
	}

	start := time.Now()
	attempts := 0
WAIT:
	// Check if we should quit
	select {
	case <-stopCh:
		return nil, nil
	default:
	}

	// Handle the one-shot mode.
	if l.opts.LockTryOnce && attempts > 0 {
		elapsed := time.Since(start)
		if elapsed > l.opts.LockWaitTime {
			return nil, nil
		}

		// Query wait time should not exceed the lock wait time
		qOpts.WaitTime = l.opts.LockWaitTime - elapsed
	}
	attempts++

	// Look for an existing lock, blocking until not taken
	pair, meta, err := kv.Get(l.opts.Key, &qOpts)
	if err != nil {
		return nil, fmt.Errorf("failed to read lock: %v", err)
	}
	if pair != nil && pair.Flags != LockFlagValue {
		return nil, ErrLockConflict
	}
	locked := false
	if pair != nil && pair.Session == l.lockSession {
		goto HELD
	}
	if pair != nil && pair.Session != "" {
		qOpts.WaitIndex = meta.LastIndex
		goto WAIT
	}

	// Try to acquire the lock
	pair = l.lockEntry(l.lockSession)

	locked, _, err = kv.Acquire(pair, &wOpts)
	if err != nil {
		return nil, fmt.Errorf("failed to acquire lock: %v", err)
	}

	// Handle the case of not getting the lock
	if !locked {
		// Determine why the lock failed
		qOpts.WaitIndex = 0
		pair, meta, err = kv.Get(l.opts.Key, &qOpts)
		if err != nil {
			return nil, err
		}
		if pair != nil && pair.Session != "" {
			//If the session is not null, this means that a wait can safely happen
			//using a long poll
			qOpts.WaitIndex = meta.LastIndex
			goto WAIT
		} else {
			// If the session is empty and the lock failed to acquire, then it means
			// a lock-delay is in effect and a timed wait must be used
			select {
			case <-time.After(DefaultLockRetryTime):
				goto WAIT
			case <-stopCh:
				return nil, nil
			}
		}
	}

HELD:
	// Watch to ensure we maintain leadership
	leaderCh := make(chan struct{})
	go l.monitorLock(l.lockSession, leaderCh)

	// Set that we own the lock
	l.isHeld = true

	// Locked! All done
	return leaderCh, nil
}

// Unlock released the lock. It is an error to call this
// if the lock is not currently held.
func (l *Lock) Unlock() error {
	// Hold the lock as we try to release
	l.l.Lock()
	defer l.l.Unlock()

	// Ensure the lock is actually held
	if !l.isHeld {
		return ErrLockNotHeld
	}

	// Set that we no longer own the lock
	l.isHeld = false

	// Stop the session renew
	if l.sessionRenew != nil {
		defer func() {
			close(l.sessionRenew)
			l.sessionRenew = nil
		}()
	}

	// Get the lock entry, and clear the lock session
	lockEnt := l.lockEntry(l.lockSession)
	l.lockSession = ""

	// Release the lock explicitly
	kv := l.c.KV()
	w := WriteOptions{Namespace: l.opts.Namespace}

	_, _, err := kv.Release(lockEnt, &w)
	if err != nil {
		return fmt.Errorf("failed to release lock: %v", err)
	}
	return nil
}

// Destroy is used to cleanup the lock entry. It is not necessary
// to invoke. It will fail if the lock is in use.
func (l *Lock) Destroy() error {
	// Hold the lock as we try to release
	l.l.Lock()
	defer l.l.Unlock()

	// Check if we already hold the lock
	if l.isHeld {
		return ErrLockHeld
	}

	// Look for an existing lock
	kv := l.c.KV()
	q := QueryOptions{Namespace: l.opts.Namespace}

	pair, _, err := kv.Get(l.opts.Key, &q)
	if err != nil {
		return fmt.Errorf("failed to read lock: %v", err)
	}

	// Nothing to do if the lock does not exist
	if pair == nil {
		return nil
	}

	// Check for possible flag conflict
	if pair.Flags != LockFlagValue {
		return ErrLockConflict
	}

	// Check if it is in use
	if pair.Session != "" {
		return ErrLockInUse
	}

	// Attempt the delete
	w := WriteOptions{Namespace: l.opts.Namespace}
	didRemove, _, err := kv.DeleteCAS(pair, &w)
	if err != nil {
		return fmt.Errorf("failed to remove lock: %v", err)
	}
	if !didRemove {
		return ErrLockInUse
	}
	return nil
}

// createSession is used to create a new managed session
func (l *Lock) createSession() (string, error) {
	session := l.c.Session()
	se := l.opts.SessionOpts
	if se == nil {
		se = &SessionEntry{
			Name: l.opts.SessionName,
			TTL:  l.opts.SessionTTL,
		}
	}
	w := WriteOptions{Namespace: l.opts.Namespace}
	id, _, err := session.Create(se, &w)
	if err != nil {
		return "", err
	}
	return id, nil
}

// lockEntry returns a formatted KVPair for the lock
func (l *Lock) lockEntry(session string) *KVPair {
	return &KVPair{
		Key:     l.opts.Key,
		Value:   l.opts.Value,
		Session: session,
		Flags:   LockFlagValue,
	}
}

// monitorLock is a long running routine to monitor a lock ownership
// It closes the stopCh if we lose our leadership.
func (l *Lock) monitorLock(session string, stopCh chan struct{}) {
	defer close(stopCh)
	kv := l.c.KV()
	opts := QueryOptions{
		RequireConsistent: true,
		Namespace:         l.opts.Namespace,
	}
WAIT:
	retries := l.opts.MonitorRetries
RETRY:
	pair, meta, err := kv.Get(l.opts.Key, &opts)
	if err != nil {
		// If configured we can try to ride out a brief Consul unavailability
		// by doing retries. Note that we have to attempt the retry in a non-
		// blocking fashion so that we have a clean place to reset the retry
		// counter if service is restored.
		if retries > 0 && IsRetryableError(err) {
			time.Sleep(l.opts.MonitorRetryTime)
			retries--
			opts.WaitIndex = 0
			goto RETRY
		}
		return
	}
	if pair != nil && pair.Session == session {
		opts.WaitIndex = meta.LastIndex
		goto WAIT
	}
}
<commit_msg>Make LockDelay configurable in api locks (#8621)<commit_after>package api

import (
	"fmt"
	"sync"
	"time"
)

const (
	// DefaultLockSessionName is the Session Name we assign if none is provided
	DefaultLockSessionName = "Consul API Lock"

	// DefaultLockSessionTTL is the default session TTL if no Session is provided
	// when creating a new Lock. This is used because we do not have another
	// other check to depend upon.
	DefaultLockSessionTTL = "15s"

	// DefaultLockWaitTime is how long we block for at a time to check if lock
	// acquisition is possible. This affects the minimum time it takes to cancel
	// a Lock acquisition.
	DefaultLockWaitTime = 15 * time.Second

	// DefaultLockRetryTime is how long we wait after a failed lock acquisition
	// before attempting to do the lock again. This is so that once a lock-delay
	// is in effect, we do not hot loop retrying the acquisition.
	DefaultLockRetryTime = 5 * time.Second

	// DefaultMonitorRetryTime is how long we wait after a failed monitor check
	// of a lock (500 response code). This allows the monitor to ride out brief
	// periods of unavailability, subject to the MonitorRetries setting in the
	// lock options which is by default set to 0, disabling this feature. This
	// affects locks and semaphores.
	DefaultMonitorRetryTime = 2 * time.Second

	// LockFlagValue is a magic flag we set to indicate a key
	// is being used for a lock. It is used to detect a potential
	// conflict with a semaphore.
	LockFlagValue = 0x2ddccbc058a50c18
)

var (
	// ErrLockHeld is returned if we attempt to double lock
	ErrLockHeld = fmt.Errorf("Lock already held")

	// ErrLockNotHeld is returned if we attempt to unlock a lock
	// that we do not hold.
	ErrLockNotHeld = fmt.Errorf("Lock not held")

	// ErrLockInUse is returned if we attempt to destroy a lock
	// that is in use.
	ErrLockInUse = fmt.Errorf("Lock in use")

	// ErrLockConflict is returned if the flags on a key
	// used for a lock do not match expectation
	ErrLockConflict = fmt.Errorf("Existing key does not match lock use")
)

// Lock is used to implement client-side leader election. It is follows the
// algorithm as described here: https://www.consul.io/docs/guides/leader-election.html.
type Lock struct {
	c    *Client
	opts *LockOptions

	isHeld       bool
	sessionRenew chan struct{}
	lockSession  string
	l            sync.Mutex
}

// LockOptions is used to parameterize the Lock behavior.
type LockOptions struct {
	Key              string        // Must be set and have write permissions
	Value            []byte        // Optional, value to associate with the lock
	Session          string        // Optional, created if not specified
	SessionOpts      *SessionEntry // Optional, options to use when creating a session
	SessionName      string        // Optional, defaults to DefaultLockSessionName (ignored if SessionOpts is given)
	SessionTTL       string        // Optional, defaults to DefaultLockSessionTTL (ignored if SessionOpts is given)
	MonitorRetries   int           // Optional, defaults to 0 which means no retries
	MonitorRetryTime time.Duration // Optional, defaults to DefaultMonitorRetryTime
	LockWaitTime     time.Duration // Optional, defaults to DefaultLockWaitTime
	LockTryOnce      bool          // Optional, defaults to false which means try forever
	LockDelay        time.Duration // Optional, defaults to 15s
	Namespace        string        `json:",omitempty"` // Optional, defaults to API client config, namespace of ACL token, or "default" namespace
}

// LockKey returns a handle to a lock struct which can be used
// to acquire and release the mutex. The key used must have
// write permissions.
func (c *Client) LockKey(key string) (*Lock, error) {
	opts := &LockOptions{
		Key: key,
	}
	return c.LockOpts(opts)
}

// LockOpts returns a handle to a lock struct which can be used
// to acquire and release the mutex. The key used must have
// write permissions.
func (c *Client) LockOpts(opts *LockOptions) (*Lock, error) {
	if opts.Key == "" {
		return nil, fmt.Errorf("missing key")
	}
	if opts.SessionName == "" {
		opts.SessionName = DefaultLockSessionName
	}
	if opts.SessionTTL == "" {
		opts.SessionTTL = DefaultLockSessionTTL
	} else {
		if _, err := time.ParseDuration(opts.SessionTTL); err != nil {
			return nil, fmt.Errorf("invalid SessionTTL: %v", err)
		}
	}
	if opts.MonitorRetryTime == 0 {
		opts.MonitorRetryTime = DefaultMonitorRetryTime
	}
	if opts.LockWaitTime == 0 {
		opts.LockWaitTime = DefaultLockWaitTime
	}
	l := &Lock{
		c:    c,
		opts: opts,
	}
	return l, nil
}

// Lock attempts to acquire the lock and blocks while doing so.
// Providing a non-nil stopCh can be used to abort the lock attempt.
// Returns a channel that is closed if our lock is lost or an error.
// This channel could be closed at any time due to session invalidation,
// communication errors, operator intervention, etc. It is NOT safe to
// assume that the lock is held until Unlock() unless the Session is specifically
// created without any associated health checks. By default Consul sessions
// prefer liveness over safety and an application must be able to handle
// the lock being lost.
func (l *Lock) Lock(stopCh <-chan struct{}) (<-chan struct{}, error) {
	// Hold the lock as we try to acquire
	l.l.Lock()
	defer l.l.Unlock()

	// Check if we already hold the lock
	if l.isHeld {
		return nil, ErrLockHeld
	}

	wOpts := WriteOptions{
		Namespace: l.opts.Namespace,
	}

	// Check if we need to create a session first
	l.lockSession = l.opts.Session
	if l.lockSession == "" {
		s, err := l.createSession()
		if err != nil {
			return nil, fmt.Errorf("failed to create session: %v", err)
		}

		l.sessionRenew = make(chan struct{})
		l.lockSession = s

		session := l.c.Session()
		go session.RenewPeriodic(l.opts.SessionTTL, s, &wOpts, l.sessionRenew)

		// If we fail to acquire the lock, cleanup the session
		defer func() {
			if !l.isHeld {
				close(l.sessionRenew)
				l.sessionRenew = nil
			}
		}()
	}

	// Setup the query options
	kv := l.c.KV()
	qOpts := QueryOptions{
		WaitTime:  l.opts.LockWaitTime,
		Namespace: l.opts.Namespace,
	}

	start := time.Now()
	attempts := 0
WAIT:
	// Check if we should quit
	select {
	case <-stopCh:
		return nil, nil
	default:
	}

	// Handle the one-shot mode.
	if l.opts.LockTryOnce && attempts > 0 {
		elapsed := time.Since(start)
		if elapsed > l.opts.LockWaitTime {
			return nil, nil
		}

		// Query wait time should not exceed the lock wait time
		qOpts.WaitTime = l.opts.LockWaitTime - elapsed
	}
	attempts++

	// Look for an existing lock, blocking until not taken
	pair, meta, err := kv.Get(l.opts.Key, &qOpts)
	if err != nil {
		return nil, fmt.Errorf("failed to read lock: %v", err)
	}
	if pair != nil && pair.Flags != LockFlagValue {
		return nil, ErrLockConflict
	}
	locked := false
	if pair != nil && pair.Session == l.lockSession {
		goto HELD
	}
	if pair != nil && pair.Session != "" {
		qOpts.WaitIndex = meta.LastIndex
		goto WAIT
	}

	// Try to acquire the lock
	pair = l.lockEntry(l.lockSession)

	locked, _, err = kv.Acquire(pair, &wOpts)
	if err != nil {
		return nil, fmt.Errorf("failed to acquire lock: %v", err)
	}

	// Handle the case of not getting the lock
	if !locked {
		// Determine why the lock failed
		qOpts.WaitIndex = 0
		pair, meta, err = kv.Get(l.opts.Key, &qOpts)
		if err != nil {
			return nil, err
		}
		if pair != nil && pair.Session != "" {
			//If the session is not null, this means that a wait can safely happen
			//using a long poll
			qOpts.WaitIndex = meta.LastIndex
			goto WAIT
		} else {
			// If the session is empty and the lock failed to acquire, then it means
			// a lock-delay is in effect and a timed wait must be used
			select {
			case <-time.After(DefaultLockRetryTime):
				goto WAIT
			case <-stopCh:
				return nil, nil
			}
		}
	}

HELD:
	// Watch to ensure we maintain leadership
	leaderCh := make(chan struct{})
	go l.monitorLock(l.lockSession, leaderCh)

	// Set that we own the lock
	l.isHeld = true

	// Locked! All done
	return leaderCh, nil
}

// Unlock released the lock. It is an error to call this
// if the lock is not currently held.
func (l *Lock) Unlock() error {
	// Hold the lock as we try to release
	l.l.Lock()
	defer l.l.Unlock()

	// Ensure the lock is actually held
	if !l.isHeld {
		return ErrLockNotHeld
	}

	// Set that we no longer own the lock
	l.isHeld = false

	// Stop the session renew
	if l.sessionRenew != nil {
		defer func() {
			close(l.sessionRenew)
			l.sessionRenew = nil
		}()
	}

	// Get the lock entry, and clear the lock session
	lockEnt := l.lockEntry(l.lockSession)
	l.lockSession = ""

	// Release the lock explicitly
	kv := l.c.KV()
	w := WriteOptions{Namespace: l.opts.Namespace}

	_, _, err := kv.Release(lockEnt, &w)
	if err != nil {
		return fmt.Errorf("failed to release lock: %v", err)
	}
	return nil
}

// Destroy is used to cleanup the lock entry. It is not necessary
// to invoke. It will fail if the lock is in use.
func (l *Lock) Destroy() error {
	// Hold the lock as we try to release
	l.l.Lock()
	defer l.l.Unlock()

	// Check if we already hold the lock
	if l.isHeld {
		return ErrLockHeld
	}

	// Look for an existing lock
	kv := l.c.KV()
	q := QueryOptions{Namespace: l.opts.Namespace}

	pair, _, err := kv.Get(l.opts.Key, &q)
	if err != nil {
		return fmt.Errorf("failed to read lock: %v", err)
	}

	// Nothing to do if the lock does not exist
	if pair == nil {
		return nil
	}

	// Check for possible flag conflict
	if pair.Flags != LockFlagValue {
		return ErrLockConflict
	}

	// Check if it is in use
	if pair.Session != "" {
		return ErrLockInUse
	}

	// Attempt the delete
	w := WriteOptions{Namespace: l.opts.Namespace}
	didRemove, _, err := kv.DeleteCAS(pair, &w)
	if err != nil {
		return fmt.Errorf("failed to remove lock: %v", err)
	}
	if !didRemove {
		return ErrLockInUse
	}
	return nil
}

// createSession is used to create a new managed session
func (l *Lock) createSession() (string, error) {
	session := l.c.Session()
	se := l.opts.SessionOpts
	if se == nil {
		se = &SessionEntry{
			Name:      l.opts.SessionName,
			TTL:       l.opts.SessionTTL,
			LockDelay: l.opts.LockDelay,
		}
	}
	w := WriteOptions{Namespace: l.opts.Namespace}
	id, _, err := session.Create(se, &w)
	if err != nil {
		return "", err
	}
	return id, nil
}

// lockEntry returns a formatted KVPair for the lock
func (l *Lock) lockEntry(session string) *KVPair {
	return &KVPair{
		Key:     l.opts.Key,
		Value:   l.opts.Value,
		Session: session,
		Flags:   LockFlagValue,
	}
}

// monitorLock is a long running routine to monitor a lock ownership
// It closes the stopCh if we lose our leadership.
func (l *Lock) monitorLock(session string, stopCh chan struct{}) {
	defer close(stopCh)
	kv := l.c.KV()
	opts := QueryOptions{
		RequireConsistent: true,
		Namespace:         l.opts.Namespace,
	}
WAIT:
	retries := l.opts.MonitorRetries
RETRY:
	pair, meta, err := kv.Get(l.opts.Key, &opts)
	if err != nil {
		// If configured we can try to ride out a brief Consul unavailability
		// by doing retries. Note that we have to attempt the retry in a non-
		// blocking fashion so that we have a clean place to reset the retry
		// counter if service is restored.
		if retries > 0 && IsRetryableError(err) {
			time.Sleep(l.opts.MonitorRetryTime)
			retries--
			opts.WaitIndex = 0
			goto RETRY
		}
		return
	}
	if pair != nil && pair.Session == session {
		opts.WaitIndex = meta.LastIndex
		goto WAIT
	}
}
<|endoftext|>