feat(builder): bump scaleway to new sdk (#9902)

This commit is contained in:
Patrik 2020-09-09 10:27:48 +02:00 committed by GitHub
parent 0719f906d4
commit f8e31bff12
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
138 changed files with 10385 additions and 13704 deletions

View File

@ -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

View File

@ -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)

View File

@ -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")},
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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))
}
}
}

View File

@ -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)

View File

@ -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
}

View File

@ -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
}

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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
```

View File

@ -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[:])
}

View File

@ -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
)

View File

@ -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
)

View File

@ -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)
*/

View File

@ -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[:])
}

View File

@ -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() {}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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";

View File

@ -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
}

1952
vendor/github.com/docker/docker/AUTHORS generated vendored

File diff suppressed because it is too large Load Diff

View File

@ -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.

View File

@ -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 ./...

View File

@ -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>

View File

@ -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

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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:], ",")
}

View File

@ -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()
}

View File

@ -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))
}

View File

@ -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

View File

@ -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###,##" => "12345,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))
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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...)
}

View File

@ -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

View File

@ -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 ./...

View File

@ -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>

View File

@ -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.

View File

@ -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.

View File

@ -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
}

View File

@ -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()
}

View File

@ -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,
}
}

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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 }

View File

@ -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")
}

View File

@ -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
}

View File

@ -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

View File

@ -1 +0,0 @@
github.com/moul/anonuuid

View File

@ -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"
}

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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))
}

View File

@ -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

View File

@ -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"
}

View File

@ -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

View File

@ -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"]

View File

@ -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.

View File

@ -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.

View File

@ -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 .

View File

@ -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)

View File

@ -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
}

View File

@ -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")
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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.

View File

@ -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
}

View File

@ -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
}

View File

@ -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.

View File

@ -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

View File

@ -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)
}

View File

@ -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 = &currentVolume.CreationDate
volumePayload.Organization = &currentVolume.Organization
volumePayload.Server.Identifier = &currentVolume.Server.Identifier
volumePayload.Server.Name = &currentVolume.Server.Name
volumePayload.Identifier = &currentVolume.Identifier
volumePayload.Size = &currentVolume.Size
volumePayload.ModificationDate = &currentVolume.ModificationDate
volumePayload.ExportURI = &currentVolume.ExportURI
volumePayload.VolumeType = &currentVolume.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
}

View File

@ -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{}) {
}

View File

@ -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, " ")
}

View File

@ -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)
}
}

View File

@ -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")
}
}

View File

@ -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