feat(builder): bump scaleway to new sdk (#9902)
This commit is contained in:
parent
0719f906d4
commit
f8e31bff12
|
@ -4,7 +4,8 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/scaleway/scaleway-cli/pkg/api"
|
||||
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
|
||||
"github.com/scaleway/scaleway-sdk-go/scw"
|
||||
)
|
||||
|
||||
type Artifact struct {
|
||||
|
@ -20,11 +21,11 @@ type Artifact struct {
|
|||
// The ID of the snapshot
|
||||
snapshotID string
|
||||
|
||||
// The name of the region
|
||||
regionName string
|
||||
// The name of the zone
|
||||
zoneName string
|
||||
|
||||
// The client for making API calls
|
||||
client *api.ScalewayAPI
|
||||
client *scw.Client
|
||||
|
||||
// StateData should store data such as GeneratedData
|
||||
// to be shared with post-processors
|
||||
|
@ -41,12 +42,12 @@ func (*Artifact) Files() []string {
|
|||
}
|
||||
|
||||
func (a *Artifact) Id() string {
|
||||
return fmt.Sprintf("%s:%s", a.regionName, a.imageID)
|
||||
return fmt.Sprintf("%s:%s", a.zoneName, a.imageID)
|
||||
}
|
||||
|
||||
func (a *Artifact) String() string {
|
||||
return fmt.Sprintf("An image was created: '%v' (ID: %v) in region '%v' based on snapshot '%v' (ID: %v)",
|
||||
a.imageName, a.imageID, a.regionName, a.snapshotName, a.snapshotID)
|
||||
return fmt.Sprintf("An image was created: '%v' (ID: %v) in zone '%v' based on snapshot '%v' (ID: %v)",
|
||||
a.imageName, a.imageID, a.zoneName, a.snapshotName, a.snapshotID)
|
||||
}
|
||||
|
||||
func (a *Artifact) State(name string) interface{} {
|
||||
|
@ -55,11 +56,19 @@ func (a *Artifact) State(name string) interface{} {
|
|||
|
||||
func (a *Artifact) Destroy() error {
|
||||
log.Printf("Destroying image: %s (%s)", a.imageID, a.imageName)
|
||||
if err := a.client.DeleteImage(a.imageID); err != nil {
|
||||
instanceAPI := instance.NewAPI(a.client)
|
||||
|
||||
err := instanceAPI.DeleteImage(&instance.DeleteImageRequest{
|
||||
ImageID: a.imageID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("Destroying snapshot: %s (%s)", a.snapshotID, a.snapshotName)
|
||||
if err := a.client.DeleteSnapshot(a.snapshotID); err != nil {
|
||||
err = instanceAPI.DeleteSnapshot(&instance.DeleteSnapshotRequest{
|
||||
SnapshotID: a.snapshotID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
|
|
@ -27,7 +27,7 @@ func TestArtifactId(t *testing.T) {
|
|||
func TestArtifactString(t *testing.T) {
|
||||
generatedData := make(map[string]interface{})
|
||||
a := &Artifact{"packer-foobar-image", "cc586e45-5156-4f71-b223-cf406b10dd1d", "packer-foobar-snapshot", "cc586e45-5156-4f71-b223-cf406b10dd1c", "ams1", nil, generatedData}
|
||||
expected := "An image was created: 'packer-foobar-image' (ID: cc586e45-5156-4f71-b223-cf406b10dd1d) in region 'ams1' based on snapshot 'packer-foobar-snapshot' (ID: cc586e45-5156-4f71-b223-cf406b10dd1c)"
|
||||
expected := "An image was created: 'packer-foobar-image' (ID: cc586e45-5156-4f71-b223-cf406b10dd1d) in zone 'ams1' based on snapshot 'packer-foobar-snapshot' (ID: cc586e45-5156-4f71-b223-cf406b10dd1c)"
|
||||
|
||||
if a.String() != expected {
|
||||
t.Fatalf("artifact string should match: %v", expected)
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/scaleway/scaleway-cli/pkg/api"
|
||||
"github.com/scaleway/scaleway-sdk-go/scw"
|
||||
)
|
||||
|
||||
// The unique id for the builder
|
||||
|
@ -32,12 +32,27 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
|||
return nil, warnings, errs
|
||||
}
|
||||
|
||||
return nil, nil, nil
|
||||
return nil, warnings, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
client, err := api.NewScalewayAPI(b.config.Organization, b.config.Token, b.config.UserAgent, b.config.Region)
|
||||
scwZone, err := scw.ParseZone(b.config.Zone)
|
||||
if err != nil {
|
||||
ui.Error(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clientOpts := []scw.ClientOption{
|
||||
scw.WithDefaultProjectID(b.config.ProjectID),
|
||||
scw.WithAuth(b.config.AccessKey, b.config.SecretKey),
|
||||
scw.WithDefaultZone(scwZone),
|
||||
}
|
||||
|
||||
if b.config.APIURL != "" {
|
||||
clientOpts = append(clientOpts, scw.WithAPIURL(b.config.APIURL))
|
||||
}
|
||||
|
||||
client, err := scw.NewClient(clientOpts...)
|
||||
if err != nil {
|
||||
ui.Error(err.Error())
|
||||
return nil, err
|
||||
|
@ -96,7 +111,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
imageID: state.Get("image_id").(string),
|
||||
snapshotName: state.Get("snapshot_name").(string),
|
||||
snapshotID: state.Get("snapshot_id").(string),
|
||||
regionName: state.Get("region").(string),
|
||||
zoneName: b.config.Zone,
|
||||
client: client,
|
||||
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
|
||||
}
|
||||
|
|
|
@ -9,9 +9,10 @@ import (
|
|||
|
||||
func testConfig() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"organization_id": "foo",
|
||||
"api_token": "bar",
|
||||
"region": "ams1",
|
||||
"project_id": "00000000-1111-2222-3333-444444444444",
|
||||
"access_key": "SCWABCXXXXXXXXXXXXXX",
|
||||
"secret_key": "00000000-1111-2222-3333-444444444444",
|
||||
"zone": "fr-par-1",
|
||||
"commercial_type": "START1-S",
|
||||
"ssh_username": "root",
|
||||
"image": "image-uuid",
|
||||
|
@ -32,10 +33,7 @@ func TestBuilder_Prepare_BadType(t *testing.T) {
|
|||
"api_token": []string{},
|
||||
}
|
||||
|
||||
_, warnings, err := b.Prepare(c)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
_, _, err := b.Prepare(c)
|
||||
if err == nil {
|
||||
t.Fatalf("prepare should fail")
|
||||
}
|
||||
|
@ -68,11 +66,11 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_Region(t *testing.T) {
|
||||
func TestBuilderPrepare_Zone(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
delete(config, "region")
|
||||
delete(config, "zone")
|
||||
_, warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
|
@ -81,9 +79,9 @@ func TestBuilderPrepare_Region(t *testing.T) {
|
|||
t.Fatalf("should error")
|
||||
}
|
||||
|
||||
expected := "ams1"
|
||||
expected := "fr-par-1"
|
||||
|
||||
config["region"] = expected
|
||||
config["zone"] = expected
|
||||
b = Builder{}
|
||||
_, warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
|
@ -93,8 +91,8 @@ func TestBuilderPrepare_Region(t *testing.T) {
|
|||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.Region != expected {
|
||||
t.Errorf("found %s, expected %s", b.config.Region, expected)
|
||||
if b.config.Zone != expected {
|
||||
t.Errorf("found %s, expected %s", b.config.Zone, expected)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,27 +17,29 @@ import (
|
|||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
|
||||
"github.com/scaleway/scaleway-sdk-go/scw"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
// The token to use to authenticate with your account.
|
||||
// It can also be specified via environment variable SCALEWAY_API_TOKEN. You
|
||||
// can see and generate tokens in the "Credentials"
|
||||
// section of the control panel.
|
||||
Token string `mapstructure:"api_token" required:"true"`
|
||||
// The organization id to use to identify your
|
||||
// organization. It can also be specified via environment variable
|
||||
// SCALEWAY_ORGANIZATION. Your organization id is available in the
|
||||
// "Account" section of the
|
||||
// control panel.
|
||||
// Previously named: api_access_key with environment variable: SCALEWAY_API_ACCESS_KEY
|
||||
Organization string `mapstructure:"organization_id" required:"true"`
|
||||
// The name of the region to launch the server in (par1
|
||||
// or ams1). Consequently, this is the region where the snapshot will be
|
||||
// available.
|
||||
Region string `mapstructure:"region" required:"true"`
|
||||
// The AccessKey corresponding to the secret key.
|
||||
// It can also be specified via the environment variable SCW_ACCESS_KEY.
|
||||
AccessKey string `mapstructure:"access_key" required:"true"`
|
||||
// The SecretKey to authenticate against the Scaleway API.
|
||||
// It can also be specified via the environment variable SCW_SECRET_KEY.
|
||||
SecretKey string `mapstructure:"secret_key" required:"true"`
|
||||
// The Project ID in which the instances, volumes and snapshots will be created.
|
||||
// It can also be specified via the environment variable SCW_DEFAULT_PROJECT_ID.
|
||||
ProjectID string `mapstructure:"project_id" required:"true"`
|
||||
// The Zone in which the instances, volumes and snapshots will be created.
|
||||
// It can also be specified via the environment variable SCW_DEFAULT_ZONE
|
||||
Zone string `mapstructure:"zone" required:"true"`
|
||||
// The Scaleway API URL to use
|
||||
// It can also be specified via the environment variable SCW_API_URL
|
||||
APIURL string `mapstructure:"api_url"`
|
||||
|
||||
// The UUID of the base image to use. This is the image
|
||||
// that will be used to launch a new server and provision it. See
|
||||
// the images list
|
||||
|
@ -69,6 +71,28 @@ type Config struct {
|
|||
|
||||
UserAgent string `mapstructure-to-hcl2:",skip"`
|
||||
ctx interpolate.Context
|
||||
|
||||
// Deprecated configs
|
||||
|
||||
// The token to use to authenticate with your account.
|
||||
// It can also be specified via environment variable SCALEWAY_API_TOKEN. You
|
||||
// can see and generate tokens in the "Credentials"
|
||||
// section of the control panel.
|
||||
// Deprecated, use SecretKey instead
|
||||
Token string `mapstructure:"api_token" required:"false"`
|
||||
// The organization id to use to identify your
|
||||
// organization. It can also be specified via environment variable
|
||||
// SCALEWAY_ORGANIZATION. Your organization id is available in the
|
||||
// "Account" section of the
|
||||
// control panel.
|
||||
// Previously named: api_access_key with environment variable: SCALEWAY_API_ACCESS_KEY
|
||||
// Deprecated, use ProjectID instead
|
||||
Organization string `mapstructure:"organization_id" required:"false"`
|
||||
// The name of the region to launch the server in (par1
|
||||
// or ams1). Consequently, this is the region where the snapshot will be
|
||||
// available.
|
||||
// Deprecated, use Zone instead
|
||||
Region string `mapstructure:"region" required:"false"`
|
||||
}
|
||||
|
||||
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||
|
@ -88,8 +112,11 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
var warnings []string
|
||||
|
||||
c.UserAgent = useragent.String()
|
||||
|
||||
// Deprecated variables
|
||||
if c.Organization == "" {
|
||||
if os.Getenv("SCALEWAY_ORGANIZATION") != "" {
|
||||
c.Organization = os.Getenv("SCALEWAY_ORGANIZATION")
|
||||
|
@ -98,10 +125,43 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
|||
c.Organization = os.Getenv("SCALEWAY_API_ACCESS_KEY")
|
||||
}
|
||||
}
|
||||
if c.Organization != "" {
|
||||
warnings = append(warnings, "organization_id is deprecated in favor of project_id")
|
||||
c.ProjectID = c.Organization
|
||||
}
|
||||
|
||||
if c.Token == "" {
|
||||
c.Token = os.Getenv("SCALEWAY_API_TOKEN")
|
||||
}
|
||||
if c.Token != "" {
|
||||
warnings = append(warnings, "token is deprecated in favor of secret_key")
|
||||
c.SecretKey = c.Token
|
||||
}
|
||||
|
||||
if c.Region != "" {
|
||||
warnings = append(warnings, "region is deprecated in favor of zone")
|
||||
c.Zone = c.Region
|
||||
}
|
||||
|
||||
if c.AccessKey == "" {
|
||||
c.AccessKey = os.Getenv(scw.ScwAccessKeyEnv)
|
||||
}
|
||||
|
||||
if c.SecretKey == "" {
|
||||
c.SecretKey = os.Getenv(scw.ScwSecretKeyEnv)
|
||||
}
|
||||
|
||||
if c.ProjectID == "" {
|
||||
c.ProjectID = os.Getenv(scw.ScwDefaultProjectIDEnv)
|
||||
}
|
||||
|
||||
if c.Zone == "" {
|
||||
c.Zone = os.Getenv(scw.ScwDefaultZoneEnv)
|
||||
}
|
||||
|
||||
if c.APIURL == "" {
|
||||
c.APIURL = os.Getenv(scw.ScwAPIURLEnv)
|
||||
}
|
||||
|
||||
if c.SnapshotName == "" {
|
||||
def, err := interpolate.Render("snapshot-packer-{{timestamp}}", nil)
|
||||
|
@ -127,26 +187,31 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
|||
}
|
||||
|
||||
if c.BootType == "" {
|
||||
c.BootType = "local"
|
||||
c.BootType = instance.BootTypeLocal.String()
|
||||
}
|
||||
|
||||
var errs *packer.MultiError
|
||||
if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
|
||||
errs = packer.MultiErrorAppend(errs, es...)
|
||||
}
|
||||
if c.Organization == "" {
|
||||
if c.ProjectID == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("Scaleway Organization ID must be specified"))
|
||||
errs, errors.New("Scaleway Project ID must be specified"))
|
||||
}
|
||||
|
||||
if c.Token == "" {
|
||||
if c.SecretKey == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("Scaleway Token must be specified"))
|
||||
errs, errors.New("Scaleway Secret Key must be specified"))
|
||||
}
|
||||
|
||||
if c.Region == "" {
|
||||
if c.AccessKey == "" {
|
||||
warnings = append(warnings, "access_key will be required in future versions")
|
||||
c.AccessKey = "SCWXXXXXXXXXXXXXXXXX"
|
||||
}
|
||||
|
||||
if c.Zone == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("region is required"))
|
||||
errs, errors.New("Scaleway Zone is required"))
|
||||
}
|
||||
|
||||
if c.CommercialType == "" {
|
||||
|
@ -160,9 +225,9 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
|||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return nil, errs
|
||||
return warnings, errs
|
||||
}
|
||||
|
||||
packer.LogSecretFilter.Set(c.Token)
|
||||
return nil, nil
|
||||
return warnings, nil
|
||||
}
|
||||
|
|
|
@ -63,9 +63,11 @@ type FlatConfig struct {
|
|||
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl" hcl:"winrm_use_ssl"`
|
||||
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure" hcl:"winrm_insecure"`
|
||||
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"`
|
||||
Token *string `mapstructure:"api_token" required:"true" cty:"api_token" hcl:"api_token"`
|
||||
Organization *string `mapstructure:"organization_id" required:"true" cty:"organization_id" hcl:"organization_id"`
|
||||
Region *string `mapstructure:"region" required:"true" cty:"region" hcl:"region"`
|
||||
AccessKey *string `mapstructure:"access_key" required:"true" cty:"access_key" hcl:"access_key"`
|
||||
SecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key" hcl:"secret_key"`
|
||||
ProjectID *string `mapstructure:"project_id" required:"true" cty:"project_id" hcl:"project_id"`
|
||||
Zone *string `mapstructure:"zone" required:"true" cty:"zone" hcl:"zone"`
|
||||
APIURL *string `mapstructure:"api_url" cty:"api_url" hcl:"api_url"`
|
||||
Image *string `mapstructure:"image" required:"true" cty:"image" hcl:"image"`
|
||||
CommercialType *string `mapstructure:"commercial_type" required:"true" cty:"commercial_type" hcl:"commercial_type"`
|
||||
SnapshotName *string `mapstructure:"snapshot_name" required:"false" cty:"snapshot_name" hcl:"snapshot_name"`
|
||||
|
@ -74,6 +76,9 @@ type FlatConfig struct {
|
|||
Bootscript *string `mapstructure:"bootscript" required:"false" cty:"bootscript" hcl:"bootscript"`
|
||||
BootType *string `mapstructure:"boottype" required:"false" cty:"boottype" hcl:"boottype"`
|
||||
RemoveVolume *bool `mapstructure:"remove_volume" cty:"remove_volume" hcl:"remove_volume"`
|
||||
Token *string `mapstructure:"api_token" required:"false" cty:"api_token" hcl:"api_token"`
|
||||
Organization *string `mapstructure:"organization_id" required:"false" cty:"organization_id" hcl:"organization_id"`
|
||||
Region *string `mapstructure:"region" required:"false" cty:"region" hcl:"region"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatConfig.
|
||||
|
@ -142,9 +147,11 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|||
"winrm_use_ssl": &hcldec.AttrSpec{Name: "winrm_use_ssl", Type: cty.Bool, Required: false},
|
||||
"winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false},
|
||||
"winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false},
|
||||
"api_token": &hcldec.AttrSpec{Name: "api_token", Type: cty.String, Required: false},
|
||||
"organization_id": &hcldec.AttrSpec{Name: "organization_id", Type: cty.String, Required: false},
|
||||
"region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false},
|
||||
"access_key": &hcldec.AttrSpec{Name: "access_key", Type: cty.String, Required: false},
|
||||
"secret_key": &hcldec.AttrSpec{Name: "secret_key", Type: cty.String, Required: false},
|
||||
"project_id": &hcldec.AttrSpec{Name: "project_id", Type: cty.String, Required: false},
|
||||
"zone": &hcldec.AttrSpec{Name: "zone", Type: cty.String, Required: false},
|
||||
"api_url": &hcldec.AttrSpec{Name: "api_url", Type: cty.String, Required: false},
|
||||
"image": &hcldec.AttrSpec{Name: "image", Type: cty.String, Required: false},
|
||||
"commercial_type": &hcldec.AttrSpec{Name: "commercial_type", Type: cty.String, Required: false},
|
||||
"snapshot_name": &hcldec.AttrSpec{Name: "snapshot_name", Type: cty.String, Required: false},
|
||||
|
@ -153,6 +160,9 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|||
"bootscript": &hcldec.AttrSpec{Name: "bootscript", Type: cty.String, Required: false},
|
||||
"boottype": &hcldec.AttrSpec{Name: "boottype", Type: cty.String, Required: false},
|
||||
"remove_volume": &hcldec.AttrSpec{Name: "remove_volume", Type: cty.Bool, Required: false},
|
||||
"api_token": &hcldec.AttrSpec{Name: "api_token", Type: cty.String, Required: false},
|
||||
"organization_id": &hcldec.AttrSpec{Name: "organization_id", Type: cty.String, Required: false},
|
||||
"region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
|
|
@ -7,13 +7,14 @@ import (
|
|||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/scaleway/scaleway-cli/pkg/api"
|
||||
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
|
||||
"github.com/scaleway/scaleway-sdk-go/scw"
|
||||
)
|
||||
|
||||
type stepImage struct{}
|
||||
|
||||
func (s *stepImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*api.ScalewayAPI)
|
||||
instanceAPI := instance.NewAPI(state.Get("client").(*scw.Client))
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
c := state.Get("config").(*Config)
|
||||
snapshotID := state.Get("snapshot_id").(string)
|
||||
|
@ -21,7 +22,9 @@ func (s *stepImage) Run(ctx context.Context, state multistep.StateBag) multistep
|
|||
|
||||
ui.Say(fmt.Sprintf("Creating image: %v", c.ImageName))
|
||||
|
||||
image, err := client.GetImage(c.Image)
|
||||
imageResp, err := instanceAPI.GetImage(&instance.GetImageRequest{
|
||||
ImageID: c.Image,
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error getting initial image info: %s", err)
|
||||
state.Put("error", err)
|
||||
|
@ -29,11 +32,16 @@ func (s *stepImage) Run(ctx context.Context, state multistep.StateBag) multistep
|
|||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if image.DefaultBootscript != nil {
|
||||
bootscriptID = image.DefaultBootscript.Identifier
|
||||
if imageResp.Image.DefaultBootscript != nil {
|
||||
bootscriptID = imageResp.Image.DefaultBootscript.ID
|
||||
}
|
||||
|
||||
imageID, err := client.PostImage(snapshotID, c.ImageName, bootscriptID, image.Arch)
|
||||
createImageResp, err := instanceAPI.CreateImage(&instance.CreateImageRequest{
|
||||
Arch: imageResp.Image.Arch,
|
||||
DefaultBootscript: bootscriptID,
|
||||
Name: c.ImageName,
|
||||
RootVolume: snapshotID,
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating image: %s", err)
|
||||
state.Put("error", err)
|
||||
|
@ -41,10 +49,11 @@ func (s *stepImage) Run(ctx context.Context, state multistep.StateBag) multistep
|
|||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
log.Printf("Image ID: %s", imageID)
|
||||
state.Put("image_id", imageID)
|
||||
log.Printf("Image ID: %s", createImageResp.Image.ID)
|
||||
state.Put("image_id", createImageResp.Image.ID)
|
||||
state.Put("image_name", c.ImageName)
|
||||
state.Put("region", c.Region)
|
||||
state.Put("region", c.Zone) // Deprecated
|
||||
state.Put("zone", c.Zone)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
|
|
@ -7,7 +7,8 @@ import (
|
|||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/scaleway/scaleway-cli/pkg/api"
|
||||
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
|
||||
"github.com/scaleway/scaleway-sdk-go/scw"
|
||||
)
|
||||
|
||||
type stepCreateServer struct {
|
||||
|
@ -15,7 +16,7 @@ type stepCreateServer struct {
|
|||
}
|
||||
|
||||
func (s *stepCreateServer) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*api.ScalewayAPI)
|
||||
instanceAPI := instance.NewAPI(state.Get("client").(*scw.Client))
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
c := state.Get("config").(*Config)
|
||||
tags := []string{}
|
||||
|
@ -31,16 +32,16 @@ func (s *stepCreateServer) Run(ctx context.Context, state multistep.StateBag) mu
|
|||
tags = []string{fmt.Sprintf("AUTHORIZED_KEY=%s", strings.Replace(strings.TrimSpace(string(c.Comm.SSHPublicKey)), " ", "_", -1))}
|
||||
}
|
||||
|
||||
server, err := client.PostServer(api.ScalewayServerDefinition{
|
||||
Name: c.ServerName,
|
||||
Image: &c.Image,
|
||||
Organization: c.Organization,
|
||||
CommercialType: c.CommercialType,
|
||||
Tags: tags,
|
||||
Bootscript: bootscript,
|
||||
BootType: c.BootType,
|
||||
})
|
||||
bootType := instance.BootType(c.BootType)
|
||||
|
||||
createServerResp, err := instanceAPI.CreateServer(&instance.CreateServerRequest{
|
||||
BootType: &bootType,
|
||||
Bootscript: bootscript,
|
||||
CommercialType: c.CommercialType,
|
||||
Name: c.ServerName,
|
||||
Image: c.Image,
|
||||
Tags: tags,
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating server: %s", err)
|
||||
state.Put("error", err)
|
||||
|
@ -48,8 +49,10 @@ func (s *stepCreateServer) Run(ctx context.Context, state multistep.StateBag) mu
|
|||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
err = client.PostServerAction(server, "poweron")
|
||||
|
||||
_, err = instanceAPI.ServerAction(&instance.ServerActionRequest{
|
||||
Action: instance.ServerActionPoweron,
|
||||
ServerID: createServerResp.Server.ID,
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error starting server: %s", err)
|
||||
state.Put("error", err)
|
||||
|
@ -57,9 +60,9 @@ func (s *stepCreateServer) Run(ctx context.Context, state multistep.StateBag) mu
|
|||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
s.serverID = server
|
||||
s.serverID = createServerResp.Server.ID
|
||||
|
||||
state.Put("server_id", server)
|
||||
state.Put("server_id", createServerResp.Server.ID)
|
||||
// instance_id is the generic term used so that users can have access to the
|
||||
// instance id inside of the provisioners, used in step_provision.
|
||||
state.Put("instance_id", s.serverID)
|
||||
|
@ -72,16 +75,22 @@ func (s *stepCreateServer) Cleanup(state multistep.StateBag) {
|
|||
return
|
||||
}
|
||||
|
||||
client := state.Get("client").(*api.ScalewayAPI)
|
||||
instanceAPI := instance.NewAPI(state.Get("client").(*scw.Client))
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("Destroying server...")
|
||||
|
||||
err := client.DeleteServerForce(s.serverID)
|
||||
|
||||
err := instanceAPI.DeleteServer(&instance.DeleteServerRequest{
|
||||
ServerID: s.serverID,
|
||||
})
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf(
|
||||
"Error destroying server. Please destroy it manually: %s", err))
|
||||
_, err = instanceAPI.ServerAction(&instance.ServerActionRequest{
|
||||
Action: instance.ServerActionTerminate,
|
||||
ServerID: s.serverID,
|
||||
})
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf(
|
||||
"Error destroying server. Please destroy it manually: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,10 +4,10 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/scaleway/scaleway-cli/pkg/api"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
|
||||
"github.com/scaleway/scaleway-sdk-go/scw"
|
||||
)
|
||||
|
||||
type stepRemoveVolume struct{}
|
||||
|
@ -24,7 +24,7 @@ func (s *stepRemoveVolume) Cleanup(state multistep.StateBag) {
|
|||
return
|
||||
}
|
||||
|
||||
client := state.Get("client").(*api.ScalewayAPI)
|
||||
instanceAPI := instance.NewAPI(state.Get("client").(*scw.Client))
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
c := state.Get("config").(*Config)
|
||||
volumeID := state.Get("root_volume_id").(string)
|
||||
|
@ -35,7 +35,9 @@ func (s *stepRemoveVolume) Cleanup(state multistep.StateBag) {
|
|||
|
||||
ui.Say("Removing Volume ...")
|
||||
|
||||
err := client.DeleteVolume(volumeID)
|
||||
err := instanceAPI.DeleteVolume(&instance.DeleteVolumeRequest{
|
||||
VolumeID: volumeID,
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error removing volume: %s", err)
|
||||
state.Put("error", err)
|
||||
|
|
|
@ -6,19 +6,22 @@ import (
|
|||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/scaleway/scaleway-cli/pkg/api"
|
||||
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
|
||||
"github.com/scaleway/scaleway-sdk-go/scw"
|
||||
)
|
||||
|
||||
type stepServerInfo struct{}
|
||||
|
||||
func (s *stepServerInfo) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*api.ScalewayAPI)
|
||||
instanceAPI := instance.NewAPI(state.Get("client").(*scw.Client))
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
serverID := state.Get("server_id").(string)
|
||||
|
||||
ui.Say("Waiting for server to become active...")
|
||||
|
||||
_, err := api.WaitForServerState(client, serverID, "running")
|
||||
instanceResp, err := instanceAPI.WaitForServer(&instance.WaitForServerRequest{
|
||||
ServerID: serverID,
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error waiting for server to become booted: %s", err)
|
||||
state.Put("error", err)
|
||||
|
@ -26,16 +29,22 @@ func (s *stepServerInfo) Run(ctx context.Context, state multistep.StateBag) mult
|
|||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
server, err := client.GetServer(serverID)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error retrieving server: %s", err)
|
||||
if instanceResp.State != instance.ServerStateRunning {
|
||||
err := fmt.Errorf("Server is in state %s", instanceResp.State.String())
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
state.Put("server_ip", server.PublicAddress.IP)
|
||||
state.Put("root_volume_id", server.Volumes["0"].Identifier)
|
||||
if instanceResp.PublicIP == nil {
|
||||
err := fmt.Errorf("Server does not have a public IP")
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
state.Put("server_ip", instanceResp.PublicIP.Address.String())
|
||||
state.Put("root_volume_id", instanceResp.Volumes["0"].ID)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
|
|
@ -6,20 +6,23 @@ import (
|
|||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/scaleway/scaleway-cli/pkg/api"
|
||||
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
|
||||
"github.com/scaleway/scaleway-sdk-go/scw"
|
||||
)
|
||||
|
||||
type stepShutdown struct{}
|
||||
|
||||
func (s *stepShutdown) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*api.ScalewayAPI)
|
||||
instanceAPI := instance.NewAPI(state.Get("client").(*scw.Client))
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
serverID := state.Get("server_id").(string)
|
||||
|
||||
ui.Say("Shutting down server...")
|
||||
|
||||
err := client.PostServerAction(serverID, "poweroff")
|
||||
|
||||
_, err := instanceAPI.ServerAction(&instance.ServerActionRequest{
|
||||
Action: instance.ServerActionPoweroff,
|
||||
ServerID: serverID,
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error stopping server: %s", err)
|
||||
state.Put("error", err)
|
||||
|
@ -27,8 +30,9 @@ func (s *stepShutdown) Run(ctx context.Context, state multistep.StateBag) multis
|
|||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
_, err = api.WaitForServerState(client, serverID, "stopped")
|
||||
|
||||
instanceResp, err := instanceAPI.WaitForServer(&instance.WaitForServerRequest{
|
||||
ServerID: serverID,
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error shutting down server: %s", err)
|
||||
state.Put("error", err)
|
||||
|
@ -36,6 +40,13 @@ func (s *stepShutdown) Run(ctx context.Context, state multistep.StateBag) multis
|
|||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if instanceResp.State != instance.ServerStateStopped {
|
||||
err := fmt.Errorf("Server is in state %s instead of stopped", instanceResp.State.String())
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
|
|
|
@ -7,19 +7,23 @@ import (
|
|||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/scaleway/scaleway-cli/pkg/api"
|
||||
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
|
||||
"github.com/scaleway/scaleway-sdk-go/scw"
|
||||
)
|
||||
|
||||
type stepSnapshot struct{}
|
||||
|
||||
func (s *stepSnapshot) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*api.ScalewayAPI)
|
||||
instanceAPI := instance.NewAPI(state.Get("client").(*scw.Client))
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
c := state.Get("config").(*Config)
|
||||
volumeID := state.Get("root_volume_id").(string)
|
||||
|
||||
ui.Say(fmt.Sprintf("Creating snapshot: %v", c.SnapshotName))
|
||||
snapshot, err := client.PostSnapshot(volumeID, c.SnapshotName)
|
||||
createSnapshotResp, err := instanceAPI.CreateSnapshot(&instance.CreateSnapshotRequest{
|
||||
Name: c.SnapshotName,
|
||||
VolumeID: volumeID,
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating snapshot: %s", err)
|
||||
state.Put("error", err)
|
||||
|
@ -27,10 +31,11 @@ func (s *stepSnapshot) Run(ctx context.Context, state multistep.StateBag) multis
|
|||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
log.Printf("Snapshot ID: %s", snapshot)
|
||||
state.Put("snapshot_id", snapshot)
|
||||
log.Printf("Snapshot ID: %s", createSnapshotResp.Snapshot.ID)
|
||||
state.Put("snapshot_id", createSnapshotResp.Snapshot.ID)
|
||||
state.Put("snapshot_name", c.SnapshotName)
|
||||
state.Put("region", c.Region)
|
||||
state.Put("region", c.Zone) // Deprecated
|
||||
state.Put("zone", c.Zone)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
|
9
go.mod
9
go.mod
|
@ -26,13 +26,10 @@ require (
|
|||
github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee
|
||||
github.com/cheggaaa/pb v1.0.27
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
|
||||
github.com/creack/goselect v0.1.0 // indirect
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/digitalocean/go-libvirt v0.0.0-20190626172931-4d226dd6c437 // indirect
|
||||
github.com/digitalocean/go-qemu v0.0.0-20181112162955-dd7bb9c771b8
|
||||
github.com/digitalocean/godo v1.11.1
|
||||
github.com/docker/docker v0.0.0-20180422163414-57142e89befe // indirect
|
||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||
github.com/dylanmei/iso8601 v0.1.0 // indirect
|
||||
github.com/dylanmei/winrmtest v0.0.0-20170819153634-c2fbb09e6c08
|
||||
github.com/exoscale/egoscale v0.18.1
|
||||
|
@ -52,7 +49,6 @@ require (
|
|||
github.com/gophercloud/gophercloud v0.12.0
|
||||
github.com/gophercloud/utils v0.0.0-20200508015959-b0167b94122c
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
|
||||
github.com/gorilla/websocket v0.0.0-20170319172727-a91eba7f9777 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.1.0
|
||||
github.com/hako/durafmt v0.0.0-20200710122514-c0fb7b4da026
|
||||
github.com/hashicorp/consul/api v1.4.0
|
||||
|
@ -103,8 +99,6 @@ require (
|
|||
github.com/mitchellh/reflectwalk v1.0.0
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||
github.com/moul/anonuuid v0.0.0-20160222162117-609b752a95ef // indirect
|
||||
github.com/moul/gotty-client v0.0.0-20180327180212-b26a57ebc215 // indirect
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.0-20180105111133-96aac992fc8b
|
||||
github.com/oracle/oci-go-sdk v18.0.0+incompatible
|
||||
|
@ -115,9 +109,8 @@ require (
|
|||
github.com/pkg/sftp v0.0.0-20160118190721-e84cc8c755ca
|
||||
github.com/posener/complete v1.2.3
|
||||
github.com/profitbricks/profitbricks-sdk-go v4.0.2+incompatible
|
||||
github.com/renstrom/fuzzysearch v0.0.0-20160331204855-2d205ac6ec17 // indirect
|
||||
github.com/satori/go.uuid v1.2.0 // indirect
|
||||
github.com/scaleway/scaleway-cli v0.0.0-20180921094345-7b12c9699d70
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200903143645-c0ce17a0443d
|
||||
github.com/shirou/gopsutil v2.18.12+incompatible
|
||||
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
|
||||
|
|
18
go.sum
18
go.sum
|
@ -143,8 +143,6 @@ github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp
|
|||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f h1:WBZRG4aNOuI15bLRrCgN8fCq8E5Xuty6jGbmSNEvSsU=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/creack/goselect v0.1.0 h1:4QiXIhcpSQF50XGaBsFzesjwX/1qOY5bOveQPmN9CXY=
|
||||
github.com/creack/goselect v0.1.0/go.mod h1:gHrIcH/9UZDn2qgeTUeW5K9eZsVYCH6/60J/FHysWyE=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
@ -163,10 +161,6 @@ github.com/diskfs/go-diskfs v1.1.1 h1:rMjLpaydtXGVZb7mdkRGK1+//30i76nKAit89zUzea
|
|||
github.com/diskfs/go-diskfs v1.1.1/go.mod h1:afUPxxu+x1snp4aCY2bKR0CoZ/YFJewV3X2UEr2nPZE=
|
||||
github.com/dnaeon/go-vcr v1.0.1 h1:r8L/HqC0Hje5AXMu1ooW8oyQyOFv4GxqpL0nRP7SLLY=
|
||||
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
||||
github.com/docker/docker v0.0.0-20180422163414-57142e89befe h1:VW8TnWi0CZgg7oCv0wH6evNwkzcJg/emnw4HrVIWws4=
|
||||
github.com/docker/docker v0.0.0-20180422163414-57142e89befe/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dylanmei/iso8601 v0.1.0 h1:812NGQDBcqquTfH5Yeo7lwR0nzx/cKdsmf3qMjPURUI=
|
||||
github.com/dylanmei/iso8601 v0.1.0/go.mod h1:w9KhXSgIyROl1DefbMYIE7UVSIvELTbMrCfx+QkYnoQ=
|
||||
github.com/dylanmei/winrmtest v0.0.0-20170819153634-c2fbb09e6c08 h1:0bp6/GrNOrTDtSXe9YYGCwf8jp5Fb/b+4a6MTRm4qzY=
|
||||
|
@ -284,8 +278,6 @@ github.com/gophercloud/utils v0.0.0-20200508015959-b0167b94122c h1:iawx2ojEQA7c+
|
|||
github.com/gophercloud/utils v0.0.0-20200508015959-b0167b94122c/go.mod h1:ehWUbLQJPqS0Ep+CxeD559hsm9pthPXadJNKwZkp43w=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/websocket v0.0.0-20170319172727-a91eba7f9777 h1:JIM+OacoOJRU30xpjMf8sulYqjr0ViA3WDrTX6j/yDI=
|
||||
github.com/gorilla/websocket v0.0.0-20170319172727-a91eba7f9777/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.1.0 h1:THDBEeQ9xZ8JEaCLyLQqXMMdRqNr0QAUJTIkQAUtFjg=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE=
|
||||
github.com/hako/durafmt v0.0.0-20200710122514-c0fb7b4da026 h1:BpJ2o0OR5FV7vrkDYfXYVJQeMNWa8RhklZOpW2ITAIQ=
|
||||
|
@ -503,10 +495,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
|||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/moul/anonuuid v0.0.0-20160222162117-609b752a95ef h1:E/seV1Rtsnr2juBw1Dfz4iDPT3/5s1H/BATx+ePmSyo=
|
||||
github.com/moul/anonuuid v0.0.0-20160222162117-609b752a95ef/go.mod h1:LgKrp0Iss/BVwquptq4eIe6HPr0s3t1WHT5x0qOh14U=
|
||||
github.com/moul/gotty-client v0.0.0-20180327180212-b26a57ebc215 h1:y6FZWUBBt1iPmJyGbGza3ncvVBMKzgd32oFChRZR7Do=
|
||||
github.com/moul/gotty-client v0.0.0-20180327180212-b26a57ebc215/go.mod h1:CxM/JGtpRrEPve5H04IhxJrGhxgwxMc6jSP2T4YD60w=
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
|
||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||
|
@ -550,8 +538,6 @@ github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jO
|
|||
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nLJdBg+pBmGgkJlSaKC2KaQmTCk1XDtE=
|
||||
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/renstrom/fuzzysearch v0.0.0-20160331204855-2d205ac6ec17 h1:4qPms2txLWMLXKzqlnYSulKRS4cS9aYgPtAEpUelQok=
|
||||
github.com/renstrom/fuzzysearch v0.0.0-20160331204855-2d205ac6ec17/go.mod h1:SAEjPB4voP88qmWJXI7mA5m15uNlEnuHLx4Eu2mPGpQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/ryanuber/columnize v2.1.0+incompatible h1:j1Wcmh8OrK4Q7GXY+V7SVSY8nUWQxHW5TkBe7YUl+2s=
|
||||
|
@ -560,8 +546,8 @@ github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkB
|
|||
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/scaleway/scaleway-cli v0.0.0-20180921094345-7b12c9699d70 h1:DaqC32ZwOuO4ctgg9qAdKnlQxwFPkKmCOEqwSNwYy7c=
|
||||
github.com/scaleway/scaleway-cli v0.0.0-20180921094345-7b12c9699d70/go.mod h1:XjlXWPd6VONhsRSEuzGkV8mzRpH7ou1cdLV7IKJk96s=
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200903143645-c0ce17a0443d h1:pK33AoOAlzj6gJs/V1vi2Ouj2u1Ww84pREwxFi1oxkM=
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200903143645-c0ce17a0443d/go.mod h1:CJJ5VAbozOl0yEw7nHB9+7BXTJbIn6h7W+f6Gau5IP8=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
|
||||
go-select*
|
||||
goselect*
|
||||
example-*
|
||||
example/example
|
|
@ -1,5 +0,0 @@
|
|||
FROM google/golang:stable
|
||||
MAINTAINER Guillaume J. Charmes <guillaume@charmes.net>
|
||||
CMD /tmp/a.out
|
||||
ADD . /src
|
||||
RUN cd /src && go build -o /tmp/a.out
|
|
@ -1,22 +0,0 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Guillaume J. Charmes
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
# go-select
|
||||
|
||||
select(2) implementation in Go
|
||||
|
||||
## Supported platforms
|
||||
|
||||
| | 386 | amd64 | arm | arm64 | mips | mipsle | mips64 | mips64le | ppc64le | s390x |
|
||||
|---------------|-----|-------|-----|-------|------|--------|--------|----------|---------|-------|
|
||||
| **linux** | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes |
|
||||
| **darwin** | yes | yes | ?? | ?? | n/a | n/a | n/a | n/a | n/a | n/a |
|
||||
| **freebsd** | yes | yes | yes | n/a | n/a | n/a | n/a | n/a | n/a | n/a |
|
||||
| **openbsd** | yes | yes | yes | n/a | n/a | n/a | n/a | n/a | n/a | n/a |
|
||||
| **netbsd** | yes | yes | yes | n/a | n/a | n/a | n/a | n/a | n/a | n/a |
|
||||
| **dragonfly** | n/a | yes | n/a | n/a | n/a | n/a | n/a | n/a | n/a | n/a |
|
||||
| **solaris** | n/a | no | n/a | n/a | n/a | n/a | n/a | n/a | n/a | n/a |
|
||||
| **plan9** | no | no | no | n/a | n/a | n/a | n/a | n/a | n/a | n/a |
|
||||
| **windows** | yes | yes | n/a | n/a | n/a | n/a | n/a | n/a | n/a | n/a |
|
||||
| **android** | ?? | ?? | ?? | ?? | n/a | n/a | n/a | n/a | n/a | n/a |
|
||||
|
||||
*n/a: platform not supported by Go
|
||||
*??: not tested
|
||||
|
||||
Go on `plan9` and `solaris` do not implement `syscall.Select` nor `syscall.SYS_SELECT`.
|
||||
|
||||
## Cross compile test
|
||||
|
||||
Note that this only tests the compilation, not the functionality.
|
||||
|
||||
```sh
|
||||
$> ./test_crosscompile.sh > /dev/null | sort
|
||||
[OK] android/386
|
||||
[OK] android/amd64
|
||||
[OK] android/arm
|
||||
[OK] android/arm64
|
||||
[OK] darwin/386
|
||||
[OK] darwin/amd64
|
||||
[OK] darwin/arm
|
||||
[OK] darwin/arm64
|
||||
[OK] dragonfly/amd64
|
||||
[OK] freebsd/386
|
||||
[OK] freebsd/amd64
|
||||
[OK] freebsd/arm
|
||||
[OK] linux/386
|
||||
[OK] linux/amd64
|
||||
[OK] linux/arm
|
||||
[OK] linux/arm64
|
||||
[OK] linux/mips
|
||||
[OK] linux/mips64
|
||||
[OK] linux/mips64le
|
||||
[OK] linux/mipsle
|
||||
[OK] linux/ppc64le
|
||||
[OK] linux/s390x
|
||||
[OK] netbsd/386
|
||||
[OK] netbsd/amd64
|
||||
[OK] netbsd/arm
|
||||
[OK] openbsd/386
|
||||
[OK] openbsd/amd64
|
||||
[OK] openbsd/arm
|
||||
[OK] plan9/386
|
||||
[OK] plan9/amd64
|
||||
[OK] plan9/arm
|
||||
[OK] solaris/amd64
|
||||
[OK] windows/386
|
||||
[OK] windows/amd64
|
||||
[OK] windows/arm
|
||||
|
||||
# Expected failures.
|
||||
[KO] android/mips
|
||||
[KO] android/mips64
|
||||
[KO] android/mips64le
|
||||
[KO] android/mipsle
|
||||
[KO] android/ppc64le
|
||||
[KO] android/s390x
|
||||
[KO] darwin/mips
|
||||
[KO] darwin/mips64
|
||||
[KO] darwin/mips64le
|
||||
[KO] darwin/mipsle
|
||||
[KO] darwin/ppc64le
|
||||
[KO] darwin/s390x
|
||||
[KO] dragonfly/386
|
||||
[KO] dragonfly/arm
|
||||
[KO] dragonfly/arm64
|
||||
[KO] dragonfly/mips
|
||||
[KO] dragonfly/mips64
|
||||
[KO] dragonfly/mips64le
|
||||
[KO] dragonfly/mipsle
|
||||
[KO] dragonfly/ppc64le
|
||||
[KO] dragonfly/s390x
|
||||
[KO] freebsd/arm64
|
||||
[KO] freebsd/mips
|
||||
[KO] freebsd/mips64
|
||||
[KO] freebsd/mips64le
|
||||
[KO] freebsd/mipsle
|
||||
[KO] freebsd/ppc64le
|
||||
[KO] freebsd/s390x
|
||||
[KO] netbsd/arm64
|
||||
[KO] netbsd/mips
|
||||
[KO] netbsd/mips64
|
||||
[KO] netbsd/mips64le
|
||||
[KO] netbsd/mipsle
|
||||
[KO] netbsd/ppc64le
|
||||
[KO] netbsd/s390x
|
||||
[KO] openbsd/arm64
|
||||
[KO] openbsd/mips
|
||||
[KO] openbsd/mips64
|
||||
[KO] openbsd/mips64le
|
||||
[KO] openbsd/mipsle
|
||||
[KO] openbsd/ppc64le
|
||||
[KO] openbsd/s390x
|
||||
[KO] plan9/arm64
|
||||
[KO] plan9/mips
|
||||
[KO] plan9/mips64
|
||||
[KO] plan9/mips64le
|
||||
[KO] plan9/mipsle
|
||||
[KO] plan9/ppc64le
|
||||
[KO] plan9/s390x
|
||||
[KO] solaris/386
|
||||
[KO] solaris/arm
|
||||
[KO] solaris/arm64
|
||||
[KO] solaris/mips
|
||||
[KO] solaris/mips64
|
||||
[KO] solaris/mips64le
|
||||
[KO] solaris/mipsle
|
||||
[KO] solaris/ppc64le
|
||||
[KO] solaris/s390x
|
||||
[KO] windows/arm64
|
||||
[KO] windows/mips
|
||||
[KO] windows/mips64
|
||||
[KO] windows/mips64le
|
||||
[KO] windows/mipsle
|
||||
[KO] windows/ppc64le
|
||||
[KO] windows/s390x
|
||||
```
|
|
@ -1,33 +0,0 @@
|
|||
// +build !freebsd,!windows,!plan9
|
||||
|
||||
package goselect
|
||||
|
||||
import "syscall"
|
||||
|
||||
const FD_SETSIZE = syscall.FD_SETSIZE
|
||||
|
||||
// FDSet wraps syscall.FdSet with convenience methods
|
||||
type FDSet syscall.FdSet
|
||||
|
||||
// Set adds the fd to the set
|
||||
func (fds *FDSet) Set(fd uintptr) {
|
||||
fds.Bits[fd/NFDBITS] |= (1 << (fd % NFDBITS))
|
||||
}
|
||||
|
||||
// Clear remove the fd from the set
|
||||
func (fds *FDSet) Clear(fd uintptr) {
|
||||
fds.Bits[fd/NFDBITS] &^= (1 << (fd % NFDBITS))
|
||||
}
|
||||
|
||||
// IsSet check if the given fd is set
|
||||
func (fds *FDSet) IsSet(fd uintptr) bool {
|
||||
return fds.Bits[fd/NFDBITS]&(1<<(fd%NFDBITS)) != 0
|
||||
}
|
||||
|
||||
// Keep a null set to avoid reinstatiation
|
||||
var nullFdSet = &FDSet{}
|
||||
|
||||
// Zero empties the Set
|
||||
func (fds *FDSet) Zero() {
|
||||
copy(fds.Bits[:], (nullFdSet).Bits[:])
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
// +build darwin openbsd netbsd 386 arm mips mipsle
|
||||
|
||||
package goselect
|
||||
|
||||
// darwin, netbsd and openbsd uses uint32 on both amd64 and 386
|
||||
|
||||
const (
|
||||
// NFDBITS is the amount of bits per mask
|
||||
NFDBITS = 4 * 8
|
||||
)
|
|
@ -1,11 +0,0 @@
|
|||
// +build !darwin,!netbsd,!openbsd
|
||||
// +build amd64 arm64 ppc64le mips64 mips64le s390x
|
||||
|
||||
package goselect
|
||||
|
||||
// darwin, netbsd and openbsd uses uint32 on both amd64 and 386
|
||||
|
||||
const (
|
||||
// NFDBITS is the amount of bits per mask
|
||||
NFDBITS = 8 * 8
|
||||
)
|
|
@ -1,93 +0,0 @@
|
|||
package goselect
|
||||
|
||||
/**
|
||||
From: XCode's MacOSX10.10.sdk/System/Library/Frameworks/Kernel.framework/Versions/A/Headers/sys/select.h
|
||||
--
|
||||
// darwin/amd64 / 386
|
||||
sizeof(__int32_t) == 4
|
||||
--
|
||||
|
||||
typedef __int32_t __fd_mask;
|
||||
|
||||
#define FD_SETSIZE 1024
|
||||
#define __NFDBITS (sizeof(__fd_mask) * 8)
|
||||
#define __howmany(x, y) ((((x) % (y)) == 0) ? ((x) / (y)) : (((x) / (y)) + 1))
|
||||
|
||||
typedef struct fd_set {
|
||||
__fd_mask fds_bits[__howmany(__FD_SETSIZE, __NFDBITS)];
|
||||
} fd_set;
|
||||
|
||||
#define __FD_MASK(n) ((__fd_mask)1 << ((n) % __NFDBITS))
|
||||
#define FD_SET(n, p) ((p)->fds_bits[(n)/__NFDBITS] |= __FD_MASK(n))
|
||||
#define FD_CLR(n, p) ((p)->fds_bits[(n)/__NFDBITS] &= ~__FD_MASK(n))
|
||||
#define FD_ISSET(n, p) (((p)->fds_bits[(n)/__NFDBITS] & __FD_MASK(n)) != 0)
|
||||
*/
|
||||
|
||||
/**
|
||||
From: /usr/include/i386-linux-gnu/sys/select.h
|
||||
--
|
||||
// linux/i686
|
||||
sizeof(long int) == 4
|
||||
--
|
||||
|
||||
typedef long int __fd_mask;
|
||||
|
||||
#define FD_SETSIZE 1024
|
||||
#define __NFDBITS (sizeof(__fd_mask) * 8)
|
||||
|
||||
|
||||
typedef struct fd_set {
|
||||
__fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
|
||||
} fd_set;
|
||||
|
||||
#define __FD_MASK(n) ((__fd_mask)1 << ((n) % __NFDBITS))
|
||||
#define FD_SET(n, p) ((p)->fds_bits[(n)/__NFDBITS] |= __FD_MASK(n))
|
||||
#define FD_CLR(n, p) ((p)->fds_bits[(n)/__NFDBITS] &= ~__FD_MASK(n))
|
||||
#define FD_ISSET(n, p) (((p)->fds_bits[(n)/__NFDBITS] & __FD_MASK(n)) != 0)
|
||||
*/
|
||||
|
||||
/**
|
||||
From: /usr/include/x86_64-linux-gnu/sys/select.h
|
||||
--
|
||||
// linux/amd64
|
||||
sizeof(long int) == 8
|
||||
--
|
||||
|
||||
typedef long int __fd_mask;
|
||||
|
||||
#define FD_SETSIZE 1024
|
||||
#define __NFDBITS (sizeof(__fd_mask) * 8)
|
||||
|
||||
|
||||
typedef struct fd_set {
|
||||
__fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
|
||||
} fd_set;
|
||||
|
||||
#define __FD_MASK(n) ((__fd_mask)1 << ((n) % __NFDBITS))
|
||||
#define FD_SET(n, p) ((p)->fds_bits[(n)/__NFDBITS] |= __FD_MASK(n))
|
||||
#define FD_CLR(n, p) ((p)->fds_bits[(n)/__NFDBITS] &= ~__FD_MASK(n))
|
||||
#define FD_ISSET(n, p) (((p)->fds_bits[(n)/__NFDBITS] & __FD_MASK(n)) != 0)
|
||||
*/
|
||||
|
||||
/**
|
||||
From: /usr/include/sys/select.h
|
||||
--
|
||||
// freebsd/amd64
|
||||
sizeof(unsigned long) == 8
|
||||
--
|
||||
|
||||
typedef unsigned long __fd_mask;
|
||||
|
||||
#define FD_SETSIZE 1024U
|
||||
#define __NFDBITS (sizeof(__fd_mask) * 8)
|
||||
#define _howmany(x, y) (((x) + ((y) - 1)) / (y))
|
||||
|
||||
typedef struct fd_set {
|
||||
__fd_mask fds_bits[_howmany(FD_SETSIZE, __NFDBITS)];
|
||||
} fd_set;
|
||||
|
||||
#define __FD_MASK(n) ((__fd_mask)1 << ((n) % __NFDBITS))
|
||||
#define FD_SET(n, p) ((p)->fds_bits[(n)/__NFDBITS] |= __FD_MASK(n))
|
||||
#define FD_CLR(n, p) ((p)->fds_bits[(n)/__NFDBITS] &= ~__FD_MASK(n))
|
||||
#define FD_ISSET(n, p) (((p)->fds_bits[(n)/__NFDBITS] & __FD_MASK(n)) != 0)
|
||||
*/
|
|
@ -1,33 +0,0 @@
|
|||
// +build freebsd
|
||||
|
||||
package goselect
|
||||
|
||||
import "syscall"
|
||||
|
||||
const FD_SETSIZE = syscall.FD_SETSIZE
|
||||
|
||||
// FDSet wraps syscall.FdSet with convenience methods
|
||||
type FDSet syscall.FdSet
|
||||
|
||||
// Set adds the fd to the set
|
||||
func (fds *FDSet) Set(fd uintptr) {
|
||||
fds.X__fds_bits[fd/NFDBITS] |= (1 << (fd % NFDBITS))
|
||||
}
|
||||
|
||||
// Clear remove the fd from the set
|
||||
func (fds *FDSet) Clear(fd uintptr) {
|
||||
fds.X__fds_bits[fd/NFDBITS] &^= (1 << (fd % NFDBITS))
|
||||
}
|
||||
|
||||
// IsSet check if the given fd is set
|
||||
func (fds *FDSet) IsSet(fd uintptr) bool {
|
||||
return fds.X__fds_bits[fd/NFDBITS]&(1<<(fd%NFDBITS)) != 0
|
||||
}
|
||||
|
||||
// Keep a null set to avoid reinstatiation
|
||||
var nullFdSet = &FDSet{}
|
||||
|
||||
// Zero empties the Set
|
||||
func (fds *FDSet) Zero() {
|
||||
copy(fds.X__fds_bits[:], (nullFdSet).X__fds_bits[:])
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
// +build plan9
|
||||
|
||||
package goselect
|
||||
|
||||
const FD_SETSIZE = 0
|
||||
|
||||
// FDSet wraps syscall.FdSet with convenience methods
|
||||
type FDSet struct{}
|
||||
|
||||
// Set adds the fd to the set
|
||||
func (fds *FDSet) Set(fd uintptr) {}
|
||||
|
||||
// Clear remove the fd from the set
|
||||
func (fds *FDSet) Clear(fd uintptr) {}
|
||||
|
||||
// IsSet check if the given fd is set
|
||||
func (fds *FDSet) IsSet(fd uintptr) bool { return false }
|
||||
|
||||
// Zero empties the Set
|
||||
func (fds *FDSet) Zero() {}
|
|
@ -1,57 +0,0 @@
|
|||
// +build windows
|
||||
|
||||
package goselect
|
||||
|
||||
import "syscall"
|
||||
|
||||
const FD_SETSIZE = 64
|
||||
|
||||
// FDSet extracted from mingw libs source code
|
||||
type FDSet struct {
|
||||
fd_count uint
|
||||
fd_array [FD_SETSIZE]uintptr
|
||||
}
|
||||
|
||||
// Set adds the fd to the set
|
||||
func (fds *FDSet) Set(fd uintptr) {
|
||||
var i uint
|
||||
for i = 0; i < fds.fd_count; i++ {
|
||||
if fds.fd_array[i] == fd {
|
||||
break
|
||||
}
|
||||
}
|
||||
if i == fds.fd_count {
|
||||
if fds.fd_count < FD_SETSIZE {
|
||||
fds.fd_array[i] = fd
|
||||
fds.fd_count++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear remove the fd from the set
|
||||
func (fds *FDSet) Clear(fd uintptr) {
|
||||
var i uint
|
||||
for i = 0; i < fds.fd_count; i++ {
|
||||
if fds.fd_array[i] == fd {
|
||||
for i < fds.fd_count-1 {
|
||||
fds.fd_array[i] = fds.fd_array[i+1]
|
||||
i++
|
||||
}
|
||||
fds.fd_count--
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IsSet check if the given fd is set
|
||||
func (fds *FDSet) IsSet(fd uintptr) bool {
|
||||
if isset, err := __WSAFDIsSet(syscall.Handle(fd), fds); err == nil && isset != 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Zero empties the Set
|
||||
func (fds *FDSet) Zero() {
|
||||
fds.fd_count = 0
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package goselect
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Select wraps syscall.Select with Go types
|
||||
func Select(n int, r, w, e *FDSet, timeout time.Duration) error {
|
||||
var timeval *syscall.Timeval
|
||||
if timeout >= 0 {
|
||||
t := syscall.NsecToTimeval(timeout.Nanoseconds())
|
||||
timeval = &t
|
||||
}
|
||||
|
||||
return sysSelect(n, r, w, e, timeval)
|
||||
}
|
||||
|
||||
// RetrySelect wraps syscall.Select with Go types, and retries a number of times, with a given retryDelay.
|
||||
func RetrySelect(n int, r, w, e *FDSet, timeout time.Duration, retries int, retryDelay time.Duration) (err error) {
|
||||
for i := 0; i < retries; i++ {
|
||||
if err = Select(n, r, w, e, timeout); err != syscall.EINTR {
|
||||
return err
|
||||
}
|
||||
time.Sleep(retryDelay)
|
||||
}
|
||||
return err
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
// +build linux
|
||||
|
||||
package goselect
|
||||
|
||||
import "syscall"
|
||||
|
||||
func sysSelect(n int, r, w, e *FDSet, timeout *syscall.Timeval) error {
|
||||
_, err := syscall.Select(n, (*syscall.FdSet)(r), (*syscall.FdSet)(w), (*syscall.FdSet)(e), timeout)
|
||||
return err
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
// +build !linux,!windows,!plan9,!solaris
|
||||
|
||||
package goselect
|
||||
|
||||
import "syscall"
|
||||
|
||||
func sysSelect(n int, r, w, e *FDSet, timeout *syscall.Timeval) error {
|
||||
return syscall.Select(n, (*syscall.FdSet)(r), (*syscall.FdSet)(w), (*syscall.FdSet)(e), timeout)
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
// +build plan9 solaris
|
||||
|
||||
package goselect
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// ErrUnsupported .
|
||||
var ErrUnsupported = fmt.Errorf("Platofrm %s/%s unsupported", runtime.GOOS, runtime.GOARCH)
|
||||
|
||||
func sysSelect(n int, r, w, e *FDSet, timeout *syscall.Timeval) error {
|
||||
return ErrUnsupported
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
// +build windows
|
||||
|
||||
package goselect
|
||||
|
||||
import "syscall"
|
||||
|
||||
//sys _select(nfds int, readfds *FDSet, writefds *FDSet, exceptfds *FDSet, timeout *syscall.Timeval) (total int, err error) = ws2_32.select
|
||||
//sys __WSAFDIsSet(handle syscall.Handle, fdset *FDSet) (isset int, err error) = ws2_32.__WSAFDIsSet
|
||||
|
||||
func sysSelect(n int, r, w, e *FDSet, timeout *syscall.Timeval) error {
|
||||
_, err := _select(n, r, w, e, timeout)
|
||||
return err
|
||||
}
|
|
@ -1,112 +0,0 @@
|
|||
rm -rf crosstest
|
||||
mkdir -p crosstest
|
||||
|
||||
export GOOS=linux; export GOARCH=arm; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=linux; export GOARCH=arm64; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=linux; export GOARCH=amd64; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=linux; export GOARCH=386; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=linux; export GOARCH=mips; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=linux; export GOARCH=mipsle; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=linux; export GOARCH=mips64; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=linux; export GOARCH=mips64le; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=linux; export GOARCH=ppc64le; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=linux; export GOARCH=s390x; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
|
||||
export GOOS=darwin; export GOARCH=arm; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=darwin; export GOARCH=arm64; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=darwin; export GOARCH=amd64; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=darwin; export GOARCH=386; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=darwin; export GOARCH=mips; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=darwin; export GOARCH=mipsle; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=darwin; export GOARCH=mips64; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=darwin; export GOARCH=mips64le; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=darwin; export GOARCH=ppc64le; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=darwin; export GOARCH=s390x; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
|
||||
export GOOS=freebsd; export GOARCH=arm; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=freebsd; export GOARCH=arm64; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=freebsd; export GOARCH=amd64; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=freebsd; export GOARCH=386; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=freebsd; export GOARCH=mips; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=freebsd; export GOARCH=mipsle; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=freebsd; export GOARCH=mips64; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=freebsd; export GOARCH=mips64le; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=freebsd; export GOARCH=ppc64le; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=freebsd; export GOARCH=s390x; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
|
||||
export GOOS=openbsd; export GOARCH=arm; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=openbsd; export GOARCH=arm64; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=openbsd; export GOARCH=amd64; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=openbsd; export GOARCH=386; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=openbsd; export GOARCH=mips; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=openbsd; export GOARCH=mipsle; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=openbsd; export GOARCH=mips64; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=openbsd; export GOARCH=mips64le; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=openbsd; export GOARCH=ppc64le; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=openbsd; export GOARCH=s390x; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
|
||||
export GOOS=netbsd; export GOARCH=arm; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=netbsd; export GOARCH=arm64; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=netbsd; export GOARCH=amd64; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=netbsd; export GOARCH=386; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=netbsd; export GOARCH=mips; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=netbsd; export GOARCH=mipsle; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=netbsd; export GOARCH=mips64; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=netbsd; export GOARCH=mips64le; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=netbsd; export GOARCH=ppc64le; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=netbsd; export GOARCH=s390x; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
|
||||
export GOOS=dragonfly; export GOARCH=arm; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=dragonfly; export GOARCH=arm64; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=dragonfly; export GOARCH=amd64; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=dragonfly; export GOARCH=386; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=dragonfly; export GOARCH=mips; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=dragonfly; export GOARCH=mipsle; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=dragonfly; export GOARCH=mips64; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=dragonfly; export GOARCH=mips64le; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=dragonfly; export GOARCH=ppc64le; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=dragonfly; export GOARCH=s390x; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
|
||||
export GOOS=solaris; export GOARCH=arm; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=solaris; export GOARCH=arm64; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=solaris; export GOARCH=amd64; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=solaris; export GOARCH=386; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=solaris; export GOARCH=mips; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=solaris; export GOARCH=mipsle; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=solaris; export GOARCH=mips64; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=solaris; export GOARCH=mips64le; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=solaris; export GOARCH=ppc64le; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=solaris; export GOARCH=s390x; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
|
||||
export GOOS=plan9; export GOARCH=arm; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=plan9; export GOARCH=arm64; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=plan9; export GOARCH=amd64; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=plan9; export GOARCH=386; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=plan9; export GOARCH=mips; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=plan9; export GOARCH=mipsle; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=plan9; export GOARCH=mips64; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=plan9; export GOARCH=mips64le; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=plan9; export GOARCH=ppc64le; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=plan9; export GOARCH=s390x; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
|
||||
export GOOS=windows; export GOARCH=arm; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=windows; export GOARCH=arm64; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=windows; export GOARCH=amd64; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=windows; export GOARCH=386; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=windows; export GOARCH=mips; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=windows; export GOARCH=mipsle; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=windows; export GOARCH=mips64; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=windows; export GOARCH=mips64le; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=windows; export GOARCH=ppc64le; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=windows; export GOARCH=s390x; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
|
||||
export GOOS=android; export GOARCH=arm; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=android; export GOARCH=arm64; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=android; export GOARCH=amd64; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=android; export GOARCH=386; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=android; export GOARCH=mips; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=android; export GOARCH=mipsle; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=android; export GOARCH=mips64; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=android; export GOARCH=mips64le; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=android; export GOARCH=ppc64le; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
||||
export GOOS=android; export GOARCH=s390x; echo "$(go build -o crosstest/${GOOS}_${GOARCH} && echo '[OK]' || echo '[KO]') $GOOS/$GOARCH";
|
|
@ -1,39 +0,0 @@
|
|||
// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT
|
||||
|
||||
package goselect
|
||||
|
||||
import "unsafe"
|
||||
import "syscall"
|
||||
|
||||
var _ unsafe.Pointer
|
||||
|
||||
var (
|
||||
modws2_32 = syscall.NewLazyDLL("ws2_32.dll")
|
||||
|
||||
procselect = modws2_32.NewProc("select")
|
||||
proc__WSAFDIsSet = modws2_32.NewProc("__WSAFDIsSet")
|
||||
)
|
||||
|
||||
func _select(nfds int, readfds *FDSet, writefds *FDSet, exceptfds *FDSet, timeout *syscall.Timeval) (total int, err error) {
|
||||
r0, _, e1 := syscall.Syscall6(procselect.Addr(), 5, uintptr(nfds), uintptr(unsafe.Pointer(readfds)), uintptr(unsafe.Pointer(writefds)), uintptr(unsafe.Pointer(exceptfds)), uintptr(unsafe.Pointer(timeout)), 0)
|
||||
total = int(r0)
|
||||
if total == 0 {
|
||||
if e1 != 0 {
|
||||
err = error(e1)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func __WSAFDIsSet(handle syscall.Handle, fdset *FDSet) (isset int, err error) {
|
||||
r0, _, e1 := syscall.Syscall(proc__WSAFDIsSet.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(fdset)), 0)
|
||||
isset = int(r0)
|
||||
if isset == 0 {
|
||||
if e1 != 0 {
|
||||
err = error(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,19 +0,0 @@
|
|||
Docker
|
||||
Copyright 2012-2017 Docker, Inc.
|
||||
|
||||
This product includes software developed at Docker, Inc. (https://www.docker.com).
|
||||
|
||||
This product contains software (https://github.com/kr/pty) developed
|
||||
by Keith Rarick, licensed under the MIT License.
|
||||
|
||||
The following is courtesy of our legal counsel:
|
||||
|
||||
|
||||
Use and transfer of Docker may be subject to certain restrictions by the
|
||||
United States and other governments.
|
||||
It is your responsibility to ensure that your use and/or transfer does not
|
||||
violate applicable laws.
|
||||
|
||||
For more information, please see https://www.bis.doc.gov
|
||||
|
||||
See also https://www.apache.org/dev/crypto.html and/or seek legal counsel.
|
|
@ -1,21 +0,0 @@
|
|||
sudo: false
|
||||
language: go
|
||||
go:
|
||||
- 1.3.x
|
||||
- 1.5.x
|
||||
- 1.6.x
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
- master
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: master
|
||||
fast_finish: true
|
||||
install:
|
||||
- # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step).
|
||||
script:
|
||||
- go get -t -v ./...
|
||||
- diff -u <(echo -n) <(gofmt -d -s .)
|
||||
- go tool vet .
|
||||
- go test -v -race ./...
|
|
@ -1,21 +0,0 @@
|
|||
Copyright (c) 2005-2008 Dustin Sallings <dustin@spy.net>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
<http://www.opensource.org/licenses/mit-license.php>
|
|
@ -1,124 +0,0 @@
|
|||
# Humane Units [![Build Status](https://travis-ci.org/dustin/go-humanize.svg?branch=master)](https://travis-ci.org/dustin/go-humanize) [![GoDoc](https://godoc.org/github.com/dustin/go-humanize?status.svg)](https://godoc.org/github.com/dustin/go-humanize)
|
||||
|
||||
Just a few functions for helping humanize times and sizes.
|
||||
|
||||
`go get` it as `github.com/dustin/go-humanize`, import it as
|
||||
`"github.com/dustin/go-humanize"`, use it as `humanize`.
|
||||
|
||||
See [godoc](https://godoc.org/github.com/dustin/go-humanize) for
|
||||
complete documentation.
|
||||
|
||||
## Sizes
|
||||
|
||||
This lets you take numbers like `82854982` and convert them to useful
|
||||
strings like, `83 MB` or `79 MiB` (whichever you prefer).
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
fmt.Printf("That file is %s.", humanize.Bytes(82854982)) // That file is 83 MB.
|
||||
```
|
||||
|
||||
## Times
|
||||
|
||||
This lets you take a `time.Time` and spit it out in relative terms.
|
||||
For example, `12 seconds ago` or `3 days from now`.
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
fmt.Printf("This was touched %s.", humanize.Time(someTimeInstance)) // This was touched 7 hours ago.
|
||||
```
|
||||
|
||||
Thanks to Kyle Lemons for the time implementation from an IRC
|
||||
conversation one day. It's pretty neat.
|
||||
|
||||
## Ordinals
|
||||
|
||||
From a [mailing list discussion][odisc] where a user wanted to be able
|
||||
to label ordinals.
|
||||
|
||||
0 -> 0th
|
||||
1 -> 1st
|
||||
2 -> 2nd
|
||||
3 -> 3rd
|
||||
4 -> 4th
|
||||
[...]
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
fmt.Printf("You're my %s best friend.", humanize.Ordinal(193)) // You are my 193rd best friend.
|
||||
```
|
||||
|
||||
## Commas
|
||||
|
||||
Want to shove commas into numbers? Be my guest.
|
||||
|
||||
0 -> 0
|
||||
100 -> 100
|
||||
1000 -> 1,000
|
||||
1000000000 -> 1,000,000,000
|
||||
-100000 -> -100,000
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
fmt.Printf("You owe $%s.\n", humanize.Comma(6582491)) // You owe $6,582,491.
|
||||
```
|
||||
|
||||
## Ftoa
|
||||
|
||||
Nicer float64 formatter that removes trailing zeros.
|
||||
|
||||
```go
|
||||
fmt.Printf("%f", 2.24) // 2.240000
|
||||
fmt.Printf("%s", humanize.Ftoa(2.24)) // 2.24
|
||||
fmt.Printf("%f", 2.0) // 2.000000
|
||||
fmt.Printf("%s", humanize.Ftoa(2.0)) // 2
|
||||
```
|
||||
|
||||
## SI notation
|
||||
|
||||
Format numbers with [SI notation][sinotation].
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
humanize.SI(0.00000000223, "M") // 2.23 nM
|
||||
```
|
||||
|
||||
## English-specific functions
|
||||
|
||||
The following functions are in the `humanize/english` subpackage.
|
||||
|
||||
### Plurals
|
||||
|
||||
Simple English pluralization
|
||||
|
||||
```go
|
||||
english.PluralWord(1, "object", "") // object
|
||||
english.PluralWord(42, "object", "") // objects
|
||||
english.PluralWord(2, "bus", "") // buses
|
||||
english.PluralWord(99, "locus", "loci") // loci
|
||||
|
||||
english.Plural(1, "object", "") // 1 object
|
||||
english.Plural(42, "object", "") // 42 objects
|
||||
english.Plural(2, "bus", "") // 2 buses
|
||||
english.Plural(99, "locus", "loci") // 99 loci
|
||||
```
|
||||
|
||||
### Word series
|
||||
|
||||
Format comma-separated words lists with conjuctions:
|
||||
|
||||
```go
|
||||
english.WordSeries([]string{"foo"}, "and") // foo
|
||||
english.WordSeries([]string{"foo", "bar"}, "and") // foo and bar
|
||||
english.WordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar and baz
|
||||
|
||||
english.OxfordWordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar, and baz
|
||||
```
|
||||
|
||||
[odisc]: https://groups.google.com/d/topic/golang-nuts/l8NhI74jl-4/discussion
|
||||
[sinotation]: http://en.wikipedia.org/wiki/Metric_prefix
|
|
@ -1,31 +0,0 @@
|
|||
package humanize
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// order of magnitude (to a max order)
|
||||
func oomm(n, b *big.Int, maxmag int) (float64, int) {
|
||||
mag := 0
|
||||
m := &big.Int{}
|
||||
for n.Cmp(b) >= 0 {
|
||||
n.DivMod(n, b, m)
|
||||
mag++
|
||||
if mag == maxmag && maxmag >= 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag
|
||||
}
|
||||
|
||||
// total order of magnitude
|
||||
// (same as above, but with no upper limit)
|
||||
func oom(n, b *big.Int) (float64, int) {
|
||||
mag := 0
|
||||
m := &big.Int{}
|
||||
for n.Cmp(b) >= 0 {
|
||||
n.DivMod(n, b, m)
|
||||
mag++
|
||||
}
|
||||
return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag
|
||||
}
|
|
@ -1,173 +0,0 @@
|
|||
package humanize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var (
|
||||
bigIECExp = big.NewInt(1024)
|
||||
|
||||
// BigByte is one byte in bit.Ints
|
||||
BigByte = big.NewInt(1)
|
||||
// BigKiByte is 1,024 bytes in bit.Ints
|
||||
BigKiByte = (&big.Int{}).Mul(BigByte, bigIECExp)
|
||||
// BigMiByte is 1,024 k bytes in bit.Ints
|
||||
BigMiByte = (&big.Int{}).Mul(BigKiByte, bigIECExp)
|
||||
// BigGiByte is 1,024 m bytes in bit.Ints
|
||||
BigGiByte = (&big.Int{}).Mul(BigMiByte, bigIECExp)
|
||||
// BigTiByte is 1,024 g bytes in bit.Ints
|
||||
BigTiByte = (&big.Int{}).Mul(BigGiByte, bigIECExp)
|
||||
// BigPiByte is 1,024 t bytes in bit.Ints
|
||||
BigPiByte = (&big.Int{}).Mul(BigTiByte, bigIECExp)
|
||||
// BigEiByte is 1,024 p bytes in bit.Ints
|
||||
BigEiByte = (&big.Int{}).Mul(BigPiByte, bigIECExp)
|
||||
// BigZiByte is 1,024 e bytes in bit.Ints
|
||||
BigZiByte = (&big.Int{}).Mul(BigEiByte, bigIECExp)
|
||||
// BigYiByte is 1,024 z bytes in bit.Ints
|
||||
BigYiByte = (&big.Int{}).Mul(BigZiByte, bigIECExp)
|
||||
)
|
||||
|
||||
var (
|
||||
bigSIExp = big.NewInt(1000)
|
||||
|
||||
// BigSIByte is one SI byte in big.Ints
|
||||
BigSIByte = big.NewInt(1)
|
||||
// BigKByte is 1,000 SI bytes in big.Ints
|
||||
BigKByte = (&big.Int{}).Mul(BigSIByte, bigSIExp)
|
||||
// BigMByte is 1,000 SI k bytes in big.Ints
|
||||
BigMByte = (&big.Int{}).Mul(BigKByte, bigSIExp)
|
||||
// BigGByte is 1,000 SI m bytes in big.Ints
|
||||
BigGByte = (&big.Int{}).Mul(BigMByte, bigSIExp)
|
||||
// BigTByte is 1,000 SI g bytes in big.Ints
|
||||
BigTByte = (&big.Int{}).Mul(BigGByte, bigSIExp)
|
||||
// BigPByte is 1,000 SI t bytes in big.Ints
|
||||
BigPByte = (&big.Int{}).Mul(BigTByte, bigSIExp)
|
||||
// BigEByte is 1,000 SI p bytes in big.Ints
|
||||
BigEByte = (&big.Int{}).Mul(BigPByte, bigSIExp)
|
||||
// BigZByte is 1,000 SI e bytes in big.Ints
|
||||
BigZByte = (&big.Int{}).Mul(BigEByte, bigSIExp)
|
||||
// BigYByte is 1,000 SI z bytes in big.Ints
|
||||
BigYByte = (&big.Int{}).Mul(BigZByte, bigSIExp)
|
||||
)
|
||||
|
||||
var bigBytesSizeTable = map[string]*big.Int{
|
||||
"b": BigByte,
|
||||
"kib": BigKiByte,
|
||||
"kb": BigKByte,
|
||||
"mib": BigMiByte,
|
||||
"mb": BigMByte,
|
||||
"gib": BigGiByte,
|
||||
"gb": BigGByte,
|
||||
"tib": BigTiByte,
|
||||
"tb": BigTByte,
|
||||
"pib": BigPiByte,
|
||||
"pb": BigPByte,
|
||||
"eib": BigEiByte,
|
||||
"eb": BigEByte,
|
||||
"zib": BigZiByte,
|
||||
"zb": BigZByte,
|
||||
"yib": BigYiByte,
|
||||
"yb": BigYByte,
|
||||
// Without suffix
|
||||
"": BigByte,
|
||||
"ki": BigKiByte,
|
||||
"k": BigKByte,
|
||||
"mi": BigMiByte,
|
||||
"m": BigMByte,
|
||||
"gi": BigGiByte,
|
||||
"g": BigGByte,
|
||||
"ti": BigTiByte,
|
||||
"t": BigTByte,
|
||||
"pi": BigPiByte,
|
||||
"p": BigPByte,
|
||||
"ei": BigEiByte,
|
||||
"e": BigEByte,
|
||||
"z": BigZByte,
|
||||
"zi": BigZiByte,
|
||||
"y": BigYByte,
|
||||
"yi": BigYiByte,
|
||||
}
|
||||
|
||||
var ten = big.NewInt(10)
|
||||
|
||||
func humanateBigBytes(s, base *big.Int, sizes []string) string {
|
||||
if s.Cmp(ten) < 0 {
|
||||
return fmt.Sprintf("%d B", s)
|
||||
}
|
||||
c := (&big.Int{}).Set(s)
|
||||
val, mag := oomm(c, base, len(sizes)-1)
|
||||
suffix := sizes[mag]
|
||||
f := "%.0f %s"
|
||||
if val < 10 {
|
||||
f = "%.1f %s"
|
||||
}
|
||||
|
||||
return fmt.Sprintf(f, val, suffix)
|
||||
|
||||
}
|
||||
|
||||
// BigBytes produces a human readable representation of an SI size.
|
||||
//
|
||||
// See also: ParseBigBytes.
|
||||
//
|
||||
// BigBytes(82854982) -> 83 MB
|
||||
func BigBytes(s *big.Int) string {
|
||||
sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
|
||||
return humanateBigBytes(s, bigSIExp, sizes)
|
||||
}
|
||||
|
||||
// BigIBytes produces a human readable representation of an IEC size.
|
||||
//
|
||||
// See also: ParseBigBytes.
|
||||
//
|
||||
// BigIBytes(82854982) -> 79 MiB
|
||||
func BigIBytes(s *big.Int) string {
|
||||
sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"}
|
||||
return humanateBigBytes(s, bigIECExp, sizes)
|
||||
}
|
||||
|
||||
// ParseBigBytes parses a string representation of bytes into the number
|
||||
// of bytes it represents.
|
||||
//
|
||||
// See also: BigBytes, BigIBytes.
|
||||
//
|
||||
// ParseBigBytes("42 MB") -> 42000000, nil
|
||||
// ParseBigBytes("42 mib") -> 44040192, nil
|
||||
func ParseBigBytes(s string) (*big.Int, error) {
|
||||
lastDigit := 0
|
||||
hasComma := false
|
||||
for _, r := range s {
|
||||
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
|
||||
break
|
||||
}
|
||||
if r == ',' {
|
||||
hasComma = true
|
||||
}
|
||||
lastDigit++
|
||||
}
|
||||
|
||||
num := s[:lastDigit]
|
||||
if hasComma {
|
||||
num = strings.Replace(num, ",", "", -1)
|
||||
}
|
||||
|
||||
val := &big.Rat{}
|
||||
_, err := fmt.Sscanf(num, "%f", val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
|
||||
if m, ok := bigBytesSizeTable[extra]; ok {
|
||||
mv := (&big.Rat{}).SetInt(m)
|
||||
val.Mul(val, mv)
|
||||
rv := &big.Int{}
|
||||
rv.Div(val.Num(), val.Denom())
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unhandled size name: %v", extra)
|
||||
}
|
|
@ -1,143 +0,0 @@
|
|||
package humanize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// IEC Sizes.
|
||||
// kibis of bits
|
||||
const (
|
||||
Byte = 1 << (iota * 10)
|
||||
KiByte
|
||||
MiByte
|
||||
GiByte
|
||||
TiByte
|
||||
PiByte
|
||||
EiByte
|
||||
)
|
||||
|
||||
// SI Sizes.
|
||||
const (
|
||||
IByte = 1
|
||||
KByte = IByte * 1000
|
||||
MByte = KByte * 1000
|
||||
GByte = MByte * 1000
|
||||
TByte = GByte * 1000
|
||||
PByte = TByte * 1000
|
||||
EByte = PByte * 1000
|
||||
)
|
||||
|
||||
var bytesSizeTable = map[string]uint64{
|
||||
"b": Byte,
|
||||
"kib": KiByte,
|
||||
"kb": KByte,
|
||||
"mib": MiByte,
|
||||
"mb": MByte,
|
||||
"gib": GiByte,
|
||||
"gb": GByte,
|
||||
"tib": TiByte,
|
||||
"tb": TByte,
|
||||
"pib": PiByte,
|
||||
"pb": PByte,
|
||||
"eib": EiByte,
|
||||
"eb": EByte,
|
||||
// Without suffix
|
||||
"": Byte,
|
||||
"ki": KiByte,
|
||||
"k": KByte,
|
||||
"mi": MiByte,
|
||||
"m": MByte,
|
||||
"gi": GiByte,
|
||||
"g": GByte,
|
||||
"ti": TiByte,
|
||||
"t": TByte,
|
||||
"pi": PiByte,
|
||||
"p": PByte,
|
||||
"ei": EiByte,
|
||||
"e": EByte,
|
||||
}
|
||||
|
||||
func logn(n, b float64) float64 {
|
||||
return math.Log(n) / math.Log(b)
|
||||
}
|
||||
|
||||
func humanateBytes(s uint64, base float64, sizes []string) string {
|
||||
if s < 10 {
|
||||
return fmt.Sprintf("%d B", s)
|
||||
}
|
||||
e := math.Floor(logn(float64(s), base))
|
||||
suffix := sizes[int(e)]
|
||||
val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
|
||||
f := "%.0f %s"
|
||||
if val < 10 {
|
||||
f = "%.1f %s"
|
||||
}
|
||||
|
||||
return fmt.Sprintf(f, val, suffix)
|
||||
}
|
||||
|
||||
// Bytes produces a human readable representation of an SI size.
|
||||
//
|
||||
// See also: ParseBytes.
|
||||
//
|
||||
// Bytes(82854982) -> 83 MB
|
||||
func Bytes(s uint64) string {
|
||||
sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
|
||||
return humanateBytes(s, 1000, sizes)
|
||||
}
|
||||
|
||||
// IBytes produces a human readable representation of an IEC size.
|
||||
//
|
||||
// See also: ParseBytes.
|
||||
//
|
||||
// IBytes(82854982) -> 79 MiB
|
||||
func IBytes(s uint64) string {
|
||||
sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
|
||||
return humanateBytes(s, 1024, sizes)
|
||||
}
|
||||
|
||||
// ParseBytes parses a string representation of bytes into the number
|
||||
// of bytes it represents.
|
||||
//
|
||||
// See Also: Bytes, IBytes.
|
||||
//
|
||||
// ParseBytes("42 MB") -> 42000000, nil
|
||||
// ParseBytes("42 mib") -> 44040192, nil
|
||||
func ParseBytes(s string) (uint64, error) {
|
||||
lastDigit := 0
|
||||
hasComma := false
|
||||
for _, r := range s {
|
||||
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
|
||||
break
|
||||
}
|
||||
if r == ',' {
|
||||
hasComma = true
|
||||
}
|
||||
lastDigit++
|
||||
}
|
||||
|
||||
num := s[:lastDigit]
|
||||
if hasComma {
|
||||
num = strings.Replace(num, ",", "", -1)
|
||||
}
|
||||
|
||||
f, err := strconv.ParseFloat(num, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
|
||||
if m, ok := bytesSizeTable[extra]; ok {
|
||||
f *= float64(m)
|
||||
if f >= math.MaxUint64 {
|
||||
return 0, fmt.Errorf("too large: %v", s)
|
||||
}
|
||||
return uint64(f), nil
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("unhandled size name: %v", extra)
|
||||
}
|
|
@ -1,116 +0,0 @@
|
|||
package humanize
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Comma produces a string form of the given number in base 10 with
|
||||
// commas after every three orders of magnitude.
|
||||
//
|
||||
// e.g. Comma(834142) -> 834,142
|
||||
func Comma(v int64) string {
|
||||
sign := ""
|
||||
|
||||
// Min int64 can't be negated to a usable value, so it has to be special cased.
|
||||
if v == math.MinInt64 {
|
||||
return "-9,223,372,036,854,775,808"
|
||||
}
|
||||
|
||||
if v < 0 {
|
||||
sign = "-"
|
||||
v = 0 - v
|
||||
}
|
||||
|
||||
parts := []string{"", "", "", "", "", "", ""}
|
||||
j := len(parts) - 1
|
||||
|
||||
for v > 999 {
|
||||
parts[j] = strconv.FormatInt(v%1000, 10)
|
||||
switch len(parts[j]) {
|
||||
case 2:
|
||||
parts[j] = "0" + parts[j]
|
||||
case 1:
|
||||
parts[j] = "00" + parts[j]
|
||||
}
|
||||
v = v / 1000
|
||||
j--
|
||||
}
|
||||
parts[j] = strconv.Itoa(int(v))
|
||||
return sign + strings.Join(parts[j:], ",")
|
||||
}
|
||||
|
||||
// Commaf produces a string form of the given number in base 10 with
|
||||
// commas after every three orders of magnitude.
|
||||
//
|
||||
// e.g. Commaf(834142.32) -> 834,142.32
|
||||
func Commaf(v float64) string {
|
||||
buf := &bytes.Buffer{}
|
||||
if v < 0 {
|
||||
buf.Write([]byte{'-'})
|
||||
v = 0 - v
|
||||
}
|
||||
|
||||
comma := []byte{','}
|
||||
|
||||
parts := strings.Split(strconv.FormatFloat(v, 'f', -1, 64), ".")
|
||||
pos := 0
|
||||
if len(parts[0])%3 != 0 {
|
||||
pos += len(parts[0]) % 3
|
||||
buf.WriteString(parts[0][:pos])
|
||||
buf.Write(comma)
|
||||
}
|
||||
for ; pos < len(parts[0]); pos += 3 {
|
||||
buf.WriteString(parts[0][pos : pos+3])
|
||||
buf.Write(comma)
|
||||
}
|
||||
buf.Truncate(buf.Len() - 1)
|
||||
|
||||
if len(parts) > 1 {
|
||||
buf.Write([]byte{'.'})
|
||||
buf.WriteString(parts[1])
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// CommafWithDigits works like the Commaf but limits the resulting
|
||||
// string to the given number of decimal places.
|
||||
//
|
||||
// e.g. CommafWithDigits(834142.32, 1) -> 834,142.3
|
||||
func CommafWithDigits(f float64, decimals int) string {
|
||||
return stripTrailingDigits(Commaf(f), decimals)
|
||||
}
|
||||
|
||||
// BigComma produces a string form of the given big.Int in base 10
|
||||
// with commas after every three orders of magnitude.
|
||||
func BigComma(b *big.Int) string {
|
||||
sign := ""
|
||||
if b.Sign() < 0 {
|
||||
sign = "-"
|
||||
b.Abs(b)
|
||||
}
|
||||
|
||||
athousand := big.NewInt(1000)
|
||||
c := (&big.Int{}).Set(b)
|
||||
_, m := oom(c, athousand)
|
||||
parts := make([]string, m+1)
|
||||
j := len(parts) - 1
|
||||
|
||||
mod := &big.Int{}
|
||||
for b.Cmp(athousand) >= 0 {
|
||||
b.DivMod(b, athousand, mod)
|
||||
parts[j] = strconv.FormatInt(mod.Int64(), 10)
|
||||
switch len(parts[j]) {
|
||||
case 2:
|
||||
parts[j] = "0" + parts[j]
|
||||
case 1:
|
||||
parts[j] = "00" + parts[j]
|
||||
}
|
||||
j--
|
||||
}
|
||||
parts[j] = strconv.Itoa(int(b.Int64()))
|
||||
return sign + strings.Join(parts[j:], ",")
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
// +build go1.6
|
||||
|
||||
package humanize
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math/big"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// BigCommaf produces a string form of the given big.Float in base 10
|
||||
// with commas after every three orders of magnitude.
|
||||
func BigCommaf(v *big.Float) string {
|
||||
buf := &bytes.Buffer{}
|
||||
if v.Sign() < 0 {
|
||||
buf.Write([]byte{'-'})
|
||||
v.Abs(v)
|
||||
}
|
||||
|
||||
comma := []byte{','}
|
||||
|
||||
parts := strings.Split(v.Text('f', -1), ".")
|
||||
pos := 0
|
||||
if len(parts[0])%3 != 0 {
|
||||
pos += len(parts[0]) % 3
|
||||
buf.WriteString(parts[0][:pos])
|
||||
buf.Write(comma)
|
||||
}
|
||||
for ; pos < len(parts[0]); pos += 3 {
|
||||
buf.WriteString(parts[0][pos : pos+3])
|
||||
buf.Write(comma)
|
||||
}
|
||||
buf.Truncate(buf.Len() - 1)
|
||||
|
||||
if len(parts) > 1 {
|
||||
buf.Write([]byte{'.'})
|
||||
buf.WriteString(parts[1])
|
||||
}
|
||||
return buf.String()
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
package humanize
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func stripTrailingZeros(s string) string {
|
||||
offset := len(s) - 1
|
||||
for offset > 0 {
|
||||
if s[offset] == '.' {
|
||||
offset--
|
||||
break
|
||||
}
|
||||
if s[offset] != '0' {
|
||||
break
|
||||
}
|
||||
offset--
|
||||
}
|
||||
return s[:offset+1]
|
||||
}
|
||||
|
||||
func stripTrailingDigits(s string, digits int) string {
|
||||
if i := strings.Index(s, "."); i >= 0 {
|
||||
if digits <= 0 {
|
||||
return s[:i]
|
||||
}
|
||||
i++
|
||||
if i+digits >= len(s) {
|
||||
return s
|
||||
}
|
||||
return s[:i+digits]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Ftoa converts a float to a string with no trailing zeros.
|
||||
func Ftoa(num float64) string {
|
||||
return stripTrailingZeros(strconv.FormatFloat(num, 'f', 6, 64))
|
||||
}
|
||||
|
||||
// FtoaWithDigits converts a float to a string but limits the resulting string
|
||||
// to the given number of decimal places, and no trailing zeros.
|
||||
func FtoaWithDigits(num float64, digits int) string {
|
||||
return stripTrailingZeros(stripTrailingDigits(strconv.FormatFloat(num, 'f', 6, 64), digits))
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
/*
|
||||
Package humanize converts boring ugly numbers to human-friendly strings and back.
|
||||
|
||||
Durations can be turned into strings such as "3 days ago", numbers
|
||||
representing sizes like 82854982 into useful strings like, "83 MB" or
|
||||
"79 MiB" (whichever you prefer).
|
||||
*/
|
||||
package humanize
|
|
@ -1,192 +0,0 @@
|
|||
package humanize
|
||||
|
||||
/*
|
||||
Slightly adapted from the source to fit go-humanize.
|
||||
|
||||
Author: https://github.com/gorhill
|
||||
Source: https://gist.github.com/gorhill/5285193
|
||||
|
||||
*/
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var (
|
||||
renderFloatPrecisionMultipliers = [...]float64{
|
||||
1,
|
||||
10,
|
||||
100,
|
||||
1000,
|
||||
10000,
|
||||
100000,
|
||||
1000000,
|
||||
10000000,
|
||||
100000000,
|
||||
1000000000,
|
||||
}
|
||||
|
||||
renderFloatPrecisionRounders = [...]float64{
|
||||
0.5,
|
||||
0.05,
|
||||
0.005,
|
||||
0.0005,
|
||||
0.00005,
|
||||
0.000005,
|
||||
0.0000005,
|
||||
0.00000005,
|
||||
0.000000005,
|
||||
0.0000000005,
|
||||
}
|
||||
)
|
||||
|
||||
// FormatFloat produces a formatted number as string based on the following user-specified criteria:
|
||||
// * thousands separator
|
||||
// * decimal separator
|
||||
// * decimal precision
|
||||
//
|
||||
// Usage: s := RenderFloat(format, n)
|
||||
// The format parameter tells how to render the number n.
|
||||
//
|
||||
// See examples: http://play.golang.org/p/LXc1Ddm1lJ
|
||||
//
|
||||
// Examples of format strings, given n = 12345.6789:
|
||||
// "#,###.##" => "12,345.67"
|
||||
// "#,###." => "12,345"
|
||||
// "#,###" => "12345,678"
|
||||
// "#\u202F###,##" => "12 345,68"
|
||||
// "#.###,###### => 12.345,678900
|
||||
// "" (aka default format) => 12,345.67
|
||||
//
|
||||
// The highest precision allowed is 9 digits after the decimal symbol.
|
||||
// There is also a version for integer number, FormatInteger(),
|
||||
// which is convenient for calls within template.
|
||||
func FormatFloat(format string, n float64) string {
|
||||
// Special cases:
|
||||
// NaN = "NaN"
|
||||
// +Inf = "+Infinity"
|
||||
// -Inf = "-Infinity"
|
||||
if math.IsNaN(n) {
|
||||
return "NaN"
|
||||
}
|
||||
if n > math.MaxFloat64 {
|
||||
return "Infinity"
|
||||
}
|
||||
if n < -math.MaxFloat64 {
|
||||
return "-Infinity"
|
||||
}
|
||||
|
||||
// default format
|
||||
precision := 2
|
||||
decimalStr := "."
|
||||
thousandStr := ","
|
||||
positiveStr := ""
|
||||
negativeStr := "-"
|
||||
|
||||
if len(format) > 0 {
|
||||
format := []rune(format)
|
||||
|
||||
// If there is an explicit format directive,
|
||||
// then default values are these:
|
||||
precision = 9
|
||||
thousandStr = ""
|
||||
|
||||
// collect indices of meaningful formatting directives
|
||||
formatIndx := []int{}
|
||||
for i, char := range format {
|
||||
if char != '#' && char != '0' {
|
||||
formatIndx = append(formatIndx, i)
|
||||
}
|
||||
}
|
||||
|
||||
if len(formatIndx) > 0 {
|
||||
// Directive at index 0:
|
||||
// Must be a '+'
|
||||
// Raise an error if not the case
|
||||
// index: 0123456789
|
||||
// +0.000,000
|
||||
// +000,000.0
|
||||
// +0000.00
|
||||
// +0000
|
||||
if formatIndx[0] == 0 {
|
||||
if format[formatIndx[0]] != '+' {
|
||||
panic("RenderFloat(): invalid positive sign directive")
|
||||
}
|
||||
positiveStr = "+"
|
||||
formatIndx = formatIndx[1:]
|
||||
}
|
||||
|
||||
// Two directives:
|
||||
// First is thousands separator
|
||||
// Raise an error if not followed by 3-digit
|
||||
// 0123456789
|
||||
// 0.000,000
|
||||
// 000,000.00
|
||||
if len(formatIndx) == 2 {
|
||||
if (formatIndx[1] - formatIndx[0]) != 4 {
|
||||
panic("RenderFloat(): thousands separator directive must be followed by 3 digit-specifiers")
|
||||
}
|
||||
thousandStr = string(format[formatIndx[0]])
|
||||
formatIndx = formatIndx[1:]
|
||||
}
|
||||
|
||||
// One directive:
|
||||
// Directive is decimal separator
|
||||
// The number of digit-specifier following the separator indicates wanted precision
|
||||
// 0123456789
|
||||
// 0.00
|
||||
// 000,0000
|
||||
if len(formatIndx) == 1 {
|
||||
decimalStr = string(format[formatIndx[0]])
|
||||
precision = len(format) - formatIndx[0] - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// generate sign part
|
||||
var signStr string
|
||||
if n >= 0.000000001 {
|
||||
signStr = positiveStr
|
||||
} else if n <= -0.000000001 {
|
||||
signStr = negativeStr
|
||||
n = -n
|
||||
} else {
|
||||
signStr = ""
|
||||
n = 0.0
|
||||
}
|
||||
|
||||
// split number into integer and fractional parts
|
||||
intf, fracf := math.Modf(n + renderFloatPrecisionRounders[precision])
|
||||
|
||||
// generate integer part string
|
||||
intStr := strconv.FormatInt(int64(intf), 10)
|
||||
|
||||
// add thousand separator if required
|
||||
if len(thousandStr) > 0 {
|
||||
for i := len(intStr); i > 3; {
|
||||
i -= 3
|
||||
intStr = intStr[:i] + thousandStr + intStr[i:]
|
||||
}
|
||||
}
|
||||
|
||||
// no fractional part, we can leave now
|
||||
if precision == 0 {
|
||||
return signStr + intStr
|
||||
}
|
||||
|
||||
// generate fractional part
|
||||
fracStr := strconv.Itoa(int(fracf * renderFloatPrecisionMultipliers[precision]))
|
||||
// may need padding
|
||||
if len(fracStr) < precision {
|
||||
fracStr = "000000000000000"[:precision-len(fracStr)] + fracStr
|
||||
}
|
||||
|
||||
return signStr + intStr + decimalStr + fracStr
|
||||
}
|
||||
|
||||
// FormatInteger produces a formatted number as string.
|
||||
// See FormatFloat.
|
||||
func FormatInteger(format string, n int) string {
|
||||
return FormatFloat(format, float64(n))
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
package humanize
|
||||
|
||||
import "strconv"
|
||||
|
||||
// Ordinal gives you the input number in a rank/ordinal format.
|
||||
//
|
||||
// Ordinal(3) -> 3rd
|
||||
func Ordinal(x int) string {
|
||||
suffix := "th"
|
||||
switch x % 10 {
|
||||
case 1:
|
||||
if x%100 != 11 {
|
||||
suffix = "st"
|
||||
}
|
||||
case 2:
|
||||
if x%100 != 12 {
|
||||
suffix = "nd"
|
||||
}
|
||||
case 3:
|
||||
if x%100 != 13 {
|
||||
suffix = "rd"
|
||||
}
|
||||
}
|
||||
return strconv.Itoa(x) + suffix
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
package humanize
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"regexp"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var siPrefixTable = map[float64]string{
|
||||
-24: "y", // yocto
|
||||
-21: "z", // zepto
|
||||
-18: "a", // atto
|
||||
-15: "f", // femto
|
||||
-12: "p", // pico
|
||||
-9: "n", // nano
|
||||
-6: "µ", // micro
|
||||
-3: "m", // milli
|
||||
0: "",
|
||||
3: "k", // kilo
|
||||
6: "M", // mega
|
||||
9: "G", // giga
|
||||
12: "T", // tera
|
||||
15: "P", // peta
|
||||
18: "E", // exa
|
||||
21: "Z", // zetta
|
||||
24: "Y", // yotta
|
||||
}
|
||||
|
||||
var revSIPrefixTable = revfmap(siPrefixTable)
|
||||
|
||||
// revfmap reverses the map and precomputes the power multiplier
|
||||
func revfmap(in map[float64]string) map[string]float64 {
|
||||
rv := map[string]float64{}
|
||||
for k, v := range in {
|
||||
rv[v] = math.Pow(10, k)
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
var riParseRegex *regexp.Regexp
|
||||
|
||||
func init() {
|
||||
ri := `^([\-0-9.]+)\s?([`
|
||||
for _, v := range siPrefixTable {
|
||||
ri += v
|
||||
}
|
||||
ri += `]?)(.*)`
|
||||
|
||||
riParseRegex = regexp.MustCompile(ri)
|
||||
}
|
||||
|
||||
// ComputeSI finds the most appropriate SI prefix for the given number
|
||||
// and returns the prefix along with the value adjusted to be within
|
||||
// that prefix.
|
||||
//
|
||||
// See also: SI, ParseSI.
|
||||
//
|
||||
// e.g. ComputeSI(2.2345e-12) -> (2.2345, "p")
|
||||
func ComputeSI(input float64) (float64, string) {
|
||||
if input == 0 {
|
||||
return 0, ""
|
||||
}
|
||||
mag := math.Abs(input)
|
||||
exponent := math.Floor(logn(mag, 10))
|
||||
exponent = math.Floor(exponent/3) * 3
|
||||
|
||||
value := mag / math.Pow(10, exponent)
|
||||
|
||||
// Handle special case where value is exactly 1000.0
|
||||
// Should return 1 M instead of 1000 k
|
||||
if value == 1000.0 {
|
||||
exponent += 3
|
||||
value = mag / math.Pow(10, exponent)
|
||||
}
|
||||
|
||||
value = math.Copysign(value, input)
|
||||
|
||||
prefix := siPrefixTable[exponent]
|
||||
return value, prefix
|
||||
}
|
||||
|
||||
// SI returns a string with default formatting.
|
||||
//
|
||||
// SI uses Ftoa to format float value, removing trailing zeros.
|
||||
//
|
||||
// See also: ComputeSI, ParseSI.
|
||||
//
|
||||
// e.g. SI(1000000, "B") -> 1 MB
|
||||
// e.g. SI(2.2345e-12, "F") -> 2.2345 pF
|
||||
func SI(input float64, unit string) string {
|
||||
value, prefix := ComputeSI(input)
|
||||
return Ftoa(value) + " " + prefix + unit
|
||||
}
|
||||
|
||||
// SIWithDigits works like SI but limits the resulting string to the
|
||||
// given number of decimal places.
|
||||
//
|
||||
// e.g. SIWithDigits(1000000, 0, "B") -> 1 MB
|
||||
// e.g. SIWithDigits(2.2345e-12, 2, "F") -> 2.23 pF
|
||||
func SIWithDigits(input float64, decimals int, unit string) string {
|
||||
value, prefix := ComputeSI(input)
|
||||
return FtoaWithDigits(value, decimals) + " " + prefix + unit
|
||||
}
|
||||
|
||||
var errInvalid = errors.New("invalid input")
|
||||
|
||||
// ParseSI parses an SI string back into the number and unit.
|
||||
//
|
||||
// See also: SI, ComputeSI.
|
||||
//
|
||||
// e.g. ParseSI("2.2345 pF") -> (2.2345e-12, "F", nil)
|
||||
func ParseSI(input string) (float64, string, error) {
|
||||
found := riParseRegex.FindStringSubmatch(input)
|
||||
if len(found) != 4 {
|
||||
return 0, "", errInvalid
|
||||
}
|
||||
mag := revSIPrefixTable[found[2]]
|
||||
unit := found[3]
|
||||
|
||||
base, err := strconv.ParseFloat(found[1], 64)
|
||||
return base * mag, unit, err
|
||||
}
|
|
@ -1,117 +0,0 @@
|
|||
package humanize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Seconds-based time units
|
||||
const (
|
||||
Day = 24 * time.Hour
|
||||
Week = 7 * Day
|
||||
Month = 30 * Day
|
||||
Year = 12 * Month
|
||||
LongTime = 37 * Year
|
||||
)
|
||||
|
||||
// Time formats a time into a relative string.
|
||||
//
|
||||
// Time(someT) -> "3 weeks ago"
|
||||
func Time(then time.Time) string {
|
||||
return RelTime(then, time.Now(), "ago", "from now")
|
||||
}
|
||||
|
||||
// A RelTimeMagnitude struct contains a relative time point at which
|
||||
// the relative format of time will switch to a new format string. A
|
||||
// slice of these in ascending order by their "D" field is passed to
|
||||
// CustomRelTime to format durations.
|
||||
//
|
||||
// The Format field is a string that may contain a "%s" which will be
|
||||
// replaced with the appropriate signed label (e.g. "ago" or "from
|
||||
// now") and a "%d" that will be replaced by the quantity.
|
||||
//
|
||||
// The DivBy field is the amount of time the time difference must be
|
||||
// divided by in order to display correctly.
|
||||
//
|
||||
// e.g. if D is 2*time.Minute and you want to display "%d minutes %s"
|
||||
// DivBy should be time.Minute so whatever the duration is will be
|
||||
// expressed in minutes.
|
||||
type RelTimeMagnitude struct {
|
||||
D time.Duration
|
||||
Format string
|
||||
DivBy time.Duration
|
||||
}
|
||||
|
||||
var defaultMagnitudes = []RelTimeMagnitude{
|
||||
{time.Second, "now", time.Second},
|
||||
{2 * time.Second, "1 second %s", 1},
|
||||
{time.Minute, "%d seconds %s", time.Second},
|
||||
{2 * time.Minute, "1 minute %s", 1},
|
||||
{time.Hour, "%d minutes %s", time.Minute},
|
||||
{2 * time.Hour, "1 hour %s", 1},
|
||||
{Day, "%d hours %s", time.Hour},
|
||||
{2 * Day, "1 day %s", 1},
|
||||
{Week, "%d days %s", Day},
|
||||
{2 * Week, "1 week %s", 1},
|
||||
{Month, "%d weeks %s", Week},
|
||||
{2 * Month, "1 month %s", 1},
|
||||
{Year, "%d months %s", Month},
|
||||
{18 * Month, "1 year %s", 1},
|
||||
{2 * Year, "2 years %s", 1},
|
||||
{LongTime, "%d years %s", Year},
|
||||
{math.MaxInt64, "a long while %s", 1},
|
||||
}
|
||||
|
||||
// RelTime formats a time into a relative string.
|
||||
//
|
||||
// It takes two times and two labels. In addition to the generic time
|
||||
// delta string (e.g. 5 minutes), the labels are used applied so that
|
||||
// the label corresponding to the smaller time is applied.
|
||||
//
|
||||
// RelTime(timeInPast, timeInFuture, "earlier", "later") -> "3 weeks earlier"
|
||||
func RelTime(a, b time.Time, albl, blbl string) string {
|
||||
return CustomRelTime(a, b, albl, blbl, defaultMagnitudes)
|
||||
}
|
||||
|
||||
// CustomRelTime formats a time into a relative string.
|
||||
//
|
||||
// It takes two times two labels and a table of relative time formats.
|
||||
// In addition to the generic time delta string (e.g. 5 minutes), the
|
||||
// labels are used applied so that the label corresponding to the
|
||||
// smaller time is applied.
|
||||
func CustomRelTime(a, b time.Time, albl, blbl string, magnitudes []RelTimeMagnitude) string {
|
||||
lbl := albl
|
||||
diff := b.Sub(a)
|
||||
|
||||
if a.After(b) {
|
||||
lbl = blbl
|
||||
diff = a.Sub(b)
|
||||
}
|
||||
|
||||
n := sort.Search(len(magnitudes), func(i int) bool {
|
||||
return magnitudes[i].D > diff
|
||||
})
|
||||
|
||||
if n >= len(magnitudes) {
|
||||
n = len(magnitudes) - 1
|
||||
}
|
||||
mag := magnitudes[n]
|
||||
args := []interface{}{}
|
||||
escaped := false
|
||||
for _, ch := range mag.Format {
|
||||
if escaped {
|
||||
switch ch {
|
||||
case 's':
|
||||
args = append(args, lbl)
|
||||
case 'd':
|
||||
args = append(args, diff/mag.DivBy)
|
||||
}
|
||||
escaped = false
|
||||
} else {
|
||||
escaped = ch == '%'
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf(mag.Format, args...)
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
|
||||
.idea/
|
||||
*.iml
|
|
@ -1,19 +0,0 @@
|
|||
language: go
|
||||
sudo: false
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- go: 1.4
|
||||
- go: 1.5
|
||||
- go: 1.6
|
||||
- go: 1.7
|
||||
- go: 1.8
|
||||
- go: tip
|
||||
allow_failures:
|
||||
- go: tip
|
||||
|
||||
script:
|
||||
- go get -t -v ./...
|
||||
- diff -u <(echo -n) <(gofmt -d .)
|
||||
- go vet $(go list ./... | grep -v /vendor/)
|
||||
- go test -v -race ./...
|
|
@ -1,8 +0,0 @@
|
|||
# This is the official list of Gorilla WebSocket authors for copyright
|
||||
# purposes.
|
||||
#
|
||||
# Please keep the list sorted.
|
||||
|
||||
Gary Burd <gary@beagledreams.com>
|
||||
Joachim Bauch <mail@joachim-bauch.de>
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
Copyright (c) 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -1,64 +0,0 @@
|
|||
# Gorilla WebSocket
|
||||
|
||||
Gorilla WebSocket is a [Go](http://golang.org/) implementation of the
|
||||
[WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol.
|
||||
|
||||
[![Build Status](https://travis-ci.org/gorilla/websocket.svg?branch=master)](https://travis-ci.org/gorilla/websocket)
|
||||
[![GoDoc](https://godoc.org/github.com/gorilla/websocket?status.svg)](https://godoc.org/github.com/gorilla/websocket)
|
||||
|
||||
### Documentation
|
||||
|
||||
* [API Reference](http://godoc.org/github.com/gorilla/websocket)
|
||||
* [Chat example](https://github.com/gorilla/websocket/tree/master/examples/chat)
|
||||
* [Command example](https://github.com/gorilla/websocket/tree/master/examples/command)
|
||||
* [Client and server example](https://github.com/gorilla/websocket/tree/master/examples/echo)
|
||||
* [File watch example](https://github.com/gorilla/websocket/tree/master/examples/filewatch)
|
||||
|
||||
### Status
|
||||
|
||||
The Gorilla WebSocket package provides a complete and tested implementation of
|
||||
the [WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol. The
|
||||
package API is stable.
|
||||
|
||||
### Installation
|
||||
|
||||
go get github.com/gorilla/websocket
|
||||
|
||||
### Protocol Compliance
|
||||
|
||||
The Gorilla WebSocket package passes the server tests in the [Autobahn Test
|
||||
Suite](http://autobahn.ws/testsuite) using the application in the [examples/autobahn
|
||||
subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn).
|
||||
|
||||
### Gorilla WebSocket compared with other packages
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th><a href="http://godoc.org/github.com/gorilla/websocket">github.com/gorilla</a></th>
|
||||
<th><a href="http://godoc.org/golang.org/x/net/websocket">golang.org/x/net</a></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr><td colspan="3"><a href="http://tools.ietf.org/html/rfc6455">RFC 6455</a> Features</td></tr>
|
||||
<tr><td>Passes <a href="http://autobahn.ws/testsuite/">Autobahn Test Suite</a></td><td><a href="https://github.com/gorilla/websocket/tree/master/examples/autobahn">Yes</a></td><td>No</td></tr>
|
||||
<tr><td>Receive <a href="https://tools.ietf.org/html/rfc6455#section-5.4">fragmented</a> message<td>Yes</td><td><a href="https://code.google.com/p/go/issues/detail?id=7632">No</a>, see note 1</td></tr>
|
||||
<tr><td>Send <a href="https://tools.ietf.org/html/rfc6455#section-5.5.1">close</a> message</td><td><a href="http://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages">Yes</a></td><td><a href="https://code.google.com/p/go/issues/detail?id=4588">No</a></td></tr>
|
||||
<tr><td>Send <a href="https://tools.ietf.org/html/rfc6455#section-5.5.2">pings</a> and receive <a href="https://tools.ietf.org/html/rfc6455#section-5.5.3">pongs</a></td><td><a href="http://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages">Yes</a></td><td>No</td></tr>
|
||||
<tr><td>Get the <a href="https://tools.ietf.org/html/rfc6455#section-5.6">type</a> of a received data message</td><td>Yes</td><td>Yes, see note 2</td></tr>
|
||||
<tr><td colspan="3">Other Features</tr></td>
|
||||
<tr><td><a href="https://tools.ietf.org/html/rfc7692">Compression Extensions</a></td><td>Experimental</td><td>No</td></tr>
|
||||
<tr><td>Read message using io.Reader</td><td><a href="http://godoc.org/github.com/gorilla/websocket#Conn.NextReader">Yes</a></td><td>No, see note 3</td></tr>
|
||||
<tr><td>Write message using io.WriteCloser</td><td><a href="http://godoc.org/github.com/gorilla/websocket#Conn.NextWriter">Yes</a></td><td>No, see note 3</td></tr>
|
||||
</table>
|
||||
|
||||
Notes:
|
||||
|
||||
1. Large messages are fragmented in [Chrome's new WebSocket implementation](http://www.ietf.org/mail-archive/web/hybi/current/msg10503.html).
|
||||
2. The application can get the type of a received data message by implementing
|
||||
a [Codec marshal](http://godoc.org/golang.org/x/net/websocket#Codec.Marshal)
|
||||
function.
|
||||
3. The go.net io.Reader and io.Writer operate across WebSocket frame boundaries.
|
||||
Read returns when the input buffer is full or a frame boundary is
|
||||
encountered. Each call to Write sends a single frame message. The Gorilla
|
||||
io.Reader and io.WriteCloser operate on a single WebSocket message.
|
||||
|
|
@ -1,392 +0,0 @@
|
|||
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ErrBadHandshake is returned when the server response to opening handshake is
|
||||
// invalid.
|
||||
var ErrBadHandshake = errors.New("websocket: bad handshake")
|
||||
|
||||
var errInvalidCompression = errors.New("websocket: invalid compression negotiation")
|
||||
|
||||
// NewClient creates a new client connection using the given net connection.
|
||||
// The URL u specifies the host and request URI. Use requestHeader to specify
|
||||
// the origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies
|
||||
// (Cookie). Use the response.Header to get the selected subprotocol
|
||||
// (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
|
||||
//
|
||||
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
|
||||
// non-nil *http.Response so that callers can handle redirects, authentication,
|
||||
// etc.
|
||||
//
|
||||
// Deprecated: Use Dialer instead.
|
||||
func NewClient(netConn net.Conn, u *url.URL, requestHeader http.Header, readBufSize, writeBufSize int) (c *Conn, response *http.Response, err error) {
|
||||
d := Dialer{
|
||||
ReadBufferSize: readBufSize,
|
||||
WriteBufferSize: writeBufSize,
|
||||
NetDial: func(net, addr string) (net.Conn, error) {
|
||||
return netConn, nil
|
||||
},
|
||||
}
|
||||
return d.Dial(u.String(), requestHeader)
|
||||
}
|
||||
|
||||
// A Dialer contains options for connecting to WebSocket server.
|
||||
type Dialer struct {
|
||||
// NetDial specifies the dial function for creating TCP connections. If
|
||||
// NetDial is nil, net.Dial is used.
|
||||
NetDial func(network, addr string) (net.Conn, error)
|
||||
|
||||
// Proxy specifies a function to return a proxy for a given
|
||||
// Request. If the function returns a non-nil error, the
|
||||
// request is aborted with the provided error.
|
||||
// If Proxy is nil or returns a nil *URL, no proxy is used.
|
||||
Proxy func(*http.Request) (*url.URL, error)
|
||||
|
||||
// TLSClientConfig specifies the TLS configuration to use with tls.Client.
|
||||
// If nil, the default configuration is used.
|
||||
TLSClientConfig *tls.Config
|
||||
|
||||
// HandshakeTimeout specifies the duration for the handshake to complete.
|
||||
HandshakeTimeout time.Duration
|
||||
|
||||
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer
|
||||
// size is zero, then a useful default size is used. The I/O buffer sizes
|
||||
// do not limit the size of the messages that can be sent or received.
|
||||
ReadBufferSize, WriteBufferSize int
|
||||
|
||||
// Subprotocols specifies the client's requested subprotocols.
|
||||
Subprotocols []string
|
||||
|
||||
// EnableCompression specifies if the client should attempt to negotiate
|
||||
// per message compression (RFC 7692). Setting this value to true does not
|
||||
// guarantee that compression will be supported. Currently only "no context
|
||||
// takeover" modes are supported.
|
||||
EnableCompression bool
|
||||
|
||||
// Jar specifies the cookie jar.
|
||||
// If Jar is nil, cookies are not sent in requests and ignored
|
||||
// in responses.
|
||||
Jar http.CookieJar
|
||||
}
|
||||
|
||||
var errMalformedURL = errors.New("malformed ws or wss URL")
|
||||
|
||||
// parseURL parses the URL.
|
||||
//
|
||||
// This function is a replacement for the standard library url.Parse function.
|
||||
// In Go 1.4 and earlier, url.Parse loses information from the path.
|
||||
func parseURL(s string) (*url.URL, error) {
|
||||
// From the RFC:
|
||||
//
|
||||
// ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
|
||||
// wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]
|
||||
var u url.URL
|
||||
switch {
|
||||
case strings.HasPrefix(s, "ws://"):
|
||||
u.Scheme = "ws"
|
||||
s = s[len("ws://"):]
|
||||
case strings.HasPrefix(s, "wss://"):
|
||||
u.Scheme = "wss"
|
||||
s = s[len("wss://"):]
|
||||
default:
|
||||
return nil, errMalformedURL
|
||||
}
|
||||
|
||||
if i := strings.Index(s, "?"); i >= 0 {
|
||||
u.RawQuery = s[i+1:]
|
||||
s = s[:i]
|
||||
}
|
||||
|
||||
if i := strings.Index(s, "/"); i >= 0 {
|
||||
u.Opaque = s[i:]
|
||||
s = s[:i]
|
||||
} else {
|
||||
u.Opaque = "/"
|
||||
}
|
||||
|
||||
u.Host = s
|
||||
|
||||
if strings.Contains(u.Host, "@") {
|
||||
// Don't bother parsing user information because user information is
|
||||
// not allowed in websocket URIs.
|
||||
return nil, errMalformedURL
|
||||
}
|
||||
|
||||
return &u, nil
|
||||
}
|
||||
|
||||
func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) {
|
||||
hostPort = u.Host
|
||||
hostNoPort = u.Host
|
||||
if i := strings.LastIndex(u.Host, ":"); i > strings.LastIndex(u.Host, "]") {
|
||||
hostNoPort = hostNoPort[:i]
|
||||
} else {
|
||||
switch u.Scheme {
|
||||
case "wss":
|
||||
hostPort += ":443"
|
||||
case "https":
|
||||
hostPort += ":443"
|
||||
default:
|
||||
hostPort += ":80"
|
||||
}
|
||||
}
|
||||
return hostPort, hostNoPort
|
||||
}
|
||||
|
||||
// DefaultDialer is a dialer with all fields set to the default zero values.
|
||||
var DefaultDialer = &Dialer{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
}
|
||||
|
||||
// Dial creates a new client connection. Use requestHeader to specify the
|
||||
// origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies (Cookie).
|
||||
// Use the response.Header to get the selected subprotocol
|
||||
// (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
|
||||
//
|
||||
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
|
||||
// non-nil *http.Response so that callers can handle redirects, authentication,
|
||||
// etcetera. The response body may not contain the entire response and does not
|
||||
// need to be closed by the application.
|
||||
func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) {
|
||||
|
||||
if d == nil {
|
||||
d = &Dialer{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
}
|
||||
}
|
||||
|
||||
challengeKey, err := generateChallengeKey()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
u, err := parseURL(urlStr)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
switch u.Scheme {
|
||||
case "ws":
|
||||
u.Scheme = "http"
|
||||
case "wss":
|
||||
u.Scheme = "https"
|
||||
default:
|
||||
return nil, nil, errMalformedURL
|
||||
}
|
||||
|
||||
if u.User != nil {
|
||||
// User name and password are not allowed in websocket URIs.
|
||||
return nil, nil, errMalformedURL
|
||||
}
|
||||
|
||||
req := &http.Request{
|
||||
Method: "GET",
|
||||
URL: u,
|
||||
Proto: "HTTP/1.1",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
Header: make(http.Header),
|
||||
Host: u.Host,
|
||||
}
|
||||
|
||||
// Set the cookies present in the cookie jar of the dialer
|
||||
if d.Jar != nil {
|
||||
for _, cookie := range d.Jar.Cookies(u) {
|
||||
req.AddCookie(cookie)
|
||||
}
|
||||
}
|
||||
|
||||
// Set the request headers using the capitalization for names and values in
|
||||
// RFC examples. Although the capitalization shouldn't matter, there are
|
||||
// servers that depend on it. The Header.Set method is not used because the
|
||||
// method canonicalizes the header names.
|
||||
req.Header["Upgrade"] = []string{"websocket"}
|
||||
req.Header["Connection"] = []string{"Upgrade"}
|
||||
req.Header["Sec-WebSocket-Key"] = []string{challengeKey}
|
||||
req.Header["Sec-WebSocket-Version"] = []string{"13"}
|
||||
if len(d.Subprotocols) > 0 {
|
||||
req.Header["Sec-WebSocket-Protocol"] = []string{strings.Join(d.Subprotocols, ", ")}
|
||||
}
|
||||
for k, vs := range requestHeader {
|
||||
switch {
|
||||
case k == "Host":
|
||||
if len(vs) > 0 {
|
||||
req.Host = vs[0]
|
||||
}
|
||||
case k == "Upgrade" ||
|
||||
k == "Connection" ||
|
||||
k == "Sec-Websocket-Key" ||
|
||||
k == "Sec-Websocket-Version" ||
|
||||
k == "Sec-Websocket-Extensions" ||
|
||||
(k == "Sec-Websocket-Protocol" && len(d.Subprotocols) > 0):
|
||||
return nil, nil, errors.New("websocket: duplicate header not allowed: " + k)
|
||||
default:
|
||||
req.Header[k] = vs
|
||||
}
|
||||
}
|
||||
|
||||
if d.EnableCompression {
|
||||
req.Header.Set("Sec-Websocket-Extensions", "permessage-deflate; server_no_context_takeover; client_no_context_takeover")
|
||||
}
|
||||
|
||||
hostPort, hostNoPort := hostPortNoPort(u)
|
||||
|
||||
var proxyURL *url.URL
|
||||
// Check wether the proxy method has been configured
|
||||
if d.Proxy != nil {
|
||||
proxyURL, err = d.Proxy(req)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var targetHostPort string
|
||||
if proxyURL != nil {
|
||||
targetHostPort, _ = hostPortNoPort(proxyURL)
|
||||
} else {
|
||||
targetHostPort = hostPort
|
||||
}
|
||||
|
||||
var deadline time.Time
|
||||
if d.HandshakeTimeout != 0 {
|
||||
deadline = time.Now().Add(d.HandshakeTimeout)
|
||||
}
|
||||
|
||||
netDial := d.NetDial
|
||||
if netDial == nil {
|
||||
netDialer := &net.Dialer{Deadline: deadline}
|
||||
netDial = netDialer.Dial
|
||||
}
|
||||
|
||||
netConn, err := netDial("tcp", targetHostPort)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if netConn != nil {
|
||||
netConn.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
if err := netConn.SetDeadline(deadline); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if proxyURL != nil {
|
||||
connectHeader := make(http.Header)
|
||||
if user := proxyURL.User; user != nil {
|
||||
proxyUser := user.Username()
|
||||
if proxyPassword, passwordSet := user.Password(); passwordSet {
|
||||
credential := base64.StdEncoding.EncodeToString([]byte(proxyUser + ":" + proxyPassword))
|
||||
connectHeader.Set("Proxy-Authorization", "Basic "+credential)
|
||||
}
|
||||
}
|
||||
connectReq := &http.Request{
|
||||
Method: "CONNECT",
|
||||
URL: &url.URL{Opaque: hostPort},
|
||||
Host: hostPort,
|
||||
Header: connectHeader,
|
||||
}
|
||||
|
||||
connectReq.Write(netConn)
|
||||
|
||||
// Read response.
|
||||
// Okay to use and discard buffered reader here, because
|
||||
// TLS server will not speak until spoken to.
|
||||
br := bufio.NewReader(netConn)
|
||||
resp, err := http.ReadResponse(br, connectReq)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
f := strings.SplitN(resp.Status, " ", 2)
|
||||
return nil, nil, errors.New(f[1])
|
||||
}
|
||||
}
|
||||
|
||||
if u.Scheme == "https" {
|
||||
cfg := cloneTLSConfig(d.TLSClientConfig)
|
||||
if cfg.ServerName == "" {
|
||||
cfg.ServerName = hostNoPort
|
||||
}
|
||||
tlsConn := tls.Client(netConn, cfg)
|
||||
netConn = tlsConn
|
||||
if err := tlsConn.Handshake(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if !cfg.InsecureSkipVerify {
|
||||
if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
conn := newConn(netConn, false, d.ReadBufferSize, d.WriteBufferSize)
|
||||
|
||||
if err := req.Write(netConn); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
resp, err := http.ReadResponse(conn.br, req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if d.Jar != nil {
|
||||
if rc := resp.Cookies(); len(rc) > 0 {
|
||||
d.Jar.SetCookies(u, rc)
|
||||
}
|
||||
}
|
||||
|
||||
if resp.StatusCode != 101 ||
|
||||
!strings.EqualFold(resp.Header.Get("Upgrade"), "websocket") ||
|
||||
!strings.EqualFold(resp.Header.Get("Connection"), "upgrade") ||
|
||||
resp.Header.Get("Sec-Websocket-Accept") != computeAcceptKey(challengeKey) {
|
||||
// Before closing the network connection on return from this
|
||||
// function, slurp up some of the response to aid application
|
||||
// debugging.
|
||||
buf := make([]byte, 1024)
|
||||
n, _ := io.ReadFull(resp.Body, buf)
|
||||
resp.Body = ioutil.NopCloser(bytes.NewReader(buf[:n]))
|
||||
return nil, resp, ErrBadHandshake
|
||||
}
|
||||
|
||||
for _, ext := range parseExtensions(resp.Header) {
|
||||
if ext[""] != "permessage-deflate" {
|
||||
continue
|
||||
}
|
||||
_, snct := ext["server_no_context_takeover"]
|
||||
_, cnct := ext["client_no_context_takeover"]
|
||||
if !snct || !cnct {
|
||||
return nil, resp, errInvalidCompression
|
||||
}
|
||||
conn.newCompressionWriter = compressNoContextTakeover
|
||||
conn.newDecompressionReader = decompressNoContextTakeover
|
||||
break
|
||||
}
|
||||
|
||||
resp.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
|
||||
conn.subprotocol = resp.Header.Get("Sec-Websocket-Protocol")
|
||||
|
||||
netConn.SetDeadline(time.Time{})
|
||||
netConn = nil // to avoid close in defer.
|
||||
return conn, resp, nil
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.8
|
||||
|
||||
package websocket
|
||||
|
||||
import "crypto/tls"
|
||||
|
||||
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
|
||||
if cfg == nil {
|
||||
return &tls.Config{}
|
||||
}
|
||||
return cfg.Clone()
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !go1.8
|
||||
|
||||
package websocket
|
||||
|
||||
import "crypto/tls"
|
||||
|
||||
// cloneTLSConfig clones all public fields except the fields
|
||||
// SessionTicketsDisabled and SessionTicketKey. This avoids copying the
|
||||
// sync.Mutex in the sync.Once and makes it safe to call cloneTLSConfig on a
|
||||
// config in active use.
|
||||
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
|
||||
if cfg == nil {
|
||||
return &tls.Config{}
|
||||
}
|
||||
return &tls.Config{
|
||||
Rand: cfg.Rand,
|
||||
Time: cfg.Time,
|
||||
Certificates: cfg.Certificates,
|
||||
NameToCertificate: cfg.NameToCertificate,
|
||||
GetCertificate: cfg.GetCertificate,
|
||||
RootCAs: cfg.RootCAs,
|
||||
NextProtos: cfg.NextProtos,
|
||||
ServerName: cfg.ServerName,
|
||||
ClientAuth: cfg.ClientAuth,
|
||||
ClientCAs: cfg.ClientCAs,
|
||||
InsecureSkipVerify: cfg.InsecureSkipVerify,
|
||||
CipherSuites: cfg.CipherSuites,
|
||||
PreferServerCipherSuites: cfg.PreferServerCipherSuites,
|
||||
ClientSessionCache: cfg.ClientSessionCache,
|
||||
MinVersion: cfg.MinVersion,
|
||||
MaxVersion: cfg.MaxVersion,
|
||||
CurvePreferences: cfg.CurvePreferences,
|
||||
}
|
||||
}
|
|
@ -1,148 +0,0 @@
|
|||
// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"compress/flate"
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
minCompressionLevel = -2 // flate.HuffmanOnly not defined in Go < 1.6
|
||||
maxCompressionLevel = flate.BestCompression
|
||||
defaultCompressionLevel = 1
|
||||
)
|
||||
|
||||
var (
|
||||
flateWriterPools [maxCompressionLevel - minCompressionLevel + 1]sync.Pool
|
||||
flateReaderPool = sync.Pool{New: func() interface{} {
|
||||
return flate.NewReader(nil)
|
||||
}}
|
||||
)
|
||||
|
||||
func decompressNoContextTakeover(r io.Reader) io.ReadCloser {
|
||||
const tail =
|
||||
// Add four bytes as specified in RFC
|
||||
"\x00\x00\xff\xff" +
|
||||
// Add final block to squelch unexpected EOF error from flate reader.
|
||||
"\x01\x00\x00\xff\xff"
|
||||
|
||||
fr, _ := flateReaderPool.Get().(io.ReadCloser)
|
||||
fr.(flate.Resetter).Reset(io.MultiReader(r, strings.NewReader(tail)), nil)
|
||||
return &flateReadWrapper{fr}
|
||||
}
|
||||
|
||||
func isValidCompressionLevel(level int) bool {
|
||||
return minCompressionLevel <= level && level <= maxCompressionLevel
|
||||
}
|
||||
|
||||
func compressNoContextTakeover(w io.WriteCloser, level int) io.WriteCloser {
|
||||
p := &flateWriterPools[level-minCompressionLevel]
|
||||
tw := &truncWriter{w: w}
|
||||
fw, _ := p.Get().(*flate.Writer)
|
||||
if fw == nil {
|
||||
fw, _ = flate.NewWriter(tw, level)
|
||||
} else {
|
||||
fw.Reset(tw)
|
||||
}
|
||||
return &flateWriteWrapper{fw: fw, tw: tw, p: p}
|
||||
}
|
||||
|
||||
// truncWriter is an io.Writer that writes all but the last four bytes of the
|
||||
// stream to another io.Writer.
|
||||
type truncWriter struct {
|
||||
w io.WriteCloser
|
||||
n int
|
||||
p [4]byte
|
||||
}
|
||||
|
||||
func (w *truncWriter) Write(p []byte) (int, error) {
|
||||
n := 0
|
||||
|
||||
// fill buffer first for simplicity.
|
||||
if w.n < len(w.p) {
|
||||
n = copy(w.p[w.n:], p)
|
||||
p = p[n:]
|
||||
w.n += n
|
||||
if len(p) == 0 {
|
||||
return n, nil
|
||||
}
|
||||
}
|
||||
|
||||
m := len(p)
|
||||
if m > len(w.p) {
|
||||
m = len(w.p)
|
||||
}
|
||||
|
||||
if nn, err := w.w.Write(w.p[:m]); err != nil {
|
||||
return n + nn, err
|
||||
}
|
||||
|
||||
copy(w.p[:], w.p[m:])
|
||||
copy(w.p[len(w.p)-m:], p[len(p)-m:])
|
||||
nn, err := w.w.Write(p[:len(p)-m])
|
||||
return n + nn, err
|
||||
}
|
||||
|
||||
type flateWriteWrapper struct {
|
||||
fw *flate.Writer
|
||||
tw *truncWriter
|
||||
p *sync.Pool
|
||||
}
|
||||
|
||||
func (w *flateWriteWrapper) Write(p []byte) (int, error) {
|
||||
if w.fw == nil {
|
||||
return 0, errWriteClosed
|
||||
}
|
||||
return w.fw.Write(p)
|
||||
}
|
||||
|
||||
func (w *flateWriteWrapper) Close() error {
|
||||
if w.fw == nil {
|
||||
return errWriteClosed
|
||||
}
|
||||
err1 := w.fw.Flush()
|
||||
w.p.Put(w.fw)
|
||||
w.fw = nil
|
||||
if w.tw.p != [4]byte{0, 0, 0xff, 0xff} {
|
||||
return errors.New("websocket: internal error, unexpected bytes at end of flate stream")
|
||||
}
|
||||
err2 := w.tw.w.Close()
|
||||
if err1 != nil {
|
||||
return err1
|
||||
}
|
||||
return err2
|
||||
}
|
||||
|
||||
type flateReadWrapper struct {
|
||||
fr io.ReadCloser
|
||||
}
|
||||
|
||||
func (r *flateReadWrapper) Read(p []byte) (int, error) {
|
||||
if r.fr == nil {
|
||||
return 0, io.ErrClosedPipe
|
||||
}
|
||||
n, err := r.fr.Read(p)
|
||||
if err == io.EOF {
|
||||
// Preemptively place the reader back in the pool. This helps with
|
||||
// scenarios where the application does not call NextReader() soon after
|
||||
// this final read.
|
||||
r.Close()
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (r *flateReadWrapper) Close() error {
|
||||
if r.fr == nil {
|
||||
return io.ErrClosedPipe
|
||||
}
|
||||
err := r.fr.Close()
|
||||
flateReaderPool.Put(r.fr)
|
||||
r.fr = nil
|
||||
return err
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,18 +0,0 @@
|
|||
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package websocket
|
||||
|
||||
import "io"
|
||||
|
||||
func (c *Conn) read(n int) ([]byte, error) {
|
||||
p, err := c.br.Peek(n)
|
||||
if err == io.EOF {
|
||||
err = errUnexpectedEOF
|
||||
}
|
||||
c.br.Discard(len(p))
|
||||
return p, err
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package websocket
|
||||
|
||||
import "io"
|
||||
|
||||
func (c *Conn) read(n int) ([]byte, error) {
|
||||
p, err := c.br.Peek(n)
|
||||
if err == io.EOF {
|
||||
err = errUnexpectedEOF
|
||||
}
|
||||
if len(p) > 0 {
|
||||
// advance over the bytes just read
|
||||
io.ReadFull(c.br, p)
|
||||
}
|
||||
return p, err
|
||||
}
|
|
@ -1,180 +0,0 @@
|
|||
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package websocket implements the WebSocket protocol defined in RFC 6455.
|
||||
//
|
||||
// Overview
|
||||
//
|
||||
// The Conn type represents a WebSocket connection. A server application uses
|
||||
// the Upgrade function from an Upgrader object with a HTTP request handler
|
||||
// to get a pointer to a Conn:
|
||||
//
|
||||
// var upgrader = websocket.Upgrader{
|
||||
// ReadBufferSize: 1024,
|
||||
// WriteBufferSize: 1024,
|
||||
// }
|
||||
//
|
||||
// func handler(w http.ResponseWriter, r *http.Request) {
|
||||
// conn, err := upgrader.Upgrade(w, r, nil)
|
||||
// if err != nil {
|
||||
// log.Println(err)
|
||||
// return
|
||||
// }
|
||||
// ... Use conn to send and receive messages.
|
||||
// }
|
||||
//
|
||||
// Call the connection's WriteMessage and ReadMessage methods to send and
|
||||
// receive messages as a slice of bytes. This snippet of code shows how to echo
|
||||
// messages using these methods:
|
||||
//
|
||||
// for {
|
||||
// messageType, p, err := conn.ReadMessage()
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// if err = conn.WriteMessage(messageType, p); err != nil {
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// In above snippet of code, p is a []byte and messageType is an int with value
|
||||
// websocket.BinaryMessage or websocket.TextMessage.
|
||||
//
|
||||
// An application can also send and receive messages using the io.WriteCloser
|
||||
// and io.Reader interfaces. To send a message, call the connection NextWriter
|
||||
// method to get an io.WriteCloser, write the message to the writer and close
|
||||
// the writer when done. To receive a message, call the connection NextReader
|
||||
// method to get an io.Reader and read until io.EOF is returned. This snippet
|
||||
// shows how to echo messages using the NextWriter and NextReader methods:
|
||||
//
|
||||
// for {
|
||||
// messageType, r, err := conn.NextReader()
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// w, err := conn.NextWriter(messageType)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// if _, err := io.Copy(w, r); err != nil {
|
||||
// return err
|
||||
// }
|
||||
// if err := w.Close(); err != nil {
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Data Messages
|
||||
//
|
||||
// The WebSocket protocol distinguishes between text and binary data messages.
|
||||
// Text messages are interpreted as UTF-8 encoded text. The interpretation of
|
||||
// binary messages is left to the application.
|
||||
//
|
||||
// This package uses the TextMessage and BinaryMessage integer constants to
|
||||
// identify the two data message types. The ReadMessage and NextReader methods
|
||||
// return the type of the received message. The messageType argument to the
|
||||
// WriteMessage and NextWriter methods specifies the type of a sent message.
|
||||
//
|
||||
// It is the application's responsibility to ensure that text messages are
|
||||
// valid UTF-8 encoded text.
|
||||
//
|
||||
// Control Messages
|
||||
//
|
||||
// The WebSocket protocol defines three types of control messages: close, ping
|
||||
// and pong. Call the connection WriteControl, WriteMessage or NextWriter
|
||||
// methods to send a control message to the peer.
|
||||
//
|
||||
// Connections handle received close messages by sending a close message to the
|
||||
// peer and returning a *CloseError from the the NextReader, ReadMessage or the
|
||||
// message Read method.
|
||||
//
|
||||
// Connections handle received ping and pong messages by invoking callback
|
||||
// functions set with SetPingHandler and SetPongHandler methods. The callback
|
||||
// functions are called from the NextReader, ReadMessage and the message Read
|
||||
// methods.
|
||||
//
|
||||
// The default ping handler sends a pong to the peer. The application's reading
|
||||
// goroutine can block for a short time while the handler writes the pong data
|
||||
// to the connection.
|
||||
//
|
||||
// The application must read the connection to process ping, pong and close
|
||||
// messages sent from the peer. If the application is not otherwise interested
|
||||
// in messages from the peer, then the application should start a goroutine to
|
||||
// read and discard messages from the peer. A simple example is:
|
||||
//
|
||||
// func readLoop(c *websocket.Conn) {
|
||||
// for {
|
||||
// if _, _, err := c.NextReader(); err != nil {
|
||||
// c.Close()
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Concurrency
|
||||
//
|
||||
// Connections support one concurrent reader and one concurrent writer.
|
||||
//
|
||||
// Applications are responsible for ensuring that no more than one goroutine
|
||||
// calls the write methods (NextWriter, SetWriteDeadline, WriteMessage,
|
||||
// WriteJSON, EnableWriteCompression, SetCompressionLevel) concurrently and
|
||||
// that no more than one goroutine calls the read methods (NextReader,
|
||||
// SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler, SetPingHandler)
|
||||
// concurrently.
|
||||
//
|
||||
// The Close and WriteControl methods can be called concurrently with all other
|
||||
// methods.
|
||||
//
|
||||
// Origin Considerations
|
||||
//
|
||||
// Web browsers allow Javascript applications to open a WebSocket connection to
|
||||
// any host. It's up to the server to enforce an origin policy using the Origin
|
||||
// request header sent by the browser.
|
||||
//
|
||||
// The Upgrader calls the function specified in the CheckOrigin field to check
|
||||
// the origin. If the CheckOrigin function returns false, then the Upgrade
|
||||
// method fails the WebSocket handshake with HTTP status 403.
|
||||
//
|
||||
// If the CheckOrigin field is nil, then the Upgrader uses a safe default: fail
|
||||
// the handshake if the Origin request header is present and not equal to the
|
||||
// Host request header.
|
||||
//
|
||||
// An application can allow connections from any origin by specifying a
|
||||
// function that always returns true:
|
||||
//
|
||||
// var upgrader = websocket.Upgrader{
|
||||
// CheckOrigin: func(r *http.Request) bool { return true },
|
||||
// }
|
||||
//
|
||||
// The deprecated Upgrade function does not enforce an origin policy. It's the
|
||||
// application's responsibility to check the Origin header before calling
|
||||
// Upgrade.
|
||||
//
|
||||
// Compression EXPERIMENTAL
|
||||
//
|
||||
// Per message compression extensions (RFC 7692) are experimentally supported
|
||||
// by this package in a limited capacity. Setting the EnableCompression option
|
||||
// to true in Dialer or Upgrader will attempt to negotiate per message deflate
|
||||
// support.
|
||||
//
|
||||
// var upgrader = websocket.Upgrader{
|
||||
// EnableCompression: true,
|
||||
// }
|
||||
//
|
||||
// If compression was successfully negotiated with the connection's peer, any
|
||||
// message received in compressed form will be automatically decompressed.
|
||||
// All Read methods will return uncompressed bytes.
|
||||
//
|
||||
// Per message compression of messages written to a connection can be enabled
|
||||
// or disabled by calling the corresponding Conn method:
|
||||
//
|
||||
// conn.EnableWriteCompression(false)
|
||||
//
|
||||
// Currently this package does not support compression with "context takeover".
|
||||
// This means that messages must be compressed and decompressed in isolation,
|
||||
// without retaining sliding window or dictionary state across messages. For
|
||||
// more details refer to RFC 7692.
|
||||
//
|
||||
// Use of compression is experimental and may result in decreased performance.
|
||||
package websocket
|
|
@ -1,55 +0,0 @@
|
|||
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
)
|
||||
|
||||
// WriteJSON is deprecated, use c.WriteJSON instead.
|
||||
func WriteJSON(c *Conn, v interface{}) error {
|
||||
return c.WriteJSON(v)
|
||||
}
|
||||
|
||||
// WriteJSON writes the JSON encoding of v to the connection.
|
||||
//
|
||||
// See the documentation for encoding/json Marshal for details about the
|
||||
// conversion of Go values to JSON.
|
||||
func (c *Conn) WriteJSON(v interface{}) error {
|
||||
w, err := c.NextWriter(TextMessage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err1 := json.NewEncoder(w).Encode(v)
|
||||
err2 := w.Close()
|
||||
if err1 != nil {
|
||||
return err1
|
||||
}
|
||||
return err2
|
||||
}
|
||||
|
||||
// ReadJSON is deprecated, use c.ReadJSON instead.
|
||||
func ReadJSON(c *Conn, v interface{}) error {
|
||||
return c.ReadJSON(v)
|
||||
}
|
||||
|
||||
// ReadJSON reads the next JSON-encoded message from the connection and stores
|
||||
// it in the value pointed to by v.
|
||||
//
|
||||
// See the documentation for the encoding/json Unmarshal function for details
|
||||
// about the conversion of JSON to a Go value.
|
||||
func (c *Conn) ReadJSON(v interface{}) error {
|
||||
_, r, err := c.NextReader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = json.NewDecoder(r).Decode(v)
|
||||
if err == io.EOF {
|
||||
// One value is expected in the message.
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
return err
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in the
|
||||
// LICENSE file.
|
||||
|
||||
// +build !appengine
|
||||
|
||||
package websocket
|
||||
|
||||
import "unsafe"
|
||||
|
||||
const wordSize = int(unsafe.Sizeof(uintptr(0)))
|
||||
|
||||
func maskBytes(key [4]byte, pos int, b []byte) int {
|
||||
|
||||
// Mask one byte at a time for small buffers.
|
||||
if len(b) < 2*wordSize {
|
||||
for i := range b {
|
||||
b[i] ^= key[pos&3]
|
||||
pos++
|
||||
}
|
||||
return pos & 3
|
||||
}
|
||||
|
||||
// Mask one byte at a time to word boundary.
|
||||
if n := int(uintptr(unsafe.Pointer(&b[0]))) % wordSize; n != 0 {
|
||||
n = wordSize - n
|
||||
for i := range b[:n] {
|
||||
b[i] ^= key[pos&3]
|
||||
pos++
|
||||
}
|
||||
b = b[n:]
|
||||
}
|
||||
|
||||
// Create aligned word size key.
|
||||
var k [wordSize]byte
|
||||
for i := range k {
|
||||
k[i] = key[(pos+i)&3]
|
||||
}
|
||||
kw := *(*uintptr)(unsafe.Pointer(&k))
|
||||
|
||||
// Mask one word at a time.
|
||||
n := (len(b) / wordSize) * wordSize
|
||||
for i := 0; i < n; i += wordSize {
|
||||
*(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(i))) ^= kw
|
||||
}
|
||||
|
||||
// Mask one byte at a time for remaining bytes.
|
||||
b = b[n:]
|
||||
for i := range b {
|
||||
b[i] ^= key[pos&3]
|
||||
pos++
|
||||
}
|
||||
|
||||
return pos & 3
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in the
|
||||
// LICENSE file.
|
||||
|
||||
// +build appengine
|
||||
|
||||
package websocket
|
||||
|
||||
func maskBytes(key [4]byte, pos int, b []byte) int {
|
||||
for i := range b {
|
||||
b[i] ^= key[pos&3]
|
||||
pos++
|
||||
}
|
||||
return pos & 3
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// PreparedMessage caches on the wire representations of a message payload.
|
||||
// Use PreparedMessage to efficiently send a message payload to multiple
|
||||
// connections. PreparedMessage is especially useful when compression is used
|
||||
// because the CPU and memory expensive compression operation can be executed
|
||||
// once for a given set of compression options.
|
||||
type PreparedMessage struct {
|
||||
messageType int
|
||||
data []byte
|
||||
err error
|
||||
mu sync.Mutex
|
||||
frames map[prepareKey]*preparedFrame
|
||||
}
|
||||
|
||||
// prepareKey defines a unique set of options to cache prepared frames in PreparedMessage.
|
||||
type prepareKey struct {
|
||||
isServer bool
|
||||
compress bool
|
||||
compressionLevel int
|
||||
}
|
||||
|
||||
// preparedFrame contains data in wire representation.
|
||||
type preparedFrame struct {
|
||||
once sync.Once
|
||||
data []byte
|
||||
}
|
||||
|
||||
// NewPreparedMessage returns an initialized PreparedMessage. You can then send
|
||||
// it to connection using WritePreparedMessage method. Valid wire
|
||||
// representation will be calculated lazily only once for a set of current
|
||||
// connection options.
|
||||
func NewPreparedMessage(messageType int, data []byte) (*PreparedMessage, error) {
|
||||
pm := &PreparedMessage{
|
||||
messageType: messageType,
|
||||
frames: make(map[prepareKey]*preparedFrame),
|
||||
data: data,
|
||||
}
|
||||
|
||||
// Prepare a plain server frame.
|
||||
_, frameData, err := pm.frame(prepareKey{isServer: true, compress: false})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// To protect against caller modifying the data argument, remember the data
|
||||
// copied to the plain server frame.
|
||||
pm.data = frameData[len(frameData)-len(data):]
|
||||
return pm, nil
|
||||
}
|
||||
|
||||
func (pm *PreparedMessage) frame(key prepareKey) (int, []byte, error) {
|
||||
pm.mu.Lock()
|
||||
frame, ok := pm.frames[key]
|
||||
if !ok {
|
||||
frame = &preparedFrame{}
|
||||
pm.frames[key] = frame
|
||||
}
|
||||
pm.mu.Unlock()
|
||||
|
||||
var err error
|
||||
frame.once.Do(func() {
|
||||
// Prepare a frame using a 'fake' connection.
|
||||
// TODO: Refactor code in conn.go to allow more direct construction of
|
||||
// the frame.
|
||||
mu := make(chan bool, 1)
|
||||
mu <- true
|
||||
var nc prepareConn
|
||||
c := &Conn{
|
||||
conn: &nc,
|
||||
mu: mu,
|
||||
isServer: key.isServer,
|
||||
compressionLevel: key.compressionLevel,
|
||||
enableWriteCompression: true,
|
||||
writeBuf: make([]byte, defaultWriteBufferSize+maxFrameHeaderSize),
|
||||
}
|
||||
if key.compress {
|
||||
c.newCompressionWriter = compressNoContextTakeover
|
||||
}
|
||||
err = c.WriteMessage(pm.messageType, pm.data)
|
||||
frame.data = nc.buf.Bytes()
|
||||
})
|
||||
return pm.messageType, frame.data, err
|
||||
}
|
||||
|
||||
type prepareConn struct {
|
||||
buf bytes.Buffer
|
||||
net.Conn
|
||||
}
|
||||
|
||||
func (pc *prepareConn) Write(p []byte) (int, error) { return pc.buf.Write(p) }
|
||||
func (pc *prepareConn) SetWriteDeadline(t time.Time) error { return nil }
|
|
@ -1,291 +0,0 @@
|
|||
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// HandshakeError describes an error with the handshake from the peer.
|
||||
type HandshakeError struct {
|
||||
message string
|
||||
}
|
||||
|
||||
func (e HandshakeError) Error() string { return e.message }
|
||||
|
||||
// Upgrader specifies parameters for upgrading an HTTP connection to a
|
||||
// WebSocket connection.
|
||||
type Upgrader struct {
|
||||
// HandshakeTimeout specifies the duration for the handshake to complete.
|
||||
HandshakeTimeout time.Duration
|
||||
|
||||
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer
|
||||
// size is zero, then buffers allocated by the HTTP server are used. The
|
||||
// I/O buffer sizes do not limit the size of the messages that can be sent
|
||||
// or received.
|
||||
ReadBufferSize, WriteBufferSize int
|
||||
|
||||
// Subprotocols specifies the server's supported protocols in order of
|
||||
// preference. If this field is set, then the Upgrade method negotiates a
|
||||
// subprotocol by selecting the first match in this list with a protocol
|
||||
// requested by the client.
|
||||
Subprotocols []string
|
||||
|
||||
// Error specifies the function for generating HTTP error responses. If Error
|
||||
// is nil, then http.Error is used to generate the HTTP response.
|
||||
Error func(w http.ResponseWriter, r *http.Request, status int, reason error)
|
||||
|
||||
// CheckOrigin returns true if the request Origin header is acceptable. If
|
||||
// CheckOrigin is nil, the host in the Origin header must not be set or
|
||||
// must match the host of the request.
|
||||
CheckOrigin func(r *http.Request) bool
|
||||
|
||||
// EnableCompression specify if the server should attempt to negotiate per
|
||||
// message compression (RFC 7692). Setting this value to true does not
|
||||
// guarantee that compression will be supported. Currently only "no context
|
||||
// takeover" modes are supported.
|
||||
EnableCompression bool
|
||||
}
|
||||
|
||||
func (u *Upgrader) returnError(w http.ResponseWriter, r *http.Request, status int, reason string) (*Conn, error) {
|
||||
err := HandshakeError{reason}
|
||||
if u.Error != nil {
|
||||
u.Error(w, r, status, err)
|
||||
} else {
|
||||
w.Header().Set("Sec-Websocket-Version", "13")
|
||||
http.Error(w, http.StatusText(status), status)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// checkSameOrigin returns true if the origin is not set or is equal to the request host.
|
||||
func checkSameOrigin(r *http.Request) bool {
|
||||
origin := r.Header["Origin"]
|
||||
if len(origin) == 0 {
|
||||
return true
|
||||
}
|
||||
u, err := url.Parse(origin[0])
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return u.Host == r.Host
|
||||
}
|
||||
|
||||
func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header) string {
|
||||
if u.Subprotocols != nil {
|
||||
clientProtocols := Subprotocols(r)
|
||||
for _, serverProtocol := range u.Subprotocols {
|
||||
for _, clientProtocol := range clientProtocols {
|
||||
if clientProtocol == serverProtocol {
|
||||
return clientProtocol
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if responseHeader != nil {
|
||||
return responseHeader.Get("Sec-Websocket-Protocol")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Upgrade upgrades the HTTP server connection to the WebSocket protocol.
|
||||
//
|
||||
// The responseHeader is included in the response to the client's upgrade
|
||||
// request. Use the responseHeader to specify cookies (Set-Cookie) and the
|
||||
// application negotiated subprotocol (Sec-Websocket-Protocol).
|
||||
//
|
||||
// If the upgrade fails, then Upgrade replies to the client with an HTTP error
|
||||
// response.
|
||||
func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error) {
|
||||
if r.Method != "GET" {
|
||||
return u.returnError(w, r, http.StatusMethodNotAllowed, "websocket: not a websocket handshake: request method is not GET")
|
||||
}
|
||||
|
||||
if _, ok := responseHeader["Sec-Websocket-Extensions"]; ok {
|
||||
return u.returnError(w, r, http.StatusInternalServerError, "websocket: application specific 'Sec-Websocket-Extensions' headers are unsupported")
|
||||
}
|
||||
|
||||
if !tokenListContainsValue(r.Header, "Connection", "upgrade") {
|
||||
return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'upgrade' token not found in 'Connection' header")
|
||||
}
|
||||
|
||||
if !tokenListContainsValue(r.Header, "Upgrade", "websocket") {
|
||||
return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'websocket' token not found in 'Upgrade' header")
|
||||
}
|
||||
|
||||
if !tokenListContainsValue(r.Header, "Sec-Websocket-Version", "13") {
|
||||
return u.returnError(w, r, http.StatusBadRequest, "websocket: unsupported version: 13 not found in 'Sec-Websocket-Version' header")
|
||||
}
|
||||
|
||||
checkOrigin := u.CheckOrigin
|
||||
if checkOrigin == nil {
|
||||
checkOrigin = checkSameOrigin
|
||||
}
|
||||
if !checkOrigin(r) {
|
||||
return u.returnError(w, r, http.StatusForbidden, "websocket: 'Origin' header value not allowed")
|
||||
}
|
||||
|
||||
challengeKey := r.Header.Get("Sec-Websocket-Key")
|
||||
if challengeKey == "" {
|
||||
return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: `Sec-Websocket-Key' header is missing or blank")
|
||||
}
|
||||
|
||||
subprotocol := u.selectSubprotocol(r, responseHeader)
|
||||
|
||||
// Negotiate PMCE
|
||||
var compress bool
|
||||
if u.EnableCompression {
|
||||
for _, ext := range parseExtensions(r.Header) {
|
||||
if ext[""] != "permessage-deflate" {
|
||||
continue
|
||||
}
|
||||
compress = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
netConn net.Conn
|
||||
err error
|
||||
)
|
||||
|
||||
h, ok := w.(http.Hijacker)
|
||||
if !ok {
|
||||
return u.returnError(w, r, http.StatusInternalServerError, "websocket: response does not implement http.Hijacker")
|
||||
}
|
||||
var brw *bufio.ReadWriter
|
||||
netConn, brw, err = h.Hijack()
|
||||
if err != nil {
|
||||
return u.returnError(w, r, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
if brw.Reader.Buffered() > 0 {
|
||||
netConn.Close()
|
||||
return nil, errors.New("websocket: client sent data before handshake is complete")
|
||||
}
|
||||
|
||||
c := newConnBRW(netConn, true, u.ReadBufferSize, u.WriteBufferSize, brw)
|
||||
c.subprotocol = subprotocol
|
||||
|
||||
if compress {
|
||||
c.newCompressionWriter = compressNoContextTakeover
|
||||
c.newDecompressionReader = decompressNoContextTakeover
|
||||
}
|
||||
|
||||
p := c.writeBuf[:0]
|
||||
p = append(p, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "...)
|
||||
p = append(p, computeAcceptKey(challengeKey)...)
|
||||
p = append(p, "\r\n"...)
|
||||
if c.subprotocol != "" {
|
||||
p = append(p, "Sec-Websocket-Protocol: "...)
|
||||
p = append(p, c.subprotocol...)
|
||||
p = append(p, "\r\n"...)
|
||||
}
|
||||
if compress {
|
||||
p = append(p, "Sec-Websocket-Extensions: permessage-deflate; server_no_context_takeover; client_no_context_takeover\r\n"...)
|
||||
}
|
||||
for k, vs := range responseHeader {
|
||||
if k == "Sec-Websocket-Protocol" {
|
||||
continue
|
||||
}
|
||||
for _, v := range vs {
|
||||
p = append(p, k...)
|
||||
p = append(p, ": "...)
|
||||
for i := 0; i < len(v); i++ {
|
||||
b := v[i]
|
||||
if b <= 31 {
|
||||
// prevent response splitting.
|
||||
b = ' '
|
||||
}
|
||||
p = append(p, b)
|
||||
}
|
||||
p = append(p, "\r\n"...)
|
||||
}
|
||||
}
|
||||
p = append(p, "\r\n"...)
|
||||
|
||||
// Clear deadlines set by HTTP server.
|
||||
netConn.SetDeadline(time.Time{})
|
||||
|
||||
if u.HandshakeTimeout > 0 {
|
||||
netConn.SetWriteDeadline(time.Now().Add(u.HandshakeTimeout))
|
||||
}
|
||||
if _, err = netConn.Write(p); err != nil {
|
||||
netConn.Close()
|
||||
return nil, err
|
||||
}
|
||||
if u.HandshakeTimeout > 0 {
|
||||
netConn.SetWriteDeadline(time.Time{})
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Upgrade upgrades the HTTP server connection to the WebSocket protocol.
|
||||
//
|
||||
// This function is deprecated, use websocket.Upgrader instead.
|
||||
//
|
||||
// The application is responsible for checking the request origin before
|
||||
// calling Upgrade. An example implementation of the same origin policy is:
|
||||
//
|
||||
// if req.Header.Get("Origin") != "http://"+req.Host {
|
||||
// http.Error(w, "Origin not allowed", 403)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// If the endpoint supports subprotocols, then the application is responsible
|
||||
// for negotiating the protocol used on the connection. Use the Subprotocols()
|
||||
// function to get the subprotocols requested by the client. Use the
|
||||
// Sec-Websocket-Protocol response header to specify the subprotocol selected
|
||||
// by the application.
|
||||
//
|
||||
// The responseHeader is included in the response to the client's upgrade
|
||||
// request. Use the responseHeader to specify cookies (Set-Cookie) and the
|
||||
// negotiated subprotocol (Sec-Websocket-Protocol).
|
||||
//
|
||||
// The connection buffers IO to the underlying network connection. The
|
||||
// readBufSize and writeBufSize parameters specify the size of the buffers to
|
||||
// use. Messages can be larger than the buffers.
|
||||
//
|
||||
// If the request is not a valid WebSocket handshake, then Upgrade returns an
|
||||
// error of type HandshakeError. Applications should handle this error by
|
||||
// replying to the client with an HTTP error response.
|
||||
func Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header, readBufSize, writeBufSize int) (*Conn, error) {
|
||||
u := Upgrader{ReadBufferSize: readBufSize, WriteBufferSize: writeBufSize}
|
||||
u.Error = func(w http.ResponseWriter, r *http.Request, status int, reason error) {
|
||||
// don't return errors to maintain backwards compatibility
|
||||
}
|
||||
u.CheckOrigin = func(r *http.Request) bool {
|
||||
// allow all connections by default
|
||||
return true
|
||||
}
|
||||
return u.Upgrade(w, r, responseHeader)
|
||||
}
|
||||
|
||||
// Subprotocols returns the subprotocols requested by the client in the
|
||||
// Sec-Websocket-Protocol header.
|
||||
func Subprotocols(r *http.Request) []string {
|
||||
h := strings.TrimSpace(r.Header.Get("Sec-Websocket-Protocol"))
|
||||
if h == "" {
|
||||
return nil
|
||||
}
|
||||
protocols := strings.Split(h, ",")
|
||||
for i := range protocols {
|
||||
protocols[i] = strings.TrimSpace(protocols[i])
|
||||
}
|
||||
return protocols
|
||||
}
|
||||
|
||||
// IsWebSocketUpgrade returns true if the client requested upgrade to the
|
||||
// WebSocket protocol.
|
||||
func IsWebSocketUpgrade(r *http.Request) bool {
|
||||
return tokenListContainsValue(r.Header, "Connection", "upgrade") &&
|
||||
tokenListContainsValue(r.Header, "Upgrade", "websocket")
|
||||
}
|
|
@ -1,214 +0,0 @@
|
|||
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
|
||||
|
||||
func computeAcceptKey(challengeKey string) string {
|
||||
h := sha1.New()
|
||||
h.Write([]byte(challengeKey))
|
||||
h.Write(keyGUID)
|
||||
return base64.StdEncoding.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
func generateChallengeKey() (string, error) {
|
||||
p := make([]byte, 16)
|
||||
if _, err := io.ReadFull(rand.Reader, p); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(p), nil
|
||||
}
|
||||
|
||||
// Octet types from RFC 2616.
|
||||
var octetTypes [256]byte
|
||||
|
||||
const (
|
||||
isTokenOctet = 1 << iota
|
||||
isSpaceOctet
|
||||
)
|
||||
|
||||
func init() {
|
||||
// From RFC 2616
|
||||
//
|
||||
// OCTET = <any 8-bit sequence of data>
|
||||
// CHAR = <any US-ASCII character (octets 0 - 127)>
|
||||
// CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
|
||||
// CR = <US-ASCII CR, carriage return (13)>
|
||||
// LF = <US-ASCII LF, linefeed (10)>
|
||||
// SP = <US-ASCII SP, space (32)>
|
||||
// HT = <US-ASCII HT, horizontal-tab (9)>
|
||||
// <"> = <US-ASCII double-quote mark (34)>
|
||||
// CRLF = CR LF
|
||||
// LWS = [CRLF] 1*( SP | HT )
|
||||
// TEXT = <any OCTET except CTLs, but including LWS>
|
||||
// separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <">
|
||||
// | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
|
||||
// token = 1*<any CHAR except CTLs or separators>
|
||||
// qdtext = <any TEXT except <">>
|
||||
|
||||
for c := 0; c < 256; c++ {
|
||||
var t byte
|
||||
isCtl := c <= 31 || c == 127
|
||||
isChar := 0 <= c && c <= 127
|
||||
isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0
|
||||
if strings.IndexRune(" \t\r\n", rune(c)) >= 0 {
|
||||
t |= isSpaceOctet
|
||||
}
|
||||
if isChar && !isCtl && !isSeparator {
|
||||
t |= isTokenOctet
|
||||
}
|
||||
octetTypes[c] = t
|
||||
}
|
||||
}
|
||||
|
||||
func skipSpace(s string) (rest string) {
|
||||
i := 0
|
||||
for ; i < len(s); i++ {
|
||||
if octetTypes[s[i]]&isSpaceOctet == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return s[i:]
|
||||
}
|
||||
|
||||
func nextToken(s string) (token, rest string) {
|
||||
i := 0
|
||||
for ; i < len(s); i++ {
|
||||
if octetTypes[s[i]]&isTokenOctet == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return s[:i], s[i:]
|
||||
}
|
||||
|
||||
func nextTokenOrQuoted(s string) (value string, rest string) {
|
||||
if !strings.HasPrefix(s, "\"") {
|
||||
return nextToken(s)
|
||||
}
|
||||
s = s[1:]
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch s[i] {
|
||||
case '"':
|
||||
return s[:i], s[i+1:]
|
||||
case '\\':
|
||||
p := make([]byte, len(s)-1)
|
||||
j := copy(p, s[:i])
|
||||
escape := true
|
||||
for i = i + 1; i < len(s); i++ {
|
||||
b := s[i]
|
||||
switch {
|
||||
case escape:
|
||||
escape = false
|
||||
p[j] = b
|
||||
j += 1
|
||||
case b == '\\':
|
||||
escape = true
|
||||
case b == '"':
|
||||
return string(p[:j]), s[i+1:]
|
||||
default:
|
||||
p[j] = b
|
||||
j += 1
|
||||
}
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
|
||||
// tokenListContainsValue returns true if the 1#token header with the given
|
||||
// name contains token.
|
||||
func tokenListContainsValue(header http.Header, name string, value string) bool {
|
||||
headers:
|
||||
for _, s := range header[name] {
|
||||
for {
|
||||
var t string
|
||||
t, s = nextToken(skipSpace(s))
|
||||
if t == "" {
|
||||
continue headers
|
||||
}
|
||||
s = skipSpace(s)
|
||||
if s != "" && s[0] != ',' {
|
||||
continue headers
|
||||
}
|
||||
if strings.EqualFold(t, value) {
|
||||
return true
|
||||
}
|
||||
if s == "" {
|
||||
continue headers
|
||||
}
|
||||
s = s[1:]
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// parseExtensiosn parses WebSocket extensions from a header.
|
||||
func parseExtensions(header http.Header) []map[string]string {
|
||||
|
||||
// From RFC 6455:
|
||||
//
|
||||
// Sec-WebSocket-Extensions = extension-list
|
||||
// extension-list = 1#extension
|
||||
// extension = extension-token *( ";" extension-param )
|
||||
// extension-token = registered-token
|
||||
// registered-token = token
|
||||
// extension-param = token [ "=" (token | quoted-string) ]
|
||||
// ;When using the quoted-string syntax variant, the value
|
||||
// ;after quoted-string unescaping MUST conform to the
|
||||
// ;'token' ABNF.
|
||||
|
||||
var result []map[string]string
|
||||
headers:
|
||||
for _, s := range header["Sec-Websocket-Extensions"] {
|
||||
for {
|
||||
var t string
|
||||
t, s = nextToken(skipSpace(s))
|
||||
if t == "" {
|
||||
continue headers
|
||||
}
|
||||
ext := map[string]string{"": t}
|
||||
for {
|
||||
s = skipSpace(s)
|
||||
if !strings.HasPrefix(s, ";") {
|
||||
break
|
||||
}
|
||||
var k string
|
||||
k, s = nextToken(skipSpace(s[1:]))
|
||||
if k == "" {
|
||||
continue headers
|
||||
}
|
||||
s = skipSpace(s)
|
||||
var v string
|
||||
if strings.HasPrefix(s, "=") {
|
||||
v, s = nextTokenOrQuoted(skipSpace(s[1:]))
|
||||
s = skipSpace(s)
|
||||
}
|
||||
if s != "" && s[0] != ',' && s[0] != ';' {
|
||||
continue headers
|
||||
}
|
||||
ext[k] = v
|
||||
}
|
||||
if s != "" && s[0] != ',' {
|
||||
continue headers
|
||||
}
|
||||
result = append(result, ext)
|
||||
if s == "" {
|
||||
continue headers
|
||||
}
|
||||
s = s[1:]
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
anonuuid
|
||||
|
||||
/dist
|
||||
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
|
@ -1 +0,0 @@
|
|||
github.com/moul/anonuuid
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"AppName": "anonuuid",
|
||||
"ArtifactsDest": "dist",
|
||||
"OutPath": "{{.Dest}}{{.PS}}{{.Version}}{{.PS}}{{.Os}}_{{.Arch}}{{.PS}}{{.ExeName}}{{.Ext}}",
|
||||
"TasksExclude": [
|
||||
"go-test",
|
||||
"go-vet"
|
||||
],
|
||||
"MainDirsExclude": "vendor,Godeps,testdata",
|
||||
"PackageVersion": "1.0",
|
||||
"ConfigVersion": "0.9"
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
language: go
|
||||
|
||||
|
||||
go:
|
||||
- 1.3
|
||||
- 1.4
|
||||
- 1.5
|
||||
- tip
|
||||
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: tip
|
||||
|
||||
|
||||
before_install:
|
||||
- go get -u github.com/axw/gocov/gocov
|
||||
- go get -u github.com/mattn/goveralls
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
|
||||
|
||||
#install:
|
||||
#- go get -d -v -t ./...
|
||||
|
||||
|
||||
script:
|
||||
- go build -v ./cmd/...
|
||||
- go test -v ./
|
||||
- go test -covermode=count -coverprofile=profile.out .
|
||||
- goveralls -covermode=count -service=travis-ci -v -coverprofile=profile.out
|
|
@ -1,22 +0,0 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Manfred Touron
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
# Project-specific variables
|
||||
BINARIES ?= anonuuid
|
||||
CONVEY_PORT ?= 9042
|
||||
|
||||
|
||||
# Common variables
|
||||
SOURCES := $(shell find . -name "*.go")
|
||||
COMMANDS := $(shell go list ./... | grep -v /vendor/ | grep /cmd/)
|
||||
PACKAGES := $(shell go list ./... | grep -v /vendor/ | grep -v /cmd/)
|
||||
GOENV ?= GO15VENDOREXPERIMENT=1
|
||||
GO ?= $(GOENV) go
|
||||
GODEP ?= $(GOENV) godep
|
||||
USER ?= $(shell whoami)
|
||||
|
||||
|
||||
all: build
|
||||
|
||||
|
||||
.PHONY: build
|
||||
build: $(BINARIES)
|
||||
|
||||
|
||||
$(BINARIES): $(SOURCES)
|
||||
$(GO) build -o $@ ./cmd/$@
|
||||
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
$(GO) get -t .
|
||||
$(GO) test -v .
|
||||
|
||||
|
||||
.PHONY: godep-save
|
||||
godep-save:
|
||||
$(GODEP) save $(PACKAGES) $(COMMANDS)
|
||||
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -f $(BINARIES)
|
||||
|
||||
|
||||
.PHONY: re
|
||||
re: clean all
|
||||
|
||||
|
||||
.PHONY: convey
|
||||
convey:
|
||||
$(GO) get github.com/smartystreets/goconvey
|
||||
goconvey -cover -port=$(CONVEY_PORT) -workDir="$(realpath .)" -depth=1
|
||||
|
||||
|
||||
.PHONY: cover
|
||||
cover: profile.out
|
||||
|
||||
|
||||
profile.out: $(SOURCES)
|
||||
rm -f $@
|
||||
$(GO) test -covermode=count -coverpkg=. -coverprofile=$@ .
|
||||
|
||||
|
||||
.PHONY: docker-build
|
||||
docker-build:
|
||||
go get github.com/laher/goxc
|
||||
rm -rf contrib/docker/linux_386
|
||||
for binary in $(BINARIES); do \
|
||||
goxc -bc="linux,386" -d . -pv contrib/docker -n $$binary xc; \
|
||||
mv contrib/docker/linux_386/$$binary contrib/docker/entrypoint; \
|
||||
docker build -t $(USER)/$$binary contrib/docker; \
|
||||
docker run -it --rm $(USER)/$$binary || true; \
|
||||
docker inspect --type=image --format="{{ .Id }}" moul/$$binary || true; \
|
||||
echo "Now you can run 'docker push $(USER)/$$binary'"; \
|
||||
done
|
|
@ -1,170 +0,0 @@
|
|||
# AnonUUID
|
||||
|
||||
[![Build Status](https://travis-ci.org/moul/anonuuid.svg)](https://travis-ci.org/moul/anonuuid)
|
||||
[![GoDoc](https://godoc.org/github.com/moul/anonuuid?status.svg)](https://godoc.org/github.com/moul/anonuuid)
|
||||
[![Coverage Status](https://coveralls.io/repos/moul/anonuuid/badge.svg?branch=master&service=github)](https://coveralls.io/github/moul/anonuuid?branch=master)
|
||||
|
||||
:wrench: Anonymize UUIDs outputs (written in Golang)
|
||||
|
||||
![AnonUUID Logo](https://raw.githubusercontent.com/moul/anonuuid/master/assets/anonuuid.png)
|
||||
|
||||
**anonuuid** anonymize an input string by replacing all UUIDs by an anonymized
|
||||
new one.
|
||||
|
||||
The fake UUIDs are cached, so if AnonUUID encounter the same real UUIDs multiple
|
||||
times, the translation will be the same.
|
||||
|
||||
## Usage
|
||||
|
||||
```console
|
||||
$ anonuuid --help
|
||||
NAME:
|
||||
anonuuid - Anonymize UUIDs outputs
|
||||
|
||||
USAGE:
|
||||
anonuuid [global options] command [command options] [arguments...]
|
||||
|
||||
VERSION:
|
||||
1.0.0-dev
|
||||
|
||||
AUTHOR(S):
|
||||
Manfred Touron <https://github.com/moul>
|
||||
|
||||
COMMANDS:
|
||||
help, h Shows a list of commands or help for one command
|
||||
|
||||
GLOBAL OPTIONS:
|
||||
--hexspeak Generate hexspeak style fake UUIDs
|
||||
--random, -r Generate random fake UUIDs
|
||||
--keep-beginning Keep first part of the UUID unchanged
|
||||
--keep-end Keep last part of the UUID unchanged
|
||||
--prefix, -p Prefix generated UUIDs
|
||||
--suffix Suffix generated UUIDs
|
||||
--help, -h show help
|
||||
--version, -v print the version
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
Replace all UUIDs and cache the correspondance.
|
||||
|
||||
```command
|
||||
$ anonuuid git:(master) ✗ cat <<EOF | anonuuid
|
||||
VOLUMES_0_SERVER_ID=15573749-c89d-41dd-a655-16e79bed52e0
|
||||
VOLUMES_0_SERVER_NAME=hello
|
||||
VOLUMES_0_ID=c245c3cb-3336-4567-ada1-70cb1fe4eefe
|
||||
VOLUMES_0_SIZE=50000000000
|
||||
ORGANIZATION=fe1e54e8-d69d-4f7c-a9f1-42069e03da31
|
||||
TEST=15573749-c89d-41dd-a655-16e79bed52e0
|
||||
EOF
|
||||
VOLUMES_0_SERVER_ID=00000000-0000-0000-0000-000000000000
|
||||
VOLUMES_0_SERVER_NAME=hello
|
||||
VOLUMES_0_ID=11111111-1111-1111-1111-111111111111
|
||||
VOLUMES_0_SIZE=50000000000
|
||||
ORGANIZATION=22222222-2222-2222-2222-222222222222
|
||||
TEST=00000000-0000-0000-0000-000000000000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Inline
|
||||
|
||||
```command
|
||||
$ echo 'VOLUMES_0_SERVER_ID=15573749-c89d-41dd-a655-16e79bed52e0 VOLUMES_0_SERVER_NAME=bitrig1 VOLUMES_0_ID=c245c3cb-3336-4567-ada1-70cb1fe4eefe VOLUMES_0_SIZE=50000000000 ORGANIZATION=fe1e54e8-d69d-4f7c-a9f1-42069e03da31 TEST=15573749-c89d-41dd-a655-16e79bed52e0' | ./anonuuid
|
||||
VOLUMES_0_SERVER_ID=00000000-0000-0000-0000-000000000000 VOLUMES_0_SERVER_NAME=bitrig1 VOLUMES_0_ID=11111111-1111-1111-1111-111111111111 VOLUMES_0_SIZE=50000000000 ORGANIZATION=22222222-2222-2222-2222-222222222222 TEST=00000000-0000-0000-0000-000000000000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
```command
|
||||
$ curl -s https://api.pathwar.net/achievements\?max_results\=2 | anonuuid | jq .
|
||||
{
|
||||
"_items": [
|
||||
{
|
||||
"_updated": "Thu, 30 Apr 2015 13:00:58 GMT",
|
||||
"description": "You",
|
||||
"_links": {
|
||||
"self": {
|
||||
"href": "achievements/00000000-0000-0000-0000-000000000000",
|
||||
"title": "achievement"
|
||||
}
|
||||
},
|
||||
"_created": "Thu, 30 Apr 2015 13:00:58 GMT",
|
||||
"_id": "00000000-0000-0000-0000-000000000000",
|
||||
"_etag": "b1e9f850accfcb952c58384db41d89728890a69f",
|
||||
"name": "finish-20-levels"
|
||||
},
|
||||
{
|
||||
"_updated": "Thu, 30 Apr 2015 13:01:07 GMT",
|
||||
"description": "You",
|
||||
"_links": {
|
||||
"self": {
|
||||
"href": "achievements/11111111-1111-1111-1111-111111111111",
|
||||
"title": "achievement"
|
||||
}
|
||||
},
|
||||
"_created": "Thu, 30 Apr 2015 13:01:07 GMT",
|
||||
"_id": "11111111-1111-1111-1111-111111111111",
|
||||
"_etag": "c346f5e1c4f7658f2dfc4124efa87aba909a9821",
|
||||
"name": "buy-30-levels"
|
||||
}
|
||||
],
|
||||
"_links": {
|
||||
"self": {
|
||||
"href": "achievements?max_results=2",
|
||||
"title": "achievements"
|
||||
},
|
||||
"last": {
|
||||
"href": "achievements?max_results=2&page=23",
|
||||
"title": "last page"
|
||||
},
|
||||
"parent": {
|
||||
"href": "/",
|
||||
"title": "home"
|
||||
},
|
||||
"next": {
|
||||
"href": "achievements?max_results=2&page=2",
|
||||
"title": "next page"
|
||||
}
|
||||
},
|
||||
"_meta": {
|
||||
"max_results": 2,
|
||||
"total": 46,
|
||||
"page": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Install
|
||||
|
||||
Using go
|
||||
|
||||
- `go get github.com/moul/anonuuid/...`
|
||||
|
||||
## Changelog
|
||||
|
||||
### master (unreleased)
|
||||
|
||||
* Add mutex to protect the cache field ([@QuentinPerez](https://github.com/QuentinPerez))
|
||||
* Switch from `Party` to `Godep`
|
||||
* Support of `--suffix=xxx`, `--keep-beginning` and `--keep-end` options ([#4](https://github.com/moul/anonuuid/issues/4))
|
||||
* Using **party** to stabilize vendor package versions ([#8](https://github.com/moul/anonuuid/issues/8))
|
||||
* Add homebrew package ([#6](https://github.com/moul/anonuuid/issues/6))
|
||||
|
||||
[full commits list](https://github.com/moul/anonuuid/compare/v1.0.0...master)
|
||||
|
||||
### [v1.0.0](https://github.com/moul/anonuuid/releases/tag/v1.0.0) (2015-10-07)
|
||||
|
||||
**Initial release**
|
||||
|
||||
#### Features
|
||||
|
||||
* Support of `--hexspeak` option
|
||||
* Support of `--random` option
|
||||
* Support of `--prefix` option
|
||||
* Anonymize input stream
|
||||
* Anonymize files
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
|
@ -1,229 +0,0 @@
|
|||
package anonuuid
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// UUIDRegex is the regex used to find UUIDs in texts
|
||||
UUIDRegex = "[a-z0-9]{8}-[a-z0-9]{4}-[1-5][a-z0-9]{3}-[a-z0-9]{4}-[a-z0-9]{12}"
|
||||
)
|
||||
|
||||
// AnonUUID is the main structure, it contains the cache map and helpers
|
||||
type AnonUUID struct {
|
||||
cache map[string]string
|
||||
|
||||
guard sync.Mutex // cache guard
|
||||
|
||||
// Hexspeak flag will generate hexspeak style fake UUIDs
|
||||
Hexspeak bool
|
||||
|
||||
// Random flag will generate random fake UUIDs
|
||||
Random bool
|
||||
|
||||
// Prefix will be the beginning of all the generated UUIDs
|
||||
Prefix string
|
||||
|
||||
// Suffix will be the end of all the generated UUIDs
|
||||
Suffix string
|
||||
|
||||
// AllowNonUUIDInput tells FakeUUID to accept non UUID input string
|
||||
AllowNonUUIDInput bool
|
||||
|
||||
// KeepBeginning tells FakeUUID to let the beginning of the UUID as it is
|
||||
KeepBeginning bool
|
||||
|
||||
// KeepEnd tells FakeUUID to let the last part of the UUID as it is
|
||||
KeepEnd bool
|
||||
}
|
||||
|
||||
// Sanitize takes a string as input and return sanitized string
|
||||
func (a *AnonUUID) Sanitize(input string) string {
|
||||
r := regexp.MustCompile(UUIDRegex)
|
||||
|
||||
return r.ReplaceAllStringFunc(input, func(m string) string {
|
||||
parts := r.FindStringSubmatch(m)
|
||||
return a.FakeUUID(parts[0])
|
||||
})
|
||||
}
|
||||
|
||||
// FakeUUID takes a word (real UUID or standard string) and returns its corresponding (mapped) fakeUUID
|
||||
func (a *AnonUUID) FakeUUID(input string) string {
|
||||
if !a.AllowNonUUIDInput {
|
||||
err := IsUUID(input)
|
||||
if err != nil {
|
||||
return "invaliduuid"
|
||||
}
|
||||
}
|
||||
a.guard.Lock()
|
||||
defer a.guard.Unlock()
|
||||
if _, ok := a.cache[input]; !ok {
|
||||
|
||||
if a.KeepBeginning {
|
||||
a.Prefix = input[:8]
|
||||
}
|
||||
|
||||
if a.KeepEnd {
|
||||
a.Suffix = input[36-12:]
|
||||
}
|
||||
|
||||
if a.Prefix != "" {
|
||||
matched, err := regexp.MatchString("^[a-z0-9]+$", a.Prefix)
|
||||
if err != nil || !matched {
|
||||
a.Prefix = "invalidprefix"
|
||||
}
|
||||
}
|
||||
|
||||
if a.Suffix != "" {
|
||||
matched, err := regexp.MatchString("^[a-z0-9]+$", a.Suffix)
|
||||
if err != nil || !matched {
|
||||
a.Suffix = "invalsuffix"
|
||||
}
|
||||
}
|
||||
|
||||
var fakeUUID string
|
||||
var err error
|
||||
if a.Hexspeak {
|
||||
fakeUUID, err = GenerateHexspeakUUID(len(a.cache))
|
||||
} else if a.Random {
|
||||
fakeUUID, err = GenerateRandomUUID(10)
|
||||
} else {
|
||||
fakeUUID, err = GenerateLenUUID(len(a.cache))
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to generate an UUID: %v", err)
|
||||
}
|
||||
|
||||
if a.Prefix != "" {
|
||||
fakeUUID, err = PrefixUUID(a.Prefix, fakeUUID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
if a.Suffix != "" {
|
||||
fakeUUID, err = SuffixUUID(a.Suffix, fakeUUID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: check for duplicates and retry
|
||||
|
||||
a.cache[input] = fakeUUID
|
||||
}
|
||||
return a.cache[input]
|
||||
}
|
||||
|
||||
// New returns a prepared AnonUUID structure
|
||||
func New() *AnonUUID {
|
||||
return &AnonUUID{
|
||||
cache: make(map[string]string),
|
||||
Hexspeak: false,
|
||||
Random: false,
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
}
|
||||
|
||||
// PrefixUUID returns a prefixed UUID
|
||||
func PrefixUUID(prefix string, uuid string) (string, error) {
|
||||
uuidLetters := uuid[:8] + uuid[9:13] + uuid[14:18] + uuid[19:23] + uuid[24:36]
|
||||
prefixedUUID, err := FormatUUID(prefix + uuidLetters)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return prefixedUUID, nil
|
||||
}
|
||||
|
||||
// SuffixUUID returns a suffixed UUID
|
||||
func SuffixUUID(suffix string, uuid string) (string, error) {
|
||||
uuidLetters := uuid[:8] + uuid[9:13] + uuid[14:18] + uuid[19:23] + uuid[24:36]
|
||||
uuidLetters = uuidLetters[:32-len(suffix)] + suffix
|
||||
suffixedUUID, err := FormatUUID(uuidLetters)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return suffixedUUID, nil
|
||||
}
|
||||
|
||||
// IsUUID returns nil if the input is an UUID, else it returns an error
|
||||
func IsUUID(input string) error {
|
||||
matched, err := regexp.MatchString("^"+UUIDRegex+"$", input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !matched {
|
||||
return fmt.Errorf("String '%s' is not a valid UUID", input)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FormatUUID takes a string in input and return an UUID formatted string by repeating the string and placing dashes if necessary
|
||||
func FormatUUID(part string) (string, error) {
|
||||
if len(part) < 1 {
|
||||
return "", fmt.Errorf("Empty UUID")
|
||||
}
|
||||
if len(part) < 32 {
|
||||
part = strings.Repeat(part, 32)
|
||||
}
|
||||
if len(part) > 32 {
|
||||
part = part[:32]
|
||||
}
|
||||
uuid := part[:8] + "-" + part[8:12] + "-1" + part[13:16] + "-" + part[16:20] + "-" + part[20:32]
|
||||
|
||||
err := IsUUID(uuid)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
// GenerateRandomUUID returns an UUID based on random strings
|
||||
func GenerateRandomUUID(length int) (string, error) {
|
||||
var letters = []rune("abcdef0123456789")
|
||||
|
||||
b := make([]rune, length)
|
||||
for i := range b {
|
||||
b[i] = letters[rand.Intn(len(letters))]
|
||||
}
|
||||
return FormatUUID(string(b))
|
||||
}
|
||||
|
||||
// GenerateHexspeakUUID returns an UUID formatted string containing hexspeak words
|
||||
func GenerateHexspeakUUID(i int) (string, error) {
|
||||
if i < 0 {
|
||||
i = -i
|
||||
}
|
||||
hexspeaks := []string{
|
||||
"0ff1ce",
|
||||
"31337",
|
||||
"4b1d",
|
||||
"badc0de",
|
||||
"badcafe",
|
||||
"badf00d",
|
||||
"deadbabe",
|
||||
"deadbeef",
|
||||
"deadc0de",
|
||||
"deadfeed",
|
||||
"fee1bad",
|
||||
}
|
||||
return FormatUUID(hexspeaks[i%len(hexspeaks)])
|
||||
}
|
||||
|
||||
// GenerateLenUUID returns an UUID formatted string based on an index number
|
||||
func GenerateLenUUID(i int) (string, error) {
|
||||
if i < 0 {
|
||||
i = 2<<29 + i
|
||||
}
|
||||
return FormatUUID(fmt.Sprintf("%x", i))
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
/gotty-client
|
||||
/dist
|
||||
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
|
@ -1,13 +0,0 @@
|
|||
{
|
||||
"AppName": "gotty-client",
|
||||
"ArtifactsDest": "dist",
|
||||
"BuildConstraints": "!plan9,!nacl,!solaris",
|
||||
"OutPath": "{{.Dest}}{{.PS}}{{.Version}}{{.PS}}{{.ExeName}}_{{.Version}}_{{.Os}}_{{.Arch}}{{.Ext}}",
|
||||
"TasksExclude": [
|
||||
"go-test",
|
||||
"go-vet"
|
||||
],
|
||||
"MainDirsExclude": "vendor,Godeps,testdata",
|
||||
"PackageVersion": "1.6.1+dev",
|
||||
"ConfigVersion": "0.9"
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.7
|
||||
- 1.8
|
||||
- tip
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: tip
|
||||
|
||||
script:
|
||||
- go build ./cmd/...
|
||||
- go run ./cmd/gotty-client/main.go --help || true
|
|
@ -1,11 +0,0 @@
|
|||
# build
|
||||
FROM golang:1.9 as builder
|
||||
RUN apt update && apt -y install jq
|
||||
COPY . /go/src/github.com/moul/gotty-client
|
||||
WORKDIR /go/src/github.com/moul/gotty-client
|
||||
RUN make install
|
||||
|
||||
# minimal runtime
|
||||
FROM scratch
|
||||
COPY --from=builder /go/bin/gotty-client /bin/gotty-client
|
||||
ENTRYPOINT ["/bin/gotty-client"]
|
|
@ -1,22 +0,0 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Manfred Touron
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -1,192 +0,0 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
https://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2013-2017 Docker, Inc.
|
||||
|
||||
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
|
||||
|
||||
https://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.
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
# Project-specific variables
|
||||
BINARIES ?= gotty-client
|
||||
GOTTY_URL := http://localhost:8081/
|
||||
VERSION := $(shell cat .goxc.json | jq -c .PackageVersion | sed 's/"//g')
|
||||
|
||||
CONVEY_PORT ?= 9042
|
||||
|
||||
|
||||
# Common variables
|
||||
SOURCES := $(shell find . -type f -name "*.go")
|
||||
COMMANDS := $(shell go list ./... | grep -v /vendor/ | grep /cmd/)
|
||||
PACKAGES := $(shell go list ./... | grep -v /vendor/ | grep -v /cmd/)
|
||||
GOENV ?= GO15VENDOREXPERIMENT=1
|
||||
GO ?= $(GOENV) go
|
||||
GODEP ?= $(GOENV) godep
|
||||
USER ?= $(shell whoami)
|
||||
|
||||
|
||||
all: build
|
||||
|
||||
|
||||
.PHONY: build
|
||||
build: $(BINARIES)
|
||||
|
||||
|
||||
.PHONY: install
|
||||
install:
|
||||
$(GO) install ./cmd/gotty-client
|
||||
|
||||
|
||||
$(BINARIES): $(SOURCES)
|
||||
$(GO) build -i -o $@ ./cmd/$@
|
||||
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
# $(GO) get -t .
|
||||
$(GO) test -i -v .
|
||||
$(GO) test -v .
|
||||
|
||||
|
||||
.PHONY: godep-save
|
||||
godep-save:
|
||||
$(GODEP) save $(PACKAGES) $(COMMANDS)
|
||||
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -f $(BINARIES)
|
||||
|
||||
|
||||
.PHONY: re
|
||||
re: clean all
|
||||
|
||||
|
||||
.PHONY: convey
|
||||
convey:
|
||||
$(GO) get github.com/smartystreets/goconvey
|
||||
goconvey -cover -port=$(CONVEY_PORT) -workDir="$(realpath .)" -depth=1
|
||||
|
||||
|
||||
.PHONY: cover
|
||||
cover: profile.out
|
||||
|
||||
|
||||
profile.out: $(SOURCES)
|
||||
rm -f $@
|
||||
$(GO) test -covermode=count -coverpkg=. -coverprofile=$@ .
|
||||
|
||||
|
||||
.PHONY: docker-build
|
||||
docker-build:
|
||||
docker build -t moul/gotty-client .
|
|
@ -1,205 +0,0 @@
|
|||
# gotty-client
|
||||
:wrench: Terminal client for [GoTTY](https://github.com/yudai/gotty).
|
||||
|
||||
![](https://raw.githubusercontent.com/moul/gotty-client/master/resources/gotty-client.png)
|
||||
|
||||
[![Build Status](https://travis-ci.org/moul/gotty-client.svg?branch=master)](https://travis-ci.org/moul/gotty-client)
|
||||
[![GoDoc](https://godoc.org/github.com/moul/gotty-client?status.svg)](https://godoc.org/github.com/moul/gotty-client)
|
||||
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fmoul%2Fgotty-client.svg?type=shield)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fmoul%2Fgotty-client?ref=badge_shield)
|
||||
|
||||
```ruby
|
||||
┌─────────────────┐
|
||||
┌──────▶│ /bin/bash │
|
||||
│ └─────────────────┘
|
||||
┌──────────────┐ ┌──────────┐
|
||||
│ │ │ Gotty │
|
||||
┌───────┐ ┌──▶│ Browser │───────┐ │ │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ │ │ └──────────────┘ │ │ │ ┌─────────────────┐
|
||||
│ Bob │───┤ websockets─▶│ │─▶│ emacs /var/www │
|
||||
│ │ │ ╔═ ══ ══ ══ ══ ╗ │ │ │ └─────────────────┘
|
||||
│ │ │ ║ ║ │ │ │
|
||||
└───────┘ └──▶║ gotty-client ───────┘ │ │
|
||||
║ │ │
|
||||
╚═ ══ ══ ══ ══ ╝ └──────────┘
|
||||
│ ┌─────────────────┐
|
||||
└──────▶│ tmux attach │
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
Server side ([GoTTY](https://github.com/yudai/gotty))
|
||||
|
||||
```console
|
||||
$ gotty -p 9191 sh -c 'while true; do date; sleep 1; done'
|
||||
2015/08/24 18:54:31 Server is starting with command: sh -c while true; do date; sleep 1; done
|
||||
2015/08/24 18:54:31 URL: http://[::1]:9191/
|
||||
2015/08/24 18:54:34 GET /ws
|
||||
2015/08/24 18:54:34 New client connected: 127.0.0.1:61811
|
||||
2015/08/24 18:54:34 Command is running for client 127.0.0.1:61811 with PID 64834
|
||||
2015/08/24 18:54:39 Command exited for: 127.0.0.1:61811
|
||||
2015/08/24 18:54:39 Connection closed: 127.0.0.1:61811
|
||||
...
|
||||
```
|
||||
|
||||
**Client side**
|
||||
|
||||
```console
|
||||
$ gotty-client http://localhost:9191/
|
||||
INFO[0000] New title: GoTTY - sh -c while true; do date; sleep 1; done (jean-michel-van-damme.local)
|
||||
WARN[0000] Unhandled protocol message: json pref: 2{}
|
||||
Mon Aug 24 18:54:34 CEST 2015
|
||||
Mon Aug 24 18:54:35 CEST 2015
|
||||
Mon Aug 24 18:54:36 CEST 2015
|
||||
Mon Aug 24 18:54:37 CEST 2015
|
||||
Mon Aug 24 18:54:38 CEST 2015
|
||||
^C
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```console
|
||||
$ gotty-client -h
|
||||
NAME:
|
||||
gotty-client - GoTTY client for your terminal
|
||||
|
||||
USAGE:
|
||||
gotty-client [global options] command [command options] GOTTY_URL
|
||||
|
||||
VERSION:
|
||||
1.3.0+
|
||||
|
||||
AUTHOR(S):
|
||||
Manfred Touron <https://github.com/moul/gotty-client>
|
||||
|
||||
COMMANDS:
|
||||
help, h Shows a list of commands or help for one command
|
||||
|
||||
GLOBAL OPTIONS:
|
||||
--debug, -D Enable debug mode [$GOTTY_CLIENT_DEBUG]
|
||||
--help, -h show help
|
||||
--version, -v print the version
|
||||
```
|
||||
|
||||
## Install
|
||||
|
||||
Install latest version using Golang (recommended)
|
||||
|
||||
```console
|
||||
$ go get github.com/moul/gotty-client/cmd/gotty-client
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Install latest version using Homebrew (Mac OS X)
|
||||
|
||||
```console
|
||||
$ brew install https://raw.githubusercontent.com/moul/gotty-client/master/contrib/homebrew/gotty-client.rb --HEAD
|
||||
```
|
||||
|
||||
or the latest released version
|
||||
|
||||
```console
|
||||
$ brew install https://raw.githubusercontent.com/moul/gotty-client/master/contrib/homebrew/gotty-client.rb
|
||||
```
|
||||
|
||||
## Changelog
|
||||
|
||||
### master (unreleased)
|
||||
|
||||
* Add `--detach-keys` option ([#52](https://github.com/moul/gotty-client/issues/52))
|
||||
|
||||
[full commits list](https://github.com/moul/gotty-client/compare/v1.6.1...master)
|
||||
|
||||
### [v1.6.1](https://github.com/moul/gotty-client/releases/tag/v1.6.1) (2017-01-19)
|
||||
|
||||
* Do not exit on EOF ([#45](https://github.com/moul/gotty-client/pull/45)) ([@gurjeet](https://github.com/gurjeet))
|
||||
|
||||
[full commits list](https://github.com/moul/gotty-client/compare/v1.6.0...v1.6.1)
|
||||
|
||||
### [v1.6.0](https://github.com/moul/gotty-client/releases/tag/v1.6.0) (2016-05-23)
|
||||
|
||||
* Support of `--use-proxy-from-env` (Add Proxy support) ([#36](https://github.com/moul/gotty-client/pull/36)) ([@byung2](https://github.com/byung2))
|
||||
* Add debug mode ([#18](https://github.com/moul/gotty-client/issues/18))
|
||||
* Fix argument passing ([#16](https://github.com/moul/gotty-client/issues/16))
|
||||
* Remove warnings + golang fixes and refactoring ([@QuentinPerez](https://github.com/QuentinPerez))
|
||||
|
||||
[full commits list](https://github.com/moul/gotty-client/compare/v1.5.0...v1.6.0)
|
||||
|
||||
### [v1.5.0](https://github.com/moul/gotty-client/releases/tag/v1.5.0) (2016-02-18)
|
||||
|
||||
* Add autocomplete support ([#19](https://github.com/moul/gotty-client/issues/19))
|
||||
* Switch from `Party` to `Godep`
|
||||
* Fix terminal data being interpreted as format string ([#34](https://github.com/moul/gotty-client/pull/34)) ([@mickael9](https://github.com/mickael9))
|
||||
|
||||
[full commits list](https://github.com/moul/gotty-client/compare/v1.4.0...v1.5.0)
|
||||
|
||||
### [v1.4.0](https://github.com/moul/gotty-client/releases/tag/v1.4.0) (2015-12-09)
|
||||
|
||||
* Remove solaris,plan9,nacl for `.goxc.json`
|
||||
* Add an error if the go version is lower than 1.5
|
||||
* Flexible parsing of the input URL
|
||||
* Add tests
|
||||
* Support of `--skip-tls-verify`
|
||||
|
||||
[full commits list](https://github.com/moul/gotty-client/compare/v1.3.0...v1.4.0)
|
||||
|
||||
### [v1.3.0](https://github.com/moul/gotty-client/releases/tag/v1.3.0) (2015-10-27)
|
||||
|
||||
* Fix `connected` state when using `Connect()` + `Loop()` methods
|
||||
* Add `ExitLoop` which allow to exit from `Loop` function
|
||||
|
||||
[full commits list](https://github.com/moul/gotty-client/compare/v1.2.0...v1.3.0)
|
||||
|
||||
### [v1.2.0](https://github.com/moul/gotty-client/releases/tag/v1.2.0) (2015-10-23)
|
||||
|
||||
* Removed an annoying warning when exiting connection ([#22](https://github.com/moul/gotty-client/issues/22)) ([@QuentinPerez](https://github.com/QuentinPerez))
|
||||
* Add the ability to configure alternative stdout ([#21](https://github.com/moul/gotty-client/issues/21)) ([@QuentinPerez](https://github.com/QuentinPerez))
|
||||
* Refactored the goroutine system with select, improve speed and stability ([@QuentinPerez](https://github.com/QuentinPerez))
|
||||
* Add debug mode (`--debug`/`-D`) ([#18](https://github.com/moul/gotty-client/issues/18))
|
||||
* Improve error message when connecting by checking HTTP status code
|
||||
* Fix arguments passing ([#16](https://github.com/moul/gotty-client/issues/16))
|
||||
* Dropped support for golang<1.5
|
||||
* Small fixes
|
||||
|
||||
[full commits list](https://github.com/moul/gotty-client/compare/v1.1.0...v1.2.0)
|
||||
|
||||
### [v1.1.0](https://github.com/moul/gotty-client/releases/tag/v1.1.0) (2015-10-10)
|
||||
|
||||
* Handling arguments + using mutexes (thanks to [@QuentinPerez](https://github.com/QuentinPerez))
|
||||
* Add logo ([#9](https://github.com/moul/gotty-client/issues/9))
|
||||
* Using codegansta/cli for CLI parsing ([#3](https://github.com/moul/gotty-client/issues/3))
|
||||
* Fix panic when running on older GoTTY server ([#13](https://github.com/moul/gotty-client/issues/13))
|
||||
* Add 'homebrew support' ([#1](https://github.com/moul/gotty-client/issues/1))
|
||||
* Add Changelog ([#5](https://github.com/moul/gotty-client/issues/5))
|
||||
* Add GOXC configuration to build binaries for multiple architectures ([#2](https://github.com/moul/gotty-client/issues/2))
|
||||
|
||||
[full commits list](https://github.com/moul/gotty-client/compare/v1.0.1...v1.1.0)
|
||||
|
||||
### [v1.0.1](https://github.com/moul/gotty-client/releases/tag/v1.0.1) (2015-09-27)
|
||||
|
||||
* Using party to manage dependencies
|
||||
|
||||
[full commits list](https://github.com/moul/gotty-client/compare/v1.0.0...v1.0.1)
|
||||
|
||||
### [v1.0.0](https://github.com/moul/gotty-client/releases/tag/v1.0.0) (2015-09-27)
|
||||
|
||||
Compatible with [GoTTY](https://github.com/yudai/gotty) version: [v0.0.10](https://github.com/yudai/gotty/releases/tag/v0.0.10)
|
||||
|
||||
#### Features
|
||||
|
||||
* Support **basic-auth**
|
||||
* Support **terminal-(re)size**
|
||||
* Support **write**
|
||||
* Support **title**
|
||||
* Support **custom URI**
|
||||
|
||||
[full commits list](https://github.com/moul/gotty-client/compare/cf0c1146c7ce20fe0bd65764c13253bc575cd43a...v1.0.0)
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
|
||||
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fmoul%2Fgotty-client.svg?type=large)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fmoul%2Fgotty-client?ref=badge_large)
|
|
@ -1,32 +0,0 @@
|
|||
// +build !windows
|
||||
|
||||
package gottyclient
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"golang.org/x/sys/unix"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func notifySignalSIGWINCH(c chan<- os.Signal) {
|
||||
signal.Notify(c, syscall.SIGWINCH)
|
||||
}
|
||||
|
||||
func resetSignalSIGWINCH() {
|
||||
signal.Reset(syscall.SIGWINCH)
|
||||
}
|
||||
|
||||
func syscallTIOCGWINSZ() ([]byte, error) {
|
||||
ws, err := unix.IoctlGetWinsize(0, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ioctl error: %v", err)
|
||||
}
|
||||
b, err := json.Marshal(ws)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("json.Marshal error: %v", err)
|
||||
}
|
||||
return b, err
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
package gottyclient
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
)
|
||||
|
||||
func notifySignalSIGWINCH(c chan<- os.Signal) {
|
||||
}
|
||||
|
||||
func resetSignalSIGWINCH() {
|
||||
}
|
||||
|
||||
func syscallTIOCGWINSZ() ([]byte, error) {
|
||||
return nil, errors.New("SIGWINCH isn't supported on this ARCH")
|
||||
}
|
|
@ -1,473 +0,0 @@
|
|||
package gottyclient
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/creack/goselect"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
// GetAuthTokenURL transforms a GoTTY http URL to its AuthToken file URL
|
||||
func GetAuthTokenURL(httpURL string) (*url.URL, *http.Header, error) {
|
||||
header := http.Header{}
|
||||
target, err := url.Parse(httpURL)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
target.Path = strings.TrimLeft(target.Path+"auth_token.js", "/")
|
||||
|
||||
if target.User != nil {
|
||||
header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(target.User.String())))
|
||||
target.User = nil
|
||||
}
|
||||
|
||||
return target, &header, nil
|
||||
}
|
||||
|
||||
// GetURLQuery returns url.query
|
||||
func GetURLQuery(rawurl string) (url.Values, error) {
|
||||
target, err := url.Parse(rawurl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return target.Query(), nil
|
||||
}
|
||||
|
||||
// GetWebsocketURL transforms a GoTTY http URL to its WebSocket URL
|
||||
func GetWebsocketURL(httpURL string) (*url.URL, *http.Header, error) {
|
||||
header := http.Header{}
|
||||
target, err := url.Parse(httpURL)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if target.Scheme == "https" {
|
||||
target.Scheme = "wss"
|
||||
} else {
|
||||
target.Scheme = "ws"
|
||||
}
|
||||
|
||||
target.Path = strings.TrimLeft(target.Path+"ws", "/")
|
||||
|
||||
if target.User != nil {
|
||||
header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(target.User.String())))
|
||||
target.User = nil
|
||||
}
|
||||
|
||||
return target, &header, nil
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
Dialer *websocket.Dialer
|
||||
Conn *websocket.Conn
|
||||
URL string
|
||||
WriteMutex *sync.Mutex
|
||||
Output io.Writer
|
||||
poison chan bool
|
||||
SkipTLSVerify bool
|
||||
UseProxyFromEnv bool
|
||||
Connected bool
|
||||
EscapeKeys []byte
|
||||
}
|
||||
|
||||
type querySingleType struct {
|
||||
AuthToken string `json:"AuthToken"`
|
||||
Arguments string `json:"Arguments"`
|
||||
}
|
||||
|
||||
func (c *Client) write(data []byte) error {
|
||||
c.WriteMutex.Lock()
|
||||
defer c.WriteMutex.Unlock()
|
||||
return c.Conn.WriteMessage(websocket.TextMessage, data)
|
||||
}
|
||||
|
||||
// GetAuthToken retrieves an Auth Token from dynamic auth_token.js file
|
||||
func (c *Client) GetAuthToken() (string, error) {
|
||||
target, header, err := GetAuthTokenURL(c.URL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
logrus.Debugf("Fetching auth token auth-token: %q", target.String())
|
||||
req, err := http.NewRequest("GET", target.String(), nil)
|
||||
req.Header = *header
|
||||
tr := &http.Transport{}
|
||||
if c.SkipTLSVerify {
|
||||
conf := &tls.Config{InsecureSkipVerify: true}
|
||||
tr.TLSClientConfig = conf
|
||||
}
|
||||
if c.UseProxyFromEnv {
|
||||
tr.Proxy = http.ProxyFromEnvironment
|
||||
}
|
||||
client := &http.Client{Transport: tr}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
switch resp.StatusCode {
|
||||
case 200:
|
||||
// Everything is OK
|
||||
default:
|
||||
return "", fmt.Errorf("unknown status code: %d (%s)", resp.StatusCode, http.StatusText(resp.StatusCode))
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
re := regexp.MustCompile("var gotty_auth_token = '(.*)'")
|
||||
output := re.FindStringSubmatch(string(body))
|
||||
if len(output) == 0 {
|
||||
return "", fmt.Errorf("Cannot fetch GoTTY auth-token, please upgrade your GoTTY server.")
|
||||
}
|
||||
|
||||
return output[1], nil
|
||||
}
|
||||
|
||||
// Connect tries to dial a websocket server
|
||||
func (c *Client) Connect() error {
|
||||
// Retrieve AuthToken
|
||||
authToken, err := c.GetAuthToken()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logrus.Debugf("Auth-token: %q", authToken)
|
||||
|
||||
// Open WebSocket connection
|
||||
target, header, err := GetWebsocketURL(c.URL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logrus.Debugf("Connecting to websocket: %q", target.String())
|
||||
if c.SkipTLSVerify {
|
||||
c.Dialer.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
}
|
||||
if c.UseProxyFromEnv {
|
||||
c.Dialer.Proxy = http.ProxyFromEnvironment
|
||||
}
|
||||
conn, _, err := c.Dialer.Dial(target.String(), *header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Conn = conn
|
||||
c.Connected = true
|
||||
|
||||
// Pass arguments and auth-token
|
||||
query, err := GetURLQuery(c.URL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
querySingle := querySingleType{
|
||||
Arguments: "?" + query.Encode(),
|
||||
AuthToken: authToken,
|
||||
}
|
||||
json, err := json.Marshal(querySingle)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to parse init message %v", err)
|
||||
return err
|
||||
}
|
||||
// Send Json
|
||||
logrus.Debugf("Sending arguments and auth-token")
|
||||
err = c.write(json)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go c.pingLoop()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) pingLoop() {
|
||||
for {
|
||||
logrus.Debugf("Sending ping")
|
||||
c.write([]byte("1"))
|
||||
time.Sleep(30 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
// Close will nicely close the dialer
|
||||
func (c *Client) Close() {
|
||||
c.Conn.Close()
|
||||
}
|
||||
|
||||
// ExitLoop will kill all goroutines launched by c.Loop()
|
||||
// ExitLoop() -> wait Loop() -> Close()
|
||||
func (c *Client) ExitLoop() {
|
||||
fname := "ExitLoop"
|
||||
openPoison(fname, c.poison)
|
||||
}
|
||||
|
||||
// Loop will look indefinitely for new messages
|
||||
func (c *Client) Loop() error {
|
||||
if !c.Connected {
|
||||
err := c.Connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
wg.Add(1)
|
||||
go c.termsizeLoop(wg)
|
||||
|
||||
wg.Add(1)
|
||||
go c.readLoop(wg)
|
||||
|
||||
wg.Add(1)
|
||||
go c.writeLoop(wg)
|
||||
|
||||
/* Wait for all of the above goroutines to finish */
|
||||
wg.Wait()
|
||||
|
||||
logrus.Debug("Client.Loop() exiting")
|
||||
return nil
|
||||
}
|
||||
|
||||
type winsize struct {
|
||||
Rows uint16 `json:"rows"`
|
||||
Columns uint16 `json:"columns"`
|
||||
// unused
|
||||
x uint16
|
||||
y uint16
|
||||
}
|
||||
|
||||
type posionReason int
|
||||
|
||||
const (
|
||||
committedSuicide = iota
|
||||
killed
|
||||
)
|
||||
|
||||
func openPoison(fname string, poison chan bool) posionReason {
|
||||
logrus.Debug(fname + " suicide")
|
||||
|
||||
/*
|
||||
* The close() may raise panic if multiple goroutines commit suicide at the
|
||||
* same time. Prevent that panic from bubbling up.
|
||||
*/
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
logrus.Debug("Prevented panic() of simultaneous suicides", r)
|
||||
}
|
||||
}()
|
||||
|
||||
/* Signal others to die */
|
||||
close(poison)
|
||||
return committedSuicide
|
||||
}
|
||||
|
||||
func die(fname string, poison chan bool) posionReason {
|
||||
logrus.Debug(fname + " died")
|
||||
|
||||
wasOpen := <-poison
|
||||
if wasOpen {
|
||||
logrus.Error("ERROR: The channel was open when it wasn't suppoed to be")
|
||||
}
|
||||
|
||||
return killed
|
||||
}
|
||||
|
||||
func (c *Client) termsizeLoop(wg *sync.WaitGroup) posionReason {
|
||||
|
||||
defer wg.Done()
|
||||
fname := "termsizeLoop"
|
||||
|
||||
ch := make(chan os.Signal, 1)
|
||||
notifySignalSIGWINCH(ch)
|
||||
defer resetSignalSIGWINCH()
|
||||
|
||||
for {
|
||||
if b, err := syscallTIOCGWINSZ(); err != nil {
|
||||
logrus.Warn(err)
|
||||
} else {
|
||||
if err = c.write(append([]byte("2"), b...)); err != nil {
|
||||
logrus.Warnf("ws.WriteMessage failed: %v", err)
|
||||
}
|
||||
}
|
||||
select {
|
||||
case <-c.poison:
|
||||
/* Somebody poisoned the well; die */
|
||||
return die(fname, c.poison)
|
||||
case <-ch:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type exposeFd interface {
|
||||
Fd() uintptr
|
||||
}
|
||||
|
||||
func (c *Client) writeLoop(wg *sync.WaitGroup) posionReason {
|
||||
defer wg.Done()
|
||||
fname := "writeLoop"
|
||||
|
||||
buff := make([]byte, 128)
|
||||
oldState, err := terminal.MakeRaw(0)
|
||||
if err == nil {
|
||||
defer terminal.Restore(0, oldState)
|
||||
}
|
||||
|
||||
rdfs := &goselect.FDSet{}
|
||||
reader := io.ReadCloser(os.Stdin)
|
||||
|
||||
pr := NewEscapeProxy(reader, c.EscapeKeys)
|
||||
defer reader.Close()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-c.poison:
|
||||
/* Somebody poisoned the well; die */
|
||||
return die(fname, c.poison)
|
||||
default:
|
||||
}
|
||||
|
||||
rdfs.Zero()
|
||||
rdfs.Set(reader.(exposeFd).Fd())
|
||||
err := goselect.Select(1, rdfs, nil, nil, 50*time.Millisecond)
|
||||
if err != nil {
|
||||
return openPoison(fname, c.poison)
|
||||
}
|
||||
if rdfs.IsSet(reader.(exposeFd).Fd()) {
|
||||
size, err := pr.Read(buff)
|
||||
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
// Send EOF to GoTTY
|
||||
|
||||
// Send 'Input' marker, as defined in GoTTY::client_context.go,
|
||||
// followed by EOT (a translation of Ctrl-D for terminals)
|
||||
err = c.write(append([]byte("0"), byte(4)))
|
||||
|
||||
if err != nil {
|
||||
return openPoison(fname, c.poison)
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
return openPoison(fname, c.poison)
|
||||
}
|
||||
}
|
||||
|
||||
if size <= 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
data := buff[:size]
|
||||
err = c.write(append([]byte("0"), data...))
|
||||
if err != nil {
|
||||
return openPoison(fname, c.poison)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) readLoop(wg *sync.WaitGroup) posionReason {
|
||||
|
||||
defer wg.Done()
|
||||
fname := "readLoop"
|
||||
|
||||
type MessageNonBlocking struct {
|
||||
Data []byte
|
||||
Err error
|
||||
}
|
||||
msgChan := make(chan MessageNonBlocking)
|
||||
|
||||
for {
|
||||
go func() {
|
||||
_, data, err := c.Conn.ReadMessage()
|
||||
msgChan <- MessageNonBlocking{Data: data, Err: err}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-c.poison:
|
||||
/* Somebody poisoned the well; die */
|
||||
return die(fname, c.poison)
|
||||
case msg := <-msgChan:
|
||||
if msg.Err != nil {
|
||||
|
||||
if _, ok := msg.Err.(*websocket.CloseError); !ok {
|
||||
logrus.Warnf("c.Conn.ReadMessage: %v", msg.Err)
|
||||
}
|
||||
return openPoison(fname, c.poison)
|
||||
}
|
||||
if len(msg.Data) == 0 {
|
||||
|
||||
logrus.Warnf("An error has occured")
|
||||
return openPoison(fname, c.poison)
|
||||
}
|
||||
switch msg.Data[0] {
|
||||
case '0': // data
|
||||
buf, err := base64.StdEncoding.DecodeString(string(msg.Data[1:]))
|
||||
if err != nil {
|
||||
logrus.Warnf("Invalid base64 content: %q", msg.Data[1:])
|
||||
break
|
||||
}
|
||||
c.Output.Write(buf)
|
||||
case '1': // pong
|
||||
case '2': // new title
|
||||
newTitle := string(msg.Data[1:])
|
||||
fmt.Fprintf(c.Output, "\033]0;%s\007", newTitle)
|
||||
case '3': // json prefs
|
||||
logrus.Debugf("Unhandled protocol message: json pref: %s", string(msg.Data[1:]))
|
||||
case '4': // autoreconnect
|
||||
logrus.Debugf("Unhandled protocol message: autoreconnect: %s", string(msg.Data))
|
||||
default:
|
||||
logrus.Warnf("Unhandled protocol message: %s", string(msg.Data))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SetOutput changes the output stream
|
||||
func (c *Client) SetOutput(w io.Writer) {
|
||||
c.Output = w
|
||||
}
|
||||
|
||||
// ParseURL parses an URL which may be incomplete and tries to standardize it
|
||||
func ParseURL(input string) (string, error) {
|
||||
parsed, err := url.Parse(input)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
switch parsed.Scheme {
|
||||
case "http", "https":
|
||||
// everything is ok
|
||||
default:
|
||||
return ParseURL(fmt.Sprintf("http://%s", input))
|
||||
}
|
||||
return parsed.String(), nil
|
||||
}
|
||||
|
||||
// NewClient returns a GoTTY client object
|
||||
func NewClient(inputURL string) (*Client, error) {
|
||||
url, err := ParseURL(inputURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Client{
|
||||
Dialer: &websocket.Dialer{},
|
||||
URL: url,
|
||||
WriteMutex: &sync.Mutex{},
|
||||
Output: os.Stdout,
|
||||
poison: make(chan bool),
|
||||
}, nil
|
||||
}
|
|
@ -1,90 +0,0 @@
|
|||
// Copyright 2013-2017 Docker, Inc.
|
||||
//
|
||||
// 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.
|
||||
|
||||
// CHANGES:
|
||||
// - update package
|
||||
|
||||
|
||||
package gottyclient
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// EscapeError is special error which returned by a TTY proxy reader's Read()
|
||||
// method in case its detach escape sequence is read.
|
||||
type EscapeError struct{}
|
||||
|
||||
func (EscapeError) Error() string {
|
||||
return "read escape sequence"
|
||||
}
|
||||
|
||||
// escapeProxy is used only for attaches with a TTY. It is used to proxy
|
||||
// stdin keypresses from the underlying reader and look for the passed in
|
||||
// escape key sequence to signal a detach.
|
||||
type escapeProxy struct {
|
||||
escapeKeys []byte
|
||||
escapeKeyPos int
|
||||
r io.Reader
|
||||
}
|
||||
|
||||
// NewEscapeProxy returns a new TTY proxy reader which wraps the given reader
|
||||
// and detects when the specified escape keys are read, in which case the Read
|
||||
// method will return an error of type EscapeError.
|
||||
func NewEscapeProxy(r io.Reader, escapeKeys []byte) io.Reader {
|
||||
return &escapeProxy{
|
||||
escapeKeys: escapeKeys,
|
||||
r: r,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *escapeProxy) Read(buf []byte) (int, error) {
|
||||
nr, err := r.r.Read(buf)
|
||||
|
||||
preserve := func() {
|
||||
// this preserves the original key presses in the passed in buffer
|
||||
nr += r.escapeKeyPos
|
||||
preserve := make([]byte, 0, r.escapeKeyPos+len(buf))
|
||||
preserve = append(preserve, r.escapeKeys[:r.escapeKeyPos]...)
|
||||
preserve = append(preserve, buf...)
|
||||
r.escapeKeyPos = 0
|
||||
copy(buf[0:nr], preserve)
|
||||
}
|
||||
|
||||
if nr != 1 || err != nil {
|
||||
if r.escapeKeyPos > 0 {
|
||||
preserve()
|
||||
}
|
||||
return nr, err
|
||||
}
|
||||
|
||||
if buf[0] != r.escapeKeys[r.escapeKeyPos] {
|
||||
if r.escapeKeyPos > 0 {
|
||||
preserve()
|
||||
}
|
||||
return nr, nil
|
||||
}
|
||||
|
||||
if r.escapeKeyPos == len(r.escapeKeys)-1 {
|
||||
return 0, EscapeError{}
|
||||
}
|
||||
|
||||
// Looks like we've got an escape key, but we need to match again on the next
|
||||
// read.
|
||||
// Store the current escape key we found so we can look for the next one on
|
||||
// the next read.
|
||||
// Since this is an escape key, make sure we don't let the caller read it
|
||||
// If later on we find that this is not the escape sequence, we'll add the
|
||||
// keys back
|
||||
r.escapeKeyPos++
|
||||
return nr - r.escapeKeyPos, nil
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Peter Renström
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -1,167 +0,0 @@
|
|||
// Fuzzy searching allows for flexibly matching a string with partial input,
|
||||
// useful for filtering data very quickly based on lightweight user input.
|
||||
package fuzzy
|
||||
|
||||
import (
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
var noop = func(r rune) rune { return r }
|
||||
|
||||
// Match returns true if source matches target using a fuzzy-searching
|
||||
// algorithm. Note that it doesn't implement Levenshtein distance (see
|
||||
// RankMatch instead), but rather a simplified version where there's no
|
||||
// approximation. The method will return true only if each character in the
|
||||
// source can be found in the target and occurs after the preceding matches.
|
||||
func Match(source, target string) bool {
|
||||
return match(source, target, noop)
|
||||
}
|
||||
|
||||
// MatchFold is a case-insensitive version of Match.
|
||||
func MatchFold(source, target string) bool {
|
||||
return match(source, target, unicode.ToLower)
|
||||
}
|
||||
|
||||
func match(source, target string, fn func(rune) rune) bool {
|
||||
lenDiff := len(target) - len(source)
|
||||
|
||||
if lenDiff < 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if lenDiff == 0 && source == target {
|
||||
return true
|
||||
}
|
||||
|
||||
Outer:
|
||||
for _, r1 := range source {
|
||||
for i, r2 := range target {
|
||||
if fn(r1) == fn(r2) {
|
||||
target = target[i+utf8.RuneLen(r2):]
|
||||
continue Outer
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Find will return a list of strings in targets that fuzzy matches source.
|
||||
func Find(source string, targets []string) []string {
|
||||
return find(source, targets, noop)
|
||||
}
|
||||
|
||||
// FindFold is a case-insensitive version of Find.
|
||||
func FindFold(source string, targets []string) []string {
|
||||
return find(source, targets, unicode.ToLower)
|
||||
}
|
||||
|
||||
func find(source string, targets []string, fn func(rune) rune) []string {
|
||||
var matches []string
|
||||
|
||||
for _, target := range targets {
|
||||
if match(source, target, fn) {
|
||||
matches = append(matches, target)
|
||||
}
|
||||
}
|
||||
|
||||
return matches
|
||||
}
|
||||
|
||||
// RankMatch is similar to Match except it will measure the Levenshtein
|
||||
// distance between the source and the target and return its result. If there
|
||||
// was no match, it will return -1.
|
||||
// Given the requirements of match, RankMatch only needs to perform a subset of
|
||||
// the Levenshtein calculation, only deletions need be considered, required
|
||||
// additions and substitutions would fail the match test.
|
||||
func RankMatch(source, target string) int {
|
||||
return rank(source, target, noop)
|
||||
}
|
||||
|
||||
// RankMatchFold is a case-insensitive version of RankMatch.
|
||||
func RankMatchFold(source, target string) int {
|
||||
return rank(source, target, unicode.ToLower)
|
||||
}
|
||||
|
||||
func rank(source, target string, fn func(rune) rune) int {
|
||||
lenDiff := len(target) - len(source)
|
||||
|
||||
if lenDiff < 0 {
|
||||
return -1
|
||||
}
|
||||
|
||||
if lenDiff == 0 && source == target {
|
||||
return 0
|
||||
}
|
||||
|
||||
runeDiff := 0
|
||||
|
||||
Outer:
|
||||
for _, r1 := range source {
|
||||
for i, r2 := range target {
|
||||
if fn(r1) == fn(r2) {
|
||||
target = target[i+utf8.RuneLen(r2):]
|
||||
continue Outer
|
||||
} else {
|
||||
runeDiff++
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Count up remaining char
|
||||
for len(target) > 0 {
|
||||
target = target[utf8.RuneLen(rune(target[0])):]
|
||||
runeDiff++
|
||||
}
|
||||
|
||||
return runeDiff
|
||||
}
|
||||
|
||||
// RankFind is similar to Find, except it will also rank all matches using
|
||||
// Levenshtein distance.
|
||||
func RankFind(source string, targets []string) Ranks {
|
||||
var r Ranks
|
||||
for _, target := range find(source, targets, noop) {
|
||||
distance := LevenshteinDistance(source, target)
|
||||
r = append(r, Rank{source, target, distance})
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// RankFindFold is a case-insensitive version of RankFind.
|
||||
func RankFindFold(source string, targets []string) Ranks {
|
||||
var r Ranks
|
||||
for _, target := range find(source, targets, unicode.ToLower) {
|
||||
distance := LevenshteinDistance(source, target)
|
||||
r = append(r, Rank{source, target, distance})
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
type Rank struct {
|
||||
// Source is used as the source for matching.
|
||||
Source string
|
||||
|
||||
// Target is the word matched against.
|
||||
Target string
|
||||
|
||||
// Distance is the Levenshtein distance between Source and Target.
|
||||
Distance int
|
||||
}
|
||||
|
||||
type Ranks []Rank
|
||||
|
||||
func (r Ranks) Len() int {
|
||||
return len(r)
|
||||
}
|
||||
|
||||
func (r Ranks) Swap(i, j int) {
|
||||
r[i], r[j] = r[j], r[i]
|
||||
}
|
||||
|
||||
func (r Ranks) Less(i, j int) bool {
|
||||
return r[i].Distance < r[j].Distance
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
package fuzzy
|
||||
|
||||
// LevenshteinDistance measures the difference between two strings.
|
||||
// The Levenshtein distance between two words is the minimum number of
|
||||
// single-character edits (i.e. insertions, deletions or substitutions)
|
||||
// required to change one word into the other.
|
||||
//
|
||||
// This implemention is optimized to use O(min(m,n)) space and is based on the
|
||||
// optimized C version found here:
|
||||
// http://en.wikibooks.org/wiki/Algorithm_implementation/Strings/Levenshtein_distance#C
|
||||
func LevenshteinDistance(s, t string) int {
|
||||
r1, r2 := []rune(s), []rune(t)
|
||||
column := make([]int, len(r1)+1)
|
||||
|
||||
for y := 1; y <= len(r1); y++ {
|
||||
column[y] = y
|
||||
}
|
||||
|
||||
for x := 1; x <= len(r2); x++ {
|
||||
column[0] = x
|
||||
|
||||
for y, lastDiag := 1, x-1; y <= len(r1); y++ {
|
||||
oldDiag := column[y]
|
||||
cost := 0
|
||||
if r1[y-1] != r2[x-1] {
|
||||
cost = 1
|
||||
}
|
||||
column[y] = min(column[y]+1, column[y-1]+1, lastDiag+cost)
|
||||
lastDiag = oldDiag
|
||||
}
|
||||
}
|
||||
|
||||
return column[len(r1)]
|
||||
}
|
||||
|
||||
func min(a, b, c int) int {
|
||||
if a < b && a < c {
|
||||
return a
|
||||
} else if b < c {
|
||||
return b
|
||||
}
|
||||
return c
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
The MIT License
|
||||
===============
|
||||
|
||||
Copyright (c) **2014-2016 Scaleway <opensource@scaleway.com> ([@scaleway](https://twitter.com/scaleway))**
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -1,3 +0,0 @@
|
|||
# Scaleway's API
|
||||
|
||||
## Deprecated in favor of https://github.com/scaleway/go-scaleway
|
File diff suppressed because it is too large
Load Diff
|
@ -1,816 +0,0 @@
|
|||
// Copyright (C) 2015 Scaleway. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE.md file.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/moul/anonuuid"
|
||||
"github.com/renstrom/fuzzysearch/fuzzy"
|
||||
)
|
||||
|
||||
const (
|
||||
// CacheRegion permits to access at the region field
|
||||
CacheRegion = iota
|
||||
// CacheArch permits to access at the arch field
|
||||
CacheArch
|
||||
// CacheOwner permits to access at the owner field
|
||||
CacheOwner
|
||||
// CacheTitle permits to access at the title field
|
||||
CacheTitle
|
||||
// CacheMarketPlaceUUID is used to determine the UUID of local images
|
||||
CacheMarketPlaceUUID
|
||||
// CacheMaxfield is used to determine the size of array
|
||||
CacheMaxfield
|
||||
)
|
||||
|
||||
// ScalewayCache is used not to query the API to resolve full identifiers
|
||||
type ScalewayCache struct {
|
||||
// Images contains names of Scaleway images indexed by identifier
|
||||
Images map[string][CacheMaxfield]string `json:"images"`
|
||||
|
||||
// Snapshots contains names of Scaleway snapshots indexed by identifier
|
||||
Snapshots map[string][CacheMaxfield]string `json:"snapshots"`
|
||||
|
||||
// Volumes contains names of Scaleway volumes indexed by identifier
|
||||
Volumes map[string][CacheMaxfield]string `json:"volumes"`
|
||||
|
||||
// Bootscripts contains names of Scaleway bootscripts indexed by identifier
|
||||
Bootscripts map[string][CacheMaxfield]string `json:"bootscripts"`
|
||||
|
||||
// Servers contains names of Scaleway servers indexed by identifier
|
||||
Servers map[string][CacheMaxfield]string `json:"servers"`
|
||||
|
||||
// Path is the path to the cache file
|
||||
Path string `json:"-"`
|
||||
|
||||
// Modified tells if the cache needs to be overwritten or not
|
||||
Modified bool `json:"-"`
|
||||
|
||||
// Lock allows ScalewayCache to be used concurrently
|
||||
Lock sync.Mutex `json:"-"`
|
||||
|
||||
hookSave func()
|
||||
}
|
||||
|
||||
const (
|
||||
// IdentifierUnknown is used when we don't know explicitly the type key of the object (used for nil comparison)
|
||||
IdentifierUnknown = 1 << iota
|
||||
// IdentifierServer is the type key of cached server objects
|
||||
IdentifierServer
|
||||
// IdentifierImage is the type key of cached image objects
|
||||
IdentifierImage
|
||||
// IdentifierSnapshot is the type key of cached snapshot objects
|
||||
IdentifierSnapshot
|
||||
// IdentifierBootscript is the type key of cached bootscript objects
|
||||
IdentifierBootscript
|
||||
// IdentifierVolume is the type key of cached volume objects
|
||||
IdentifierVolume
|
||||
)
|
||||
|
||||
// ScalewayResolverResult is a structure containing human-readable information
|
||||
// about resolver results. This structure is used to display the user choices.
|
||||
type ScalewayResolverResult struct {
|
||||
Identifier string
|
||||
Type int
|
||||
Name string
|
||||
Arch string
|
||||
Needle string
|
||||
RankMatch int
|
||||
Region string
|
||||
}
|
||||
|
||||
// ScalewayResolverResults is a list of `ScalewayResolverResult`
|
||||
type ScalewayResolverResults []ScalewayResolverResult
|
||||
|
||||
// NewScalewayResolverResult returns a new ScalewayResolverResult
|
||||
func NewScalewayResolverResult(Identifier, Name, Arch, Region string, Type int) (ScalewayResolverResult, error) {
|
||||
if err := anonuuid.IsUUID(Identifier); err != nil {
|
||||
return ScalewayResolverResult{}, err
|
||||
}
|
||||
return ScalewayResolverResult{
|
||||
Identifier: Identifier,
|
||||
Type: Type,
|
||||
Name: Name,
|
||||
Arch: Arch,
|
||||
Region: Region,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s ScalewayResolverResults) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
func (s ScalewayResolverResults) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
func (s ScalewayResolverResults) Less(i, j int) bool {
|
||||
return s[i].RankMatch < s[j].RankMatch
|
||||
}
|
||||
|
||||
// TruncIdentifier returns first 8 characters of an Identifier (UUID)
|
||||
func (s *ScalewayResolverResult) TruncIdentifier() string {
|
||||
return s.Identifier[:8]
|
||||
}
|
||||
|
||||
func identifierTypeName(kind int) string {
|
||||
switch kind {
|
||||
case IdentifierServer:
|
||||
return "Server"
|
||||
case IdentifierImage:
|
||||
return "Image"
|
||||
case IdentifierSnapshot:
|
||||
return "Snapshot"
|
||||
case IdentifierVolume:
|
||||
return "Volume"
|
||||
case IdentifierBootscript:
|
||||
return "Bootscript"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// CodeName returns a full resource name with typed prefix
|
||||
func (s *ScalewayResolverResult) CodeName() string {
|
||||
name := strings.ToLower(s.Name)
|
||||
name = regexp.MustCompile(`[^a-z0-9-]`).ReplaceAllString(name, "-")
|
||||
name = regexp.MustCompile(`--+`).ReplaceAllString(name, "-")
|
||||
name = strings.Trim(name, "-")
|
||||
|
||||
return fmt.Sprintf("%s:%s", strings.ToLower(identifierTypeName(s.Type)), name)
|
||||
}
|
||||
|
||||
// FilterByArch deletes the elements which not match with arch
|
||||
func (s *ScalewayResolverResults) FilterByArch(arch string) {
|
||||
REDO:
|
||||
for i := range *s {
|
||||
if (*s)[i].Arch != arch {
|
||||
(*s)[i] = (*s)[len(*s)-1]
|
||||
*s = (*s)[:len(*s)-1]
|
||||
goto REDO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewScalewayCache loads a per-user cache
|
||||
func NewScalewayCache(hookSave func()) (*ScalewayCache, error) {
|
||||
var cache ScalewayCache
|
||||
|
||||
cache.hookSave = hookSave
|
||||
homeDir := os.Getenv("HOME") // *nix
|
||||
if homeDir == "" { // Windows
|
||||
homeDir = os.Getenv("USERPROFILE")
|
||||
}
|
||||
if homeDir == "" {
|
||||
homeDir = "/tmp"
|
||||
}
|
||||
cachePath := filepath.Join(homeDir, ".scw-cache.db")
|
||||
cache.Path = cachePath
|
||||
_, err := os.Stat(cachePath)
|
||||
if os.IsNotExist(err) {
|
||||
cache.Clear()
|
||||
return &cache, nil
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
file, err := ioutil.ReadFile(cachePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = json.Unmarshal(file, &cache)
|
||||
if err != nil {
|
||||
// fix compatibility with older version
|
||||
if err = os.Remove(cachePath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cache.Clear()
|
||||
return &cache, nil
|
||||
}
|
||||
if cache.Images == nil {
|
||||
cache.Images = make(map[string][CacheMaxfield]string)
|
||||
}
|
||||
if cache.Snapshots == nil {
|
||||
cache.Snapshots = make(map[string][CacheMaxfield]string)
|
||||
}
|
||||
if cache.Volumes == nil {
|
||||
cache.Volumes = make(map[string][CacheMaxfield]string)
|
||||
}
|
||||
if cache.Servers == nil {
|
||||
cache.Servers = make(map[string][CacheMaxfield]string)
|
||||
}
|
||||
if cache.Bootscripts == nil {
|
||||
cache.Bootscripts = make(map[string][CacheMaxfield]string)
|
||||
}
|
||||
return &cache, nil
|
||||
}
|
||||
|
||||
// Clear removes all information from the cache
|
||||
func (c *ScalewayCache) Clear() {
|
||||
c.Images = make(map[string][CacheMaxfield]string)
|
||||
c.Snapshots = make(map[string][CacheMaxfield]string)
|
||||
c.Volumes = make(map[string][CacheMaxfield]string)
|
||||
c.Bootscripts = make(map[string][CacheMaxfield]string)
|
||||
c.Servers = make(map[string][CacheMaxfield]string)
|
||||
c.Modified = true
|
||||
}
|
||||
|
||||
// Flush flushes the cache database
|
||||
func (c *ScalewayCache) Flush() error {
|
||||
return os.Remove(c.Path)
|
||||
}
|
||||
|
||||
// Save atomically overwrites the current cache database
|
||||
func (c *ScalewayCache) Save() error {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
c.hookSave()
|
||||
if c.Modified {
|
||||
file, err := ioutil.TempFile(filepath.Dir(c.Path), filepath.Base(c.Path))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(file).Encode(c); err != nil {
|
||||
file.Close()
|
||||
os.Remove(file.Name())
|
||||
return err
|
||||
}
|
||||
|
||||
file.Close()
|
||||
if err := os.Rename(file.Name(), c.Path); err != nil {
|
||||
os.Remove(file.Name())
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ComputeRankMatch fills `ScalewayResolverResult.RankMatch` with its `fuzzy` score
|
||||
func (s *ScalewayResolverResult) ComputeRankMatch(needle string) {
|
||||
s.Needle = needle
|
||||
s.RankMatch = fuzzy.RankMatch(needle, s.Name)
|
||||
}
|
||||
|
||||
// LookUpImages attempts to return identifiers matching a pattern
|
||||
func (c *ScalewayCache) LookUpImages(needle string, acceptUUID bool) (ScalewayResolverResults, error) {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
var res ScalewayResolverResults
|
||||
var exactMatches ScalewayResolverResults
|
||||
|
||||
if acceptUUID && anonuuid.IsUUID(needle) == nil {
|
||||
if fields, ok := c.Images[needle]; ok {
|
||||
entry, err := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierImage)
|
||||
if err != nil {
|
||||
return ScalewayResolverResults{}, err
|
||||
}
|
||||
entry.ComputeRankMatch(needle)
|
||||
res = append(res, entry)
|
||||
}
|
||||
}
|
||||
|
||||
needle = regexp.MustCompile(`^user/`).ReplaceAllString(needle, "")
|
||||
// FIXME: if 'user/' is in needle, only watch for a user image
|
||||
nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*"))
|
||||
for identifier, fields := range c.Images {
|
||||
if fields[CacheTitle] == needle {
|
||||
entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierImage)
|
||||
if err != nil {
|
||||
return ScalewayResolverResults{}, err
|
||||
}
|
||||
entry.ComputeRankMatch(needle)
|
||||
exactMatches = append(exactMatches, entry)
|
||||
}
|
||||
if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(fields[CacheTitle]) {
|
||||
entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierImage)
|
||||
if err != nil {
|
||||
return ScalewayResolverResults{}, err
|
||||
}
|
||||
entry.ComputeRankMatch(needle)
|
||||
res = append(res, entry)
|
||||
} else if strings.HasPrefix(fields[CacheMarketPlaceUUID], needle) || nameRegex.MatchString(fields[CacheMarketPlaceUUID]) {
|
||||
entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierImage)
|
||||
if err != nil {
|
||||
return ScalewayResolverResults{}, err
|
||||
}
|
||||
entry.ComputeRankMatch(needle)
|
||||
res = append(res, entry)
|
||||
}
|
||||
}
|
||||
|
||||
if len(exactMatches) == 1 {
|
||||
return exactMatches, nil
|
||||
}
|
||||
|
||||
return removeDuplicatesResults(res), nil
|
||||
}
|
||||
|
||||
// LookUpSnapshots attempts to return identifiers matching a pattern
|
||||
func (c *ScalewayCache) LookUpSnapshots(needle string, acceptUUID bool) (ScalewayResolverResults, error) {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
var res ScalewayResolverResults
|
||||
var exactMatches ScalewayResolverResults
|
||||
|
||||
if acceptUUID && anonuuid.IsUUID(needle) == nil {
|
||||
if fields, ok := c.Snapshots[needle]; ok {
|
||||
entry, err := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierSnapshot)
|
||||
if err != nil {
|
||||
return ScalewayResolverResults{}, err
|
||||
}
|
||||
entry.ComputeRankMatch(needle)
|
||||
res = append(res, entry)
|
||||
}
|
||||
}
|
||||
|
||||
needle = regexp.MustCompile(`^user/`).ReplaceAllString(needle, "")
|
||||
nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*"))
|
||||
for identifier, fields := range c.Snapshots {
|
||||
if fields[CacheTitle] == needle {
|
||||
entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierSnapshot)
|
||||
if err != nil {
|
||||
return ScalewayResolverResults{}, err
|
||||
}
|
||||
entry.ComputeRankMatch(needle)
|
||||
exactMatches = append(exactMatches, entry)
|
||||
}
|
||||
if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(fields[CacheTitle]) {
|
||||
entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierSnapshot)
|
||||
if err != nil {
|
||||
return ScalewayResolverResults{}, err
|
||||
}
|
||||
entry.ComputeRankMatch(needle)
|
||||
res = append(res, entry)
|
||||
}
|
||||
}
|
||||
|
||||
if len(exactMatches) == 1 {
|
||||
return exactMatches, nil
|
||||
}
|
||||
|
||||
return removeDuplicatesResults(res), nil
|
||||
}
|
||||
|
||||
// LookUpVolumes attempts to return identifiers matching a pattern
|
||||
func (c *ScalewayCache) LookUpVolumes(needle string, acceptUUID bool) (ScalewayResolverResults, error) {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
var res ScalewayResolverResults
|
||||
var exactMatches ScalewayResolverResults
|
||||
|
||||
if acceptUUID && anonuuid.IsUUID(needle) == nil {
|
||||
if fields, ok := c.Volumes[needle]; ok {
|
||||
entry, err := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierVolume)
|
||||
if err != nil {
|
||||
return ScalewayResolverResults{}, err
|
||||
}
|
||||
entry.ComputeRankMatch(needle)
|
||||
res = append(res, entry)
|
||||
}
|
||||
}
|
||||
|
||||
nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*"))
|
||||
for identifier, fields := range c.Volumes {
|
||||
if fields[CacheTitle] == needle {
|
||||
entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierVolume)
|
||||
if err != nil {
|
||||
return ScalewayResolverResults{}, err
|
||||
}
|
||||
entry.ComputeRankMatch(needle)
|
||||
exactMatches = append(exactMatches, entry)
|
||||
}
|
||||
if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(fields[CacheTitle]) {
|
||||
entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierVolume)
|
||||
if err != nil {
|
||||
return ScalewayResolverResults{}, err
|
||||
}
|
||||
entry.ComputeRankMatch(needle)
|
||||
res = append(res, entry)
|
||||
}
|
||||
}
|
||||
|
||||
if len(exactMatches) == 1 {
|
||||
return exactMatches, nil
|
||||
}
|
||||
|
||||
return removeDuplicatesResults(res), nil
|
||||
}
|
||||
|
||||
// LookUpBootscripts attempts to return identifiers matching a pattern
|
||||
func (c *ScalewayCache) LookUpBootscripts(needle string, acceptUUID bool) (ScalewayResolverResults, error) {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
var res ScalewayResolverResults
|
||||
var exactMatches ScalewayResolverResults
|
||||
|
||||
if acceptUUID && anonuuid.IsUUID(needle) == nil {
|
||||
if fields, ok := c.Bootscripts[needle]; ok {
|
||||
entry, err := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierBootscript)
|
||||
if err != nil {
|
||||
return ScalewayResolverResults{}, err
|
||||
}
|
||||
entry.ComputeRankMatch(needle)
|
||||
res = append(res, entry)
|
||||
}
|
||||
}
|
||||
|
||||
nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*"))
|
||||
for identifier, fields := range c.Bootscripts {
|
||||
if fields[CacheTitle] == needle {
|
||||
entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierBootscript)
|
||||
if err != nil {
|
||||
return ScalewayResolverResults{}, err
|
||||
}
|
||||
entry.ComputeRankMatch(needle)
|
||||
exactMatches = append(exactMatches, entry)
|
||||
}
|
||||
if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(fields[CacheTitle]) {
|
||||
entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierBootscript)
|
||||
if err != nil {
|
||||
return ScalewayResolverResults{}, err
|
||||
}
|
||||
entry.ComputeRankMatch(needle)
|
||||
res = append(res, entry)
|
||||
}
|
||||
}
|
||||
|
||||
if len(exactMatches) == 1 {
|
||||
return exactMatches, nil
|
||||
}
|
||||
|
||||
return removeDuplicatesResults(res), nil
|
||||
}
|
||||
|
||||
// LookUpServers attempts to return identifiers matching a pattern
|
||||
func (c *ScalewayCache) LookUpServers(needle string, acceptUUID bool) (ScalewayResolverResults, error) {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
var res ScalewayResolverResults
|
||||
var exactMatches ScalewayResolverResults
|
||||
|
||||
if acceptUUID && anonuuid.IsUUID(needle) == nil {
|
||||
if fields, ok := c.Servers[needle]; ok {
|
||||
entry, err := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierServer)
|
||||
if err != nil {
|
||||
return ScalewayResolverResults{}, err
|
||||
}
|
||||
entry.ComputeRankMatch(needle)
|
||||
res = append(res, entry)
|
||||
}
|
||||
}
|
||||
|
||||
nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*"))
|
||||
for identifier, fields := range c.Servers {
|
||||
if fields[CacheTitle] == needle {
|
||||
entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierServer)
|
||||
if err != nil {
|
||||
return ScalewayResolverResults{}, err
|
||||
}
|
||||
entry.ComputeRankMatch(needle)
|
||||
exactMatches = append(exactMatches, entry)
|
||||
}
|
||||
if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(fields[CacheTitle]) {
|
||||
entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierServer)
|
||||
if err != nil {
|
||||
return ScalewayResolverResults{}, err
|
||||
}
|
||||
entry.ComputeRankMatch(needle)
|
||||
res = append(res, entry)
|
||||
}
|
||||
}
|
||||
|
||||
if len(exactMatches) == 1 {
|
||||
return exactMatches, nil
|
||||
}
|
||||
|
||||
return removeDuplicatesResults(res), nil
|
||||
}
|
||||
|
||||
// removeDuplicatesResults transforms an array into a unique array
|
||||
func removeDuplicatesResults(elements ScalewayResolverResults) ScalewayResolverResults {
|
||||
encountered := map[string]ScalewayResolverResult{}
|
||||
|
||||
// Create a map of all unique elements.
|
||||
for v := range elements {
|
||||
encountered[elements[v].Identifier] = elements[v]
|
||||
}
|
||||
|
||||
// Place all keys from the map into a slice.
|
||||
results := ScalewayResolverResults{}
|
||||
for _, result := range encountered {
|
||||
results = append(results, result)
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
// parseNeedle parses a user needle and try to extract a forced object type
|
||||
// i.e:
|
||||
// - server:blah-blah -> kind=server, needle=blah-blah
|
||||
// - blah-blah -> kind="", needle=blah-blah
|
||||
// - not-existing-type:blah-blah
|
||||
func parseNeedle(input string) (identifierType int, needle string) {
|
||||
parts := strings.Split(input, ":")
|
||||
if len(parts) == 2 {
|
||||
switch parts[0] {
|
||||
case "server":
|
||||
return IdentifierServer, parts[1]
|
||||
case "image":
|
||||
return IdentifierImage, parts[1]
|
||||
case "snapshot":
|
||||
return IdentifierSnapshot, parts[1]
|
||||
case "bootscript":
|
||||
return IdentifierBootscript, parts[1]
|
||||
case "volume":
|
||||
return IdentifierVolume, parts[1]
|
||||
}
|
||||
}
|
||||
return IdentifierUnknown, input
|
||||
}
|
||||
|
||||
// LookUpIdentifiers attempts to return identifiers matching a pattern
|
||||
func (c *ScalewayCache) LookUpIdentifiers(needle string) (ScalewayResolverResults, error) {
|
||||
results := ScalewayResolverResults{}
|
||||
|
||||
identifierType, needle := parseNeedle(needle)
|
||||
|
||||
if identifierType&(IdentifierUnknown|IdentifierServer) > 0 {
|
||||
servers, err := c.LookUpServers(needle, false)
|
||||
if err != nil {
|
||||
return ScalewayResolverResults{}, err
|
||||
}
|
||||
for _, result := range servers {
|
||||
entry, err := NewScalewayResolverResult(result.Identifier, result.Name, result.Arch, result.Region, IdentifierServer)
|
||||
if err != nil {
|
||||
return ScalewayResolverResults{}, err
|
||||
}
|
||||
entry.ComputeRankMatch(needle)
|
||||
results = append(results, entry)
|
||||
}
|
||||
}
|
||||
|
||||
if identifierType&(IdentifierUnknown|IdentifierImage) > 0 {
|
||||
images, err := c.LookUpImages(needle, false)
|
||||
if err != nil {
|
||||
return ScalewayResolverResults{}, err
|
||||
}
|
||||
for _, result := range images {
|
||||
entry, err := NewScalewayResolverResult(result.Identifier, result.Name, result.Arch, result.Region, IdentifierImage)
|
||||
if err != nil {
|
||||
return ScalewayResolverResults{}, err
|
||||
}
|
||||
entry.ComputeRankMatch(needle)
|
||||
results = append(results, entry)
|
||||
}
|
||||
}
|
||||
|
||||
if identifierType&(IdentifierUnknown|IdentifierSnapshot) > 0 {
|
||||
snapshots, err := c.LookUpSnapshots(needle, false)
|
||||
if err != nil {
|
||||
return ScalewayResolverResults{}, err
|
||||
}
|
||||
for _, result := range snapshots {
|
||||
entry, err := NewScalewayResolverResult(result.Identifier, result.Name, result.Arch, result.Region, IdentifierSnapshot)
|
||||
if err != nil {
|
||||
return ScalewayResolverResults{}, err
|
||||
}
|
||||
entry.ComputeRankMatch(needle)
|
||||
results = append(results, entry)
|
||||
}
|
||||
}
|
||||
|
||||
if identifierType&(IdentifierUnknown|IdentifierVolume) > 0 {
|
||||
volumes, err := c.LookUpVolumes(needle, false)
|
||||
if err != nil {
|
||||
return ScalewayResolverResults{}, err
|
||||
}
|
||||
for _, result := range volumes {
|
||||
entry, err := NewScalewayResolverResult(result.Identifier, result.Name, result.Arch, result.Region, IdentifierVolume)
|
||||
if err != nil {
|
||||
return ScalewayResolverResults{}, err
|
||||
}
|
||||
entry.ComputeRankMatch(needle)
|
||||
results = append(results, entry)
|
||||
}
|
||||
}
|
||||
|
||||
if identifierType&(IdentifierUnknown|IdentifierBootscript) > 0 {
|
||||
bootscripts, err := c.LookUpBootscripts(needle, false)
|
||||
if err != nil {
|
||||
return ScalewayResolverResults{}, err
|
||||
}
|
||||
for _, result := range bootscripts {
|
||||
entry, err := NewScalewayResolverResult(result.Identifier, result.Name, result.Arch, result.Region, IdentifierBootscript)
|
||||
if err != nil {
|
||||
return ScalewayResolverResults{}, err
|
||||
}
|
||||
entry.ComputeRankMatch(needle)
|
||||
results = append(results, entry)
|
||||
}
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// InsertServer registers a server in the cache
|
||||
func (c *ScalewayCache) InsertServer(identifier, region, arch, owner, name string) {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
fields, exists := c.Servers[identifier]
|
||||
if !exists || fields[CacheTitle] != name {
|
||||
c.Servers[identifier] = [CacheMaxfield]string{region, arch, owner, name}
|
||||
c.Modified = true
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveServer removes a server from the cache
|
||||
func (c *ScalewayCache) RemoveServer(identifier string) {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
delete(c.Servers, identifier)
|
||||
c.Modified = true
|
||||
}
|
||||
|
||||
// ClearServers removes all servers from the cache
|
||||
func (c *ScalewayCache) ClearServers() {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
c.Servers = make(map[string][CacheMaxfield]string)
|
||||
c.Modified = true
|
||||
}
|
||||
|
||||
// InsertImage registers an image in the cache
|
||||
func (c *ScalewayCache) InsertImage(identifier, region, arch, owner, name, marketPlaceUUID string) {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
fields, exists := c.Images[identifier]
|
||||
if !exists || fields[CacheTitle] != name {
|
||||
c.Images[identifier] = [CacheMaxfield]string{region, arch, owner, name, marketPlaceUUID}
|
||||
c.Modified = true
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveImage removes a server from the cache
|
||||
func (c *ScalewayCache) RemoveImage(identifier string) {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
delete(c.Images, identifier)
|
||||
c.Modified = true
|
||||
}
|
||||
|
||||
// ClearImages removes all images from the cache
|
||||
func (c *ScalewayCache) ClearImages() {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
c.Images = make(map[string][CacheMaxfield]string)
|
||||
c.Modified = true
|
||||
}
|
||||
|
||||
// InsertSnapshot registers an snapshot in the cache
|
||||
func (c *ScalewayCache) InsertSnapshot(identifier, region, arch, owner, name string) {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
fields, exists := c.Snapshots[identifier]
|
||||
if !exists || fields[CacheTitle] != name {
|
||||
c.Snapshots[identifier] = [CacheMaxfield]string{region, arch, owner, name}
|
||||
c.Modified = true
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveSnapshot removes a server from the cache
|
||||
func (c *ScalewayCache) RemoveSnapshot(identifier string) {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
delete(c.Snapshots, identifier)
|
||||
c.Modified = true
|
||||
}
|
||||
|
||||
// ClearSnapshots removes all snapshots from the cache
|
||||
func (c *ScalewayCache) ClearSnapshots() {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
c.Snapshots = make(map[string][CacheMaxfield]string)
|
||||
c.Modified = true
|
||||
}
|
||||
|
||||
// InsertVolume registers an volume in the cache
|
||||
func (c *ScalewayCache) InsertVolume(identifier, region, arch, owner, name string) {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
fields, exists := c.Volumes[identifier]
|
||||
if !exists || fields[CacheTitle] != name {
|
||||
c.Volumes[identifier] = [CacheMaxfield]string{region, arch, owner, name}
|
||||
c.Modified = true
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveVolume removes a server from the cache
|
||||
func (c *ScalewayCache) RemoveVolume(identifier string) {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
delete(c.Volumes, identifier)
|
||||
c.Modified = true
|
||||
}
|
||||
|
||||
// ClearVolumes removes all volumes from the cache
|
||||
func (c *ScalewayCache) ClearVolumes() {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
c.Volumes = make(map[string][CacheMaxfield]string)
|
||||
c.Modified = true
|
||||
}
|
||||
|
||||
// InsertBootscript registers an bootscript in the cache
|
||||
func (c *ScalewayCache) InsertBootscript(identifier, region, arch, owner, name string) {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
fields, exists := c.Bootscripts[identifier]
|
||||
if !exists || fields[CacheTitle] != name {
|
||||
c.Bootscripts[identifier] = [CacheMaxfield]string{region, arch, owner, name}
|
||||
c.Modified = true
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveBootscript removes a bootscript from the cache
|
||||
func (c *ScalewayCache) RemoveBootscript(identifier string) {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
delete(c.Bootscripts, identifier)
|
||||
c.Modified = true
|
||||
}
|
||||
|
||||
// ClearBootscripts removes all bootscripts from the cache
|
||||
func (c *ScalewayCache) ClearBootscripts() {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
c.Bootscripts = make(map[string][CacheMaxfield]string)
|
||||
c.Modified = true
|
||||
}
|
||||
|
||||
// GetNbServers returns the number of servers in the cache
|
||||
func (c *ScalewayCache) GetNbServers() int {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
return len(c.Servers)
|
||||
}
|
||||
|
||||
// GetNbImages returns the number of images in the cache
|
||||
func (c *ScalewayCache) GetNbImages() int {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
return len(c.Images)
|
||||
}
|
||||
|
||||
// GetNbSnapshots returns the number of snapshots in the cache
|
||||
func (c *ScalewayCache) GetNbSnapshots() int {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
return len(c.Snapshots)
|
||||
}
|
||||
|
||||
// GetNbVolumes returns the number of volumes in the cache
|
||||
func (c *ScalewayCache) GetNbVolumes() int {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
return len(c.Volumes)
|
||||
}
|
||||
|
||||
// GetNbBootscripts returns the number of bootscripts in the cache
|
||||
func (c *ScalewayCache) GetNbBootscripts() int {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
return len(c.Bootscripts)
|
||||
}
|
|
@ -1,725 +0,0 @@
|
|||
// Copyright (C) 2015 Scaleway. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE.md file.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/pkg/namesgenerator"
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/moul/anonuuid"
|
||||
"github.com/scaleway/scaleway-cli/pkg/utils"
|
||||
"github.com/sirupsen/logrus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// ScalewayResolvedIdentifier represents a list of matching identifier for a specifier pattern
|
||||
type ScalewayResolvedIdentifier struct {
|
||||
// Identifiers holds matching identifiers
|
||||
Identifiers ScalewayResolverResults
|
||||
|
||||
// Needle is the criteria used to lookup identifiers
|
||||
Needle string
|
||||
}
|
||||
|
||||
// ScalewayImageInterface is an interface to multiple Scaleway items
|
||||
type ScalewayImageInterface struct {
|
||||
CreationDate time.Time
|
||||
Identifier string
|
||||
Name string
|
||||
Tag string
|
||||
VirtualSize uint64
|
||||
Public bool
|
||||
Type string
|
||||
Organization string
|
||||
Archs []string
|
||||
Region []string
|
||||
}
|
||||
|
||||
// ResolveGateway tries to resolve a server public ip address, else returns the input string, i.e. IPv4, hostname
|
||||
func ResolveGateway(api *ScalewayAPI, gateway string) (string, error) {
|
||||
if gateway == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Parses optional type prefix, i.e: "server:name" -> "name"
|
||||
_, gateway = parseNeedle(gateway)
|
||||
|
||||
servers, err := api.ResolveServer(gateway)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(servers) == 0 {
|
||||
return gateway, nil
|
||||
}
|
||||
|
||||
if len(servers) > 1 {
|
||||
return "", showResolverResults(gateway, servers)
|
||||
}
|
||||
|
||||
// if len(servers) == 1 {
|
||||
server, err := api.GetServer(servers[0].Identifier)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return server.PublicAddress.IP, nil
|
||||
}
|
||||
|
||||
// CreateVolumeFromHumanSize creates a volume on the API with a human readable size
|
||||
func CreateVolumeFromHumanSize(api *ScalewayAPI, size string) (*string, error) {
|
||||
bytes, err := humanize.ParseBytes(size)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var newVolume ScalewayVolumeDefinition
|
||||
newVolume.Name = size
|
||||
newVolume.Size = bytes
|
||||
newVolume.Type = "l_ssd"
|
||||
|
||||
volumeID, err := api.PostVolume(newVolume)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &volumeID, nil
|
||||
}
|
||||
|
||||
// VolumesFromSize returns a string of standard sized volumes from a given size
|
||||
func VolumesFromSize(size uint64) string {
|
||||
const DefaultVolumeSize float64 = 50000000000
|
||||
StdVolumeSizes := []struct {
|
||||
kind string
|
||||
capacity float64
|
||||
}{
|
||||
{"150G", 150000000000},
|
||||
{"100G", 100000000000},
|
||||
{"50G", 50000000000},
|
||||
}
|
||||
|
||||
RequiredSize := float64(size) - DefaultVolumeSize
|
||||
Volumes := ""
|
||||
for _, v := range StdVolumeSizes {
|
||||
q := RequiredSize / v.capacity
|
||||
r := math.Mod(RequiredSize, v.capacity)
|
||||
RequiredSize = r
|
||||
|
||||
if q > 0 {
|
||||
Volumes += strings.Repeat(v.kind+" ", int(q))
|
||||
}
|
||||
if r == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return strings.TrimSpace(Volumes)
|
||||
}
|
||||
|
||||
// fillIdentifierCache fills the cache by fetching from the API
|
||||
func fillIdentifierCache(api *ScalewayAPI, identifierType int) {
|
||||
log.Debugf("Filling the cache")
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(5)
|
||||
go func() {
|
||||
if identifierType&(IdentifierUnknown|IdentifierServer) > 0 {
|
||||
api.GetServers(true, 0)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
if identifierType&(IdentifierUnknown|IdentifierImage) > 0 {
|
||||
api.GetImages()
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
if identifierType&(IdentifierUnknown|IdentifierSnapshot) > 0 {
|
||||
api.GetSnapshots()
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
if identifierType&(IdentifierUnknown|IdentifierVolume) > 0 {
|
||||
api.GetVolumes()
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
if identifierType&(IdentifierUnknown|IdentifierBootscript) > 0 {
|
||||
api.GetBootscripts()
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// GetIdentifier returns a an identifier if the resolved needles only match one element, else, it exists the program
|
||||
func GetIdentifier(api *ScalewayAPI, needle string) (*ScalewayResolverResult, error) {
|
||||
idents, err := ResolveIdentifier(api, needle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(idents) == 1 {
|
||||
return &idents[0], nil
|
||||
}
|
||||
if len(idents) == 0 {
|
||||
return nil, fmt.Errorf("No such identifier: %s", needle)
|
||||
}
|
||||
|
||||
sort.Sort(idents)
|
||||
for _, identifier := range idents {
|
||||
// FIXME: also print the name
|
||||
fmt.Fprintf(os.Stderr, "- %s\n", identifier.Identifier)
|
||||
}
|
||||
return nil, fmt.Errorf("Too many candidates for %s (%d)", needle, len(idents))
|
||||
}
|
||||
|
||||
// ResolveIdentifier resolves needle provided by the user
|
||||
func ResolveIdentifier(api *ScalewayAPI, needle string) (ScalewayResolverResults, error) {
|
||||
idents, err := api.Cache.LookUpIdentifiers(needle)
|
||||
if err != nil {
|
||||
return idents, err
|
||||
}
|
||||
if len(idents) > 0 {
|
||||
return idents, nil
|
||||
}
|
||||
|
||||
identifierType, _ := parseNeedle(needle)
|
||||
fillIdentifierCache(api, identifierType)
|
||||
|
||||
return api.Cache.LookUpIdentifiers(needle)
|
||||
}
|
||||
|
||||
// ResolveIdentifiers resolves needles provided by the user
|
||||
func ResolveIdentifiers(api *ScalewayAPI, needles []string, out chan ScalewayResolvedIdentifier) {
|
||||
// first attempt, only lookup from the cache
|
||||
var unresolved []string
|
||||
for _, needle := range needles {
|
||||
idents, err := api.Cache.LookUpIdentifiers(needle)
|
||||
if err != nil {
|
||||
api.Logger.Fatalf("%s", err)
|
||||
}
|
||||
if len(idents) == 0 {
|
||||
unresolved = append(unresolved, needle)
|
||||
} else {
|
||||
out <- ScalewayResolvedIdentifier{
|
||||
Identifiers: idents,
|
||||
Needle: needle,
|
||||
}
|
||||
}
|
||||
}
|
||||
// fill the cache by fetching from the API and resolve missing identifiers
|
||||
if len(unresolved) > 0 {
|
||||
// compute identifierType:
|
||||
// if identifierType is the same for every unresolved needle,
|
||||
// we use it directly, else, we choose IdentifierUnknown to
|
||||
// fulfill every types of cache
|
||||
identifierType, _ := parseNeedle(unresolved[0])
|
||||
for _, needle := range unresolved {
|
||||
newIdentifierType, _ := parseNeedle(needle)
|
||||
if identifierType != newIdentifierType {
|
||||
identifierType = IdentifierUnknown
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// fill all the cache
|
||||
fillIdentifierCache(api, identifierType)
|
||||
|
||||
// lookup again in the cache
|
||||
for _, needle := range unresolved {
|
||||
idents, err := api.Cache.LookUpIdentifiers(needle)
|
||||
if err != nil {
|
||||
api.Logger.Fatalf("%s", err)
|
||||
}
|
||||
out <- ScalewayResolvedIdentifier{
|
||||
Identifiers: idents,
|
||||
Needle: needle,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
close(out)
|
||||
}
|
||||
|
||||
// InspectIdentifierResult is returned by `InspectIdentifiers` and contains the inspected `Object` with its `Type`
|
||||
type InspectIdentifierResult struct {
|
||||
Type int
|
||||
Object interface{}
|
||||
}
|
||||
|
||||
// InspectIdentifiers inspects identifiers concurrently
|
||||
func InspectIdentifiers(api *ScalewayAPI, ci chan ScalewayResolvedIdentifier, cj chan InspectIdentifierResult, arch string) {
|
||||
var wg sync.WaitGroup
|
||||
for {
|
||||
idents, ok := <-ci
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
idents.Identifiers = FilterImagesByArch(idents.Identifiers, arch)
|
||||
idents.Identifiers = FilterImagesByRegion(idents.Identifiers, api.Region)
|
||||
if len(idents.Identifiers) != 1 {
|
||||
if len(idents.Identifiers) == 0 {
|
||||
log.Errorf("Unable to resolve identifier %s", idents.Needle)
|
||||
} else {
|
||||
logrus.Fatal(showResolverResults(idents.Needle, idents.Identifiers))
|
||||
}
|
||||
} else {
|
||||
ident := idents.Identifiers[0]
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
var obj interface{}
|
||||
var err error
|
||||
|
||||
switch ident.Type {
|
||||
case IdentifierServer:
|
||||
obj, err = api.GetServer(ident.Identifier)
|
||||
case IdentifierImage:
|
||||
obj, err = api.GetImage(ident.Identifier)
|
||||
case IdentifierSnapshot:
|
||||
obj, err = api.GetSnapshot(ident.Identifier)
|
||||
case IdentifierVolume:
|
||||
obj, err = api.GetVolume(ident.Identifier)
|
||||
case IdentifierBootscript:
|
||||
obj, err = api.GetBootscript(ident.Identifier)
|
||||
}
|
||||
if err == nil && obj != nil {
|
||||
cj <- InspectIdentifierResult{
|
||||
Type: ident.Type,
|
||||
Object: obj,
|
||||
}
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
close(cj)
|
||||
}
|
||||
|
||||
// ConfigCreateServer represents the options sent to CreateServer and defining a server
|
||||
type ConfigCreateServer struct {
|
||||
ImageName string
|
||||
Name string
|
||||
Bootscript string
|
||||
Env string
|
||||
AdditionalVolumes string
|
||||
IP string
|
||||
CommercialType string
|
||||
DynamicIPRequired bool
|
||||
EnableIPV6 bool
|
||||
BootType string
|
||||
}
|
||||
|
||||
// Return offer from any of the product name or alternate names
|
||||
func OfferNameFromName(name string, products *ScalewayProductsServers) (*ProductServer, error) {
|
||||
offer, ok := products.Servers[name]
|
||||
if ok {
|
||||
return &offer, nil
|
||||
}
|
||||
|
||||
for _, v := range products.Servers {
|
||||
for _, alt := range v.AltNames {
|
||||
alt := strings.ToUpper(alt)
|
||||
if alt == name {
|
||||
return &v, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("Unknow commercial type: %v", name)
|
||||
}
|
||||
|
||||
// CreateServer creates a server using API based on typical server fields
|
||||
func CreateServer(api *ScalewayAPI, c *ConfigCreateServer) (string, error) {
|
||||
commercialType := os.Getenv("SCW_COMMERCIAL_TYPE")
|
||||
if commercialType == "" {
|
||||
commercialType = c.CommercialType
|
||||
}
|
||||
if len(commercialType) < 2 {
|
||||
return "", errors.New("Invalid commercial type")
|
||||
}
|
||||
|
||||
if c.BootType != "local" && c.BootType != "bootscript" {
|
||||
return "", errors.New("Invalid boot type")
|
||||
}
|
||||
|
||||
if c.Name == "" {
|
||||
c.Name = strings.Replace(namesgenerator.GetRandomName(0), "_", "-", -1)
|
||||
}
|
||||
|
||||
var server ScalewayServerDefinition
|
||||
|
||||
server.CommercialType = strings.ToUpper(commercialType)
|
||||
server.Volumes = make(map[string]string)
|
||||
server.DynamicIPRequired = &c.DynamicIPRequired
|
||||
server.EnableIPV6 = c.EnableIPV6
|
||||
server.BootType = c.BootType
|
||||
if commercialType == "" {
|
||||
return "", errors.New("You need to specify a commercial-type")
|
||||
}
|
||||
if c.IP != "" {
|
||||
if anonuuid.IsUUID(c.IP) == nil {
|
||||
server.PublicIP = c.IP
|
||||
} else {
|
||||
ips, err := api.GetIPS()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, ip := range ips.IPS {
|
||||
if ip.Address == c.IP {
|
||||
server.PublicIP = ip.ID
|
||||
break
|
||||
}
|
||||
}
|
||||
if server.PublicIP == "" {
|
||||
return "", fmt.Errorf("IP address %v not found", c.IP)
|
||||
}
|
||||
}
|
||||
}
|
||||
server.Tags = []string{}
|
||||
if c.Env != "" {
|
||||
server.Tags = strings.Split(c.Env, " ")
|
||||
}
|
||||
|
||||
products, err := api.GetProductsServers()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Unable to fetch products list from the Scaleway API: %v", err)
|
||||
}
|
||||
offer, err := OfferNameFromName(server.CommercialType, products)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Unknow commercial type %v: %v", server.CommercialType, err)
|
||||
}
|
||||
if offer.VolumesConstraint.MinSize > 0 && c.AdditionalVolumes == "" {
|
||||
c.AdditionalVolumes = VolumesFromSize(offer.VolumesConstraint.MinSize)
|
||||
log.Debugf("%s needs at least %s. Automatically creates the following volumes: %s",
|
||||
server.CommercialType, humanize.Bytes(offer.VolumesConstraint.MinSize), c.AdditionalVolumes)
|
||||
}
|
||||
if c.AdditionalVolumes != "" {
|
||||
volumes := strings.Split(c.AdditionalVolumes, " ")
|
||||
for i := range volumes {
|
||||
volumeID, err := CreateVolumeFromHumanSize(api, volumes[i])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
volumeIDx := fmt.Sprintf("%d", i+1)
|
||||
server.Volumes[volumeIDx] = *volumeID
|
||||
}
|
||||
}
|
||||
|
||||
arch := os.Getenv("SCW_TARGET_ARCH")
|
||||
if arch == "" {
|
||||
arch = offer.Arch
|
||||
}
|
||||
imageIdentifier := &ScalewayImageIdentifier{
|
||||
Arch: arch,
|
||||
}
|
||||
server.Name = c.Name
|
||||
inheritingVolume := false
|
||||
_, err = humanize.ParseBytes(c.ImageName)
|
||||
if err == nil {
|
||||
// Create a new root volume
|
||||
volumeID, errCreateVol := CreateVolumeFromHumanSize(api, c.ImageName)
|
||||
if errCreateVol != nil {
|
||||
return "", errCreateVol
|
||||
}
|
||||
server.Volumes["0"] = *volumeID
|
||||
} else {
|
||||
// Use an existing image
|
||||
inheritingVolume = true
|
||||
if anonuuid.IsUUID(c.ImageName) == nil {
|
||||
server.Image = &c.ImageName
|
||||
} else {
|
||||
imageIdentifier, err = api.GetImageID(c.ImageName, arch)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if imageIdentifier.Identifier != "" {
|
||||
server.Image = &imageIdentifier.Identifier
|
||||
} else {
|
||||
snapshotID, errGetSnapID := api.GetSnapshotID(c.ImageName)
|
||||
if errGetSnapID != nil {
|
||||
return "", errGetSnapID
|
||||
}
|
||||
snapshot, errGetSnap := api.GetSnapshot(snapshotID)
|
||||
if errGetSnap != nil {
|
||||
return "", errGetSnap
|
||||
}
|
||||
if snapshot.BaseVolume.Identifier == "" {
|
||||
return "", fmt.Errorf("snapshot %v does not have base volume", snapshot.Name)
|
||||
}
|
||||
server.Volumes["0"] = snapshot.BaseVolume.Identifier
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if c.Bootscript != "" {
|
||||
bootscript := ""
|
||||
|
||||
if anonuuid.IsUUID(c.Bootscript) == nil {
|
||||
bootscript = c.Bootscript
|
||||
} else {
|
||||
var errGetBootScript error
|
||||
|
||||
bootscript, errGetBootScript = api.GetBootscriptID(c.Bootscript, imageIdentifier.Arch)
|
||||
if errGetBootScript != nil {
|
||||
return "", errGetBootScript
|
||||
}
|
||||
}
|
||||
server.Bootscript = &bootscript
|
||||
}
|
||||
serverID, err := api.PostServer(server)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// For inherited volumes, we prefix the name with server hostname
|
||||
if inheritingVolume {
|
||||
createdServer, err := api.GetServer(serverID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
currentVolume := createdServer.Volumes["0"]
|
||||
|
||||
var volumePayload ScalewayVolumePutDefinition
|
||||
newName := fmt.Sprintf("%s-%s", createdServer.Hostname, currentVolume.Name)
|
||||
volumePayload.Name = &newName
|
||||
volumePayload.CreationDate = ¤tVolume.CreationDate
|
||||
volumePayload.Organization = ¤tVolume.Organization
|
||||
volumePayload.Server.Identifier = ¤tVolume.Server.Identifier
|
||||
volumePayload.Server.Name = ¤tVolume.Server.Name
|
||||
volumePayload.Identifier = ¤tVolume.Identifier
|
||||
volumePayload.Size = ¤tVolume.Size
|
||||
volumePayload.ModificationDate = ¤tVolume.ModificationDate
|
||||
volumePayload.ExportURI = ¤tVolume.ExportURI
|
||||
volumePayload.VolumeType = ¤tVolume.VolumeType
|
||||
|
||||
err = api.PutVolume(currentVolume.Identifier, volumePayload)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return serverID, nil
|
||||
}
|
||||
|
||||
// WaitForServerState asks API in a loop until a server matches a wanted state
|
||||
func WaitForServerState(api *ScalewayAPI, serverID string, targetState string) (*ScalewayServer, error) {
|
||||
var server *ScalewayServer
|
||||
var err error
|
||||
|
||||
var currentState string
|
||||
|
||||
for {
|
||||
server, err = api.GetServer(serverID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if currentState != server.State {
|
||||
log.Infof("Server changed state to '%s'", server.State)
|
||||
currentState = server.State
|
||||
}
|
||||
if server.State == targetState {
|
||||
break
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
return server, nil
|
||||
}
|
||||
|
||||
// WaitForServerReady wait for a server state to be running, then wait for the SSH port to be available
|
||||
func WaitForServerReady(api *ScalewayAPI, serverID, gateway string) (*ScalewayServer, error) {
|
||||
promise := make(chan bool)
|
||||
var server *ScalewayServer
|
||||
var err error
|
||||
var currentState string
|
||||
|
||||
go func() {
|
||||
defer close(promise)
|
||||
|
||||
for {
|
||||
server, err = api.GetServer(serverID)
|
||||
if err != nil {
|
||||
promise <- false
|
||||
return
|
||||
}
|
||||
if currentState != server.State {
|
||||
log.Infof("Server changed state to '%s'", server.State)
|
||||
currentState = server.State
|
||||
}
|
||||
if server.State == "running" {
|
||||
break
|
||||
}
|
||||
if server.State == "stopped" {
|
||||
err = fmt.Errorf("The server has been stopped")
|
||||
promise <- false
|
||||
return
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
if gateway == "" {
|
||||
ip := server.PublicAddress.IP
|
||||
if ip == "" && server.EnableIPV6 {
|
||||
ip = fmt.Sprintf("[%s]", server.IPV6.Address)
|
||||
}
|
||||
dest := fmt.Sprintf("%s:22", ip)
|
||||
log.Debugf("Waiting for server SSH port %s", dest)
|
||||
err = utils.WaitForTCPPortOpen(dest)
|
||||
if err != nil {
|
||||
promise <- false
|
||||
return
|
||||
}
|
||||
} else {
|
||||
dest := fmt.Sprintf("%s:22", gateway)
|
||||
log.Debugf("Waiting for server SSH port %s", dest)
|
||||
err = utils.WaitForTCPPortOpen(dest)
|
||||
if err != nil {
|
||||
promise <- false
|
||||
return
|
||||
}
|
||||
log.Debugf("Check for SSH port through the gateway: %s", server.PrivateIP)
|
||||
timeout := time.Tick(120 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case <-timeout:
|
||||
err = fmt.Errorf("Timeout: unable to ping %s", server.PrivateIP)
|
||||
goto OUT
|
||||
default:
|
||||
if utils.SSHExec("", server.PrivateIP, "root", 22, []string{
|
||||
"nc",
|
||||
"-z",
|
||||
"-w",
|
||||
"1",
|
||||
server.PrivateIP,
|
||||
"22",
|
||||
}, false, gateway, false) == nil {
|
||||
goto OUT
|
||||
}
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
}
|
||||
OUT:
|
||||
if err != nil {
|
||||
logrus.Info(err)
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
promise <- true
|
||||
}()
|
||||
|
||||
loop := 0
|
||||
for {
|
||||
select {
|
||||
case done := <-promise:
|
||||
utils.LogQuiet("\r \r")
|
||||
if !done {
|
||||
return nil, err
|
||||
}
|
||||
return server, nil
|
||||
case <-time.After(time.Millisecond * 100):
|
||||
utils.LogQuiet(fmt.Sprintf("\r%c\r", "-\\|/"[loop%4]))
|
||||
loop = loop + 1
|
||||
if loop == 5 {
|
||||
loop = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WaitForServerStopped wait for a server state to be stopped
|
||||
func WaitForServerStopped(api *ScalewayAPI, serverID string) (*ScalewayServer, error) {
|
||||
server, err := WaitForServerState(api, serverID, "stopped")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return server, nil
|
||||
}
|
||||
|
||||
// ByCreationDate sorts images by CreationDate field
|
||||
type ByCreationDate []ScalewayImageInterface
|
||||
|
||||
func (a ByCreationDate) Len() int { return len(a) }
|
||||
func (a ByCreationDate) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a ByCreationDate) Less(i, j int) bool { return a[j].CreationDate.Before(a[i].CreationDate) }
|
||||
|
||||
// StartServer start a server based on its needle, can optionaly block while server is booting
|
||||
func StartServer(api *ScalewayAPI, needle string, wait bool) error {
|
||||
server, err := api.GetServerID(needle)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = api.PostServerAction(server, "poweron"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if wait {
|
||||
_, err = WaitForServerReady(api, server, "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to wait for server %s to be ready, %v", needle, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartServerOnce wraps StartServer for golang channel
|
||||
func StartServerOnce(api *ScalewayAPI, needle string, wait bool, successChan chan string, errChan chan error) {
|
||||
err := StartServer(api, needle, wait)
|
||||
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
successChan <- needle
|
||||
}
|
||||
|
||||
// DeleteServerForce tries to delete a server using multiple ways
|
||||
func (a *ScalewayAPI) DeleteServerForce(serverID string) error {
|
||||
// FIXME: also delete attached volumes and ip address
|
||||
// FIXME: call delete and stop -t in parallel to speed up process
|
||||
err := a.DeleteServer(serverID)
|
||||
if err == nil {
|
||||
logrus.Infof("Server '%s' successfully deleted", serverID)
|
||||
return nil
|
||||
}
|
||||
|
||||
err = a.PostServerAction(serverID, "terminate")
|
||||
if err == nil {
|
||||
logrus.Infof("Server '%s' successfully terminated", serverID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// FIXME: retry in a loop until timeout or Control+C
|
||||
logrus.Errorf("Failed to delete server %s", serverID)
|
||||
logrus.Errorf("Try to run 'scw rm -f %s' later", serverID)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetSSHFingerprintFromServer returns an array which containts ssh-host-fingerprints
|
||||
func (a *ScalewayAPI) GetSSHFingerprintFromServer(serverID string) []string {
|
||||
ret := []string{}
|
||||
|
||||
if value, err := a.GetUserdata(serverID, "ssh-host-fingerprints", false); err == nil {
|
||||
PublicKeys := strings.Split(string(*value), "\n")
|
||||
for i := range PublicKeys {
|
||||
if fingerprint, err := utils.SSHGetFingerprint([]byte(PublicKeys[i])); err == nil {
|
||||
ret = append(ret, fingerprint)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
// Copyright (C) 2015 Scaleway. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE.md file.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Logger handles logging concerns for the Scaleway API SDK
|
||||
type Logger interface {
|
||||
LogHTTP(*http.Request)
|
||||
Fatalf(format string, v ...interface{})
|
||||
Debugf(format string, v ...interface{})
|
||||
Infof(format string, v ...interface{})
|
||||
Warnf(format string, v ...interface{})
|
||||
}
|
||||
|
||||
// NewDefaultLogger returns a logger which is configured for stdout
|
||||
func NewDefaultLogger() Logger {
|
||||
return &defaultLogger{
|
||||
Logger: log.New(os.Stdout, "", log.LstdFlags),
|
||||
}
|
||||
}
|
||||
|
||||
type defaultLogger struct {
|
||||
*log.Logger
|
||||
}
|
||||
|
||||
func (l *defaultLogger) LogHTTP(r *http.Request) {
|
||||
l.Printf("%s %s\n", r.Method, r.URL.RawPath)
|
||||
}
|
||||
|
||||
func (l *defaultLogger) Fatalf(format string, v ...interface{}) {
|
||||
l.Printf("[FATAL] %s\n", fmt.Sprintf(format, v))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func (l *defaultLogger) Debugf(format string, v ...interface{}) {
|
||||
l.Printf("[DEBUG] %s\n", fmt.Sprintf(format, v))
|
||||
}
|
||||
|
||||
func (l *defaultLogger) Infof(format string, v ...interface{}) {
|
||||
l.Printf("[INFO ] %s\n", fmt.Sprintf(format, v))
|
||||
}
|
||||
|
||||
func (l *defaultLogger) Warnf(format string, v ...interface{}) {
|
||||
l.Printf("[WARN ] %s\n", fmt.Sprintf(format, v))
|
||||
}
|
||||
|
||||
type disableLogger struct {
|
||||
}
|
||||
|
||||
// NewDisableLogger returns a logger which is configured to do nothing
|
||||
func NewDisableLogger() Logger {
|
||||
return &disableLogger{}
|
||||
}
|
||||
|
||||
func (d *disableLogger) LogHTTP(r *http.Request) {
|
||||
}
|
||||
|
||||
func (d *disableLogger) Fatalf(format string, v ...interface{}) {
|
||||
panic(fmt.Sprintf(format, v))
|
||||
}
|
||||
|
||||
func (d *disableLogger) Debugf(format string, v ...interface{}) {
|
||||
}
|
||||
|
||||
func (d *disableLogger) Infof(format string, v ...interface{}) {
|
||||
}
|
||||
|
||||
func (d *disableLogger) Warnf(format string, v ...interface{}) {
|
||||
}
|
|
@ -1,122 +0,0 @@
|
|||
package sshcommand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Command contains settings to build a ssh command
|
||||
type Command struct {
|
||||
Host string
|
||||
User string
|
||||
Port int
|
||||
SSHOptions []string
|
||||
Gateway *Command
|
||||
Command []string
|
||||
Debug bool
|
||||
NoEscapeCommand bool
|
||||
SkipHostKeyChecking bool
|
||||
Quiet bool
|
||||
AllocateTTY bool
|
||||
EnableSSHKeyForwarding bool
|
||||
|
||||
isGateway bool
|
||||
}
|
||||
|
||||
// New returns a minimal Command
|
||||
func New(host string) *Command {
|
||||
return &Command{
|
||||
Host: host,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Command) applyDefaults() {
|
||||
if strings.Contains(c.Host, "@") {
|
||||
parts := strings.Split(c.Host, "@")
|
||||
c.User = parts[0]
|
||||
c.Host = parts[1]
|
||||
}
|
||||
|
||||
if c.Port == 0 {
|
||||
c.Port = 22
|
||||
}
|
||||
|
||||
if c.isGateway {
|
||||
c.SSHOptions = []string{"-W", "%h:%p"}
|
||||
}
|
||||
}
|
||||
|
||||
// Slice returns an execve compatible slice of arguments
|
||||
func (c *Command) Slice() []string {
|
||||
c.applyDefaults()
|
||||
|
||||
slice := []string{"ssh"}
|
||||
|
||||
if c.EnableSSHKeyForwarding {
|
||||
slice = append(slice, "-A")
|
||||
}
|
||||
|
||||
if c.Quiet {
|
||||
slice = append(slice, "-q")
|
||||
}
|
||||
|
||||
if c.SkipHostKeyChecking {
|
||||
slice = append(slice, "-o", "UserKnownHostsFile=/dev/null", "-o", "StrictHostKeyChecking=no")
|
||||
}
|
||||
|
||||
if len(c.SSHOptions) > 0 {
|
||||
slice = append(slice, c.SSHOptions...)
|
||||
}
|
||||
|
||||
if c.Gateway != nil {
|
||||
c.Gateway.isGateway = true
|
||||
slice = append(slice, "-o", "ProxyCommand="+c.Gateway.String())
|
||||
}
|
||||
|
||||
if c.User != "" {
|
||||
slice = append(slice, "-l", c.User)
|
||||
}
|
||||
|
||||
slice = append(slice, c.Host)
|
||||
|
||||
if c.AllocateTTY {
|
||||
slice = append(slice, "-t", "-t")
|
||||
}
|
||||
|
||||
slice = append(slice, "-p", fmt.Sprintf("%d", c.Port))
|
||||
if len(c.Command) > 0 {
|
||||
slice = append(slice, "--", "/bin/sh", "-e")
|
||||
if c.Debug {
|
||||
slice = append(slice, "-x")
|
||||
}
|
||||
slice = append(slice, "-c")
|
||||
|
||||
var escapedCommand []string
|
||||
if c.NoEscapeCommand {
|
||||
escapedCommand = c.Command
|
||||
} else {
|
||||
escapedCommand = []string{}
|
||||
for _, part := range c.Command {
|
||||
escapedCommand = append(escapedCommand, fmt.Sprintf("%q", part))
|
||||
}
|
||||
}
|
||||
slice = append(slice, fmt.Sprintf("%q", strings.Join(escapedCommand, " ")))
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
slice[len(slice)-1] = slice[len(slice)-1] + " " // Why ?
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
// String returns a copy-pasteable command, useful for debugging
|
||||
func (c *Command) String() string {
|
||||
slice := c.Slice()
|
||||
for i := range slice {
|
||||
quoted := fmt.Sprintf("%q", slice[i])
|
||||
if strings.Contains(slice[i], " ") || len(quoted) != len(slice[i])+2 {
|
||||
slice[i] = quoted
|
||||
}
|
||||
}
|
||||
return strings.Join(slice, " ")
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
// Copyright (C) 2015 Scaleway. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE.md file.
|
||||
|
||||
// Package utils contains logquiet
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// LogQuietStruct is a struct to store information about quiet state
|
||||
type LogQuietStruct struct {
|
||||
quiet bool
|
||||
}
|
||||
|
||||
var instanceQuiet LogQuietStruct
|
||||
|
||||
// Quiet enable or disable quiet
|
||||
func Quiet(option bool) {
|
||||
instanceQuiet.quiet = option
|
||||
}
|
||||
|
||||
// LogQuiet Displays info if quiet is activated
|
||||
func LogQuiet(str string) {
|
||||
if !instanceQuiet.quiet {
|
||||
fmt.Fprintf(os.Stderr, "%s", str)
|
||||
}
|
||||
}
|
|
@ -1,253 +0,0 @@
|
|||
// Copyright (C) 2015 Scaleway. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE.md file.
|
||||
|
||||
// scw helpers
|
||||
|
||||
// Package utils contains helpers
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/moul/gotty-client"
|
||||
"github.com/scaleway/scaleway-cli/pkg/sshcommand"
|
||||
"github.com/sirupsen/logrus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// SpawnRedirection is used to redirects the fluxes
|
||||
type SpawnRedirection struct {
|
||||
Stdin io.Reader
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
}
|
||||
|
||||
// SSHExec executes a command over SSH and redirects file-descriptors
|
||||
func SSHExec(publicIPAddress, privateIPAddress, user string, port int, command []string, checkConnection bool, gateway string, enableSSHKeyForwarding bool) error {
|
||||
gatewayUser := "root"
|
||||
gatewayIPAddress := gateway
|
||||
if strings.Contains(gateway, "@") {
|
||||
parts := strings.Split(gatewayIPAddress, "@")
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("gateway: must be like root@IP")
|
||||
}
|
||||
gatewayUser = parts[0]
|
||||
gatewayIPAddress = parts[1]
|
||||
gateway = gatewayUser + "@" + gatewayIPAddress
|
||||
}
|
||||
|
||||
if publicIPAddress == "" && gatewayIPAddress == "" {
|
||||
return errors.New("server does not have public IP")
|
||||
}
|
||||
if privateIPAddress == "" && gatewayIPAddress != "" {
|
||||
return errors.New("server does not have private IP")
|
||||
}
|
||||
|
||||
if checkConnection {
|
||||
useGateway := gatewayIPAddress != ""
|
||||
if useGateway && !IsTCPPortOpen(fmt.Sprintf("%s:22", gatewayIPAddress)) {
|
||||
return errors.New("gateway is not available, try again later")
|
||||
}
|
||||
if !useGateway && !IsTCPPortOpen(fmt.Sprintf("%s:%d", publicIPAddress, port)) {
|
||||
return errors.New("server is not ready, try again later")
|
||||
}
|
||||
}
|
||||
|
||||
sshCommand := NewSSHExecCmd(publicIPAddress, privateIPAddress, user, port, isatty.IsTerminal(os.Stdin.Fd()), command, gateway, enableSSHKeyForwarding)
|
||||
|
||||
log.Debugf("Executing: %s", sshCommand)
|
||||
|
||||
spawn := exec.Command("ssh", sshCommand.Slice()[1:]...)
|
||||
spawn.Stdout = os.Stdout
|
||||
spawn.Stdin = os.Stdin
|
||||
spawn.Stderr = os.Stderr
|
||||
return spawn.Run()
|
||||
}
|
||||
|
||||
// NewSSHExecCmd computes execve compatible arguments to run a command via ssh
|
||||
func NewSSHExecCmd(publicIPAddress, privateIPAddress, user string, port int, allocateTTY bool, command []string, gatewayIPAddress string, enableSSHKeyForwarding bool) *sshcommand.Command {
|
||||
quiet := os.Getenv("DEBUG") != "1"
|
||||
secureExec := os.Getenv("SCW_SECURE_EXEC") == "1"
|
||||
sshCommand := &sshcommand.Command{
|
||||
AllocateTTY: allocateTTY,
|
||||
Command: command,
|
||||
Host: publicIPAddress,
|
||||
Quiet: quiet,
|
||||
SkipHostKeyChecking: !secureExec,
|
||||
User: user,
|
||||
NoEscapeCommand: true,
|
||||
Port: port,
|
||||
EnableSSHKeyForwarding: enableSSHKeyForwarding,
|
||||
}
|
||||
if gatewayIPAddress != "" {
|
||||
sshCommand.Host = privateIPAddress
|
||||
sshCommand.Gateway = &sshcommand.Command{
|
||||
Host: gatewayIPAddress,
|
||||
SkipHostKeyChecking: !secureExec,
|
||||
AllocateTTY: allocateTTY,
|
||||
Quiet: quiet,
|
||||
User: user,
|
||||
Port: port,
|
||||
}
|
||||
}
|
||||
|
||||
return sshCommand
|
||||
}
|
||||
|
||||
// GeneratingAnSSHKey generates an SSH key
|
||||
func GeneratingAnSSHKey(cfg SpawnRedirection, path string, name string) (string, error) {
|
||||
args := []string{
|
||||
"-t",
|
||||
"rsa",
|
||||
"-b",
|
||||
"4096",
|
||||
"-f",
|
||||
filepath.Join(path, name),
|
||||
"-N",
|
||||
"",
|
||||
"-C",
|
||||
"",
|
||||
}
|
||||
log.Infof("Executing commands %v", args)
|
||||
spawn := exec.Command("ssh-keygen", args...)
|
||||
spawn.Stdout = cfg.Stdout
|
||||
spawn.Stdin = cfg.Stdin
|
||||
spawn.Stderr = cfg.Stderr
|
||||
return args[5], spawn.Run()
|
||||
}
|
||||
|
||||
// WaitForTCPPortOpen calls IsTCPPortOpen in a loop
|
||||
func WaitForTCPPortOpen(dest string) error {
|
||||
for {
|
||||
if IsTCPPortOpen(dest) {
|
||||
break
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsTCPPortOpen returns true if a TCP communication with "host:port" can be initialized
|
||||
func IsTCPPortOpen(dest string) bool {
|
||||
conn, err := net.DialTimeout("tcp", dest, time.Duration(2000)*time.Millisecond)
|
||||
if err == nil {
|
||||
defer conn.Close()
|
||||
}
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// TruncIf ensures the input string does not exceed max size if cond is met
|
||||
func TruncIf(str string, max int, cond bool) string {
|
||||
if cond && len(str) > max {
|
||||
return str[:max]
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// Wordify convert complex name to a single word without special shell characters
|
||||
func Wordify(str string) string {
|
||||
str = regexp.MustCompile(`[^a-zA-Z0-9-]`).ReplaceAllString(str, "_")
|
||||
str = regexp.MustCompile(`__+`).ReplaceAllString(str, "_")
|
||||
str = strings.Trim(str, "_")
|
||||
return str
|
||||
}
|
||||
|
||||
// PathToTARPathparts returns the two parts of a unix path
|
||||
func PathToTARPathparts(fullPath string) (string, string) {
|
||||
fullPath = strings.TrimRight(fullPath, "/")
|
||||
return path.Dir(fullPath), path.Base(fullPath)
|
||||
}
|
||||
|
||||
// RemoveDuplicates transforms an array into a unique array
|
||||
func RemoveDuplicates(elements []string) []string {
|
||||
encountered := map[string]bool{}
|
||||
|
||||
// Create a map of all unique elements.
|
||||
for v := range elements {
|
||||
encountered[elements[v]] = true
|
||||
}
|
||||
|
||||
// Place all keys from the map into a slice.
|
||||
result := []string{}
|
||||
for key := range encountered {
|
||||
result = append(result, key)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// AttachToSerial tries to connect to server serial using 'gotty-client' and fallback with a help message
|
||||
func AttachToSerial(serverID, apiToken, url string) (*gottyclient.Client, chan bool, error) {
|
||||
gottyURL := os.Getenv("SCW_GOTTY_URL")
|
||||
if gottyURL == "" {
|
||||
gottyURL = url
|
||||
}
|
||||
URL := fmt.Sprintf("%s?arg=%s&arg=%s", gottyURL, apiToken, serverID)
|
||||
|
||||
logrus.Debug("Connection to ", URL)
|
||||
gottycli, err := gottyclient.NewClient(URL)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if os.Getenv("SCW_TLSVERIFY") == "0" {
|
||||
gottycli.SkipTLSVerify = true
|
||||
}
|
||||
|
||||
gottycli.UseProxyFromEnv = true
|
||||
|
||||
if err = gottycli.Connect(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
done := make(chan bool)
|
||||
|
||||
fmt.Println("You are connected, type 'Ctrl+q' to quit.")
|
||||
go func() {
|
||||
gottycli.Loop()
|
||||
gottycli.Close()
|
||||
done <- true
|
||||
}()
|
||||
return gottycli, done, nil
|
||||
}
|
||||
|
||||
func rfc4716hex(data []byte) string {
|
||||
fingerprint := ""
|
||||
|
||||
for i := 0; i < len(data); i++ {
|
||||
fingerprint = fmt.Sprintf("%s%0.2x", fingerprint, data[i])
|
||||
if i != len(data)-1 {
|
||||
fingerprint = fingerprint + ":"
|
||||
}
|
||||
}
|
||||
return fingerprint
|
||||
}
|
||||
|
||||
// SSHGetFingerprint returns the fingerprint of an SSH key
|
||||
func SSHGetFingerprint(key []byte) (string, error) {
|
||||
publicKey, comment, _, _, err := ssh.ParseAuthorizedKey(key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
switch reflect.TypeOf(publicKey).String() {
|
||||
case "*ssh.rsaPublicKey", "*ssh.dsaPublicKey", "*ssh.ecdsaPublicKey":
|
||||
md5sum := md5.Sum(publicKey.Marshal())
|
||||
return publicKey.Type() + " " + rfc4716hex(md5sum[:]) + " " + comment, nil
|
||||
default:
|
||||
return "", errors.New("Can't handle this key")
|
||||
}
|
||||
}
|
|
@ -176,7 +176,7 @@
|
|||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2013-2017 Docker, Inc.
|
||||
Copyright 2019 Scaleway.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue