Merge pull request #4619 from zhuzhih2017/master
Add packer plugin which support to create alicloud ecs image
This commit is contained in:
commit
b1bcf62ae1
|
@ -0,0 +1,83 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
)
|
||||
|
||||
// Config of alicloud
|
||||
type AlicloudAccessConfig struct {
|
||||
AlicloudAccessKey string `mapstructure:"access_key"`
|
||||
AlicloudSecretKey string `mapstructure:"secret_key"`
|
||||
AlicloudRegion string `mapstructure:"region"`
|
||||
AlicloudSkipValidation bool `mapstructure:"skip_region_validation"`
|
||||
}
|
||||
|
||||
// Client for AlicloudClient
|
||||
func (c *AlicloudAccessConfig) Client() (*ecs.Client, error) {
|
||||
if err := c.loadAndValidate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client := ecs.NewClient(c.AlicloudAccessKey, c.AlicloudSecretKey)
|
||||
client.SetBusinessInfo("Packer")
|
||||
if _, err := client.DescribeRegions(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (c *AlicloudAccessConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
var errs []error
|
||||
if err := c.Config(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
if c.AlicloudRegion != "" && !c.AlicloudSkipValidation {
|
||||
if c.validateRegion() != nil {
|
||||
errs = append(errs, fmt.Errorf("Unknown alicloud region: %s", c.AlicloudRegion))
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *AlicloudAccessConfig) Config() error {
|
||||
if c.AlicloudAccessKey == "" {
|
||||
c.AlicloudAccessKey = os.Getenv("ALICLOUD_ACCESS_KEY")
|
||||
}
|
||||
if c.AlicloudSecretKey == "" {
|
||||
c.AlicloudSecretKey = os.Getenv("ALICLOUD_SECRET_KEY")
|
||||
}
|
||||
if c.AlicloudAccessKey == "" || c.AlicloudSecretKey == "" {
|
||||
return fmt.Errorf("ALICLOUD_ACCESS_KEY and ALICLOUD_SECRET_KEY must be set in template file or environment variables.")
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (c *AlicloudAccessConfig) loadAndValidate() error {
|
||||
if err := c.validateRegion(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *AlicloudAccessConfig) validateRegion() error {
|
||||
|
||||
for _, valid := range common.ValidRegions {
|
||||
if c.AlicloudRegion == string(valid) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("Not a valid alicloud region: %s", c.AlicloudRegion)
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testAlicloudAccessConfig() *AlicloudAccessConfig {
|
||||
return &AlicloudAccessConfig{
|
||||
AlicloudAccessKey: "ak",
|
||||
AlicloudSecretKey: "acs",
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestAlicloudAccessConfigPrepareRegion(t *testing.T) {
|
||||
c := testAlicloudAccessConfig()
|
||||
c.AlicloudRegion = ""
|
||||
if err := c.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err)
|
||||
}
|
||||
|
||||
c.AlicloudRegion = "cn-beijing-3"
|
||||
if err := c.Prepare(nil); err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
c.AlicloudRegion = "cn-beijing"
|
||||
if err := c.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err)
|
||||
}
|
||||
|
||||
c.AlicloudRegion = "unknown"
|
||||
if err := c.Prepare(nil); err == nil {
|
||||
t.Fatalf("should have err")
|
||||
}
|
||||
|
||||
c.AlicloudRegion = "unknown"
|
||||
c.AlicloudSkipValidation = true
|
||||
if err := c.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err)
|
||||
}
|
||||
c.AlicloudSkipValidation = false
|
||||
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type Artifact struct {
|
||||
// A map of regions to alicloud image IDs.
|
||||
AlicloudImages map[string]string
|
||||
|
||||
// BuilderId is the unique ID for the builder that created this alicloud image
|
||||
BuilderIdValue string
|
||||
|
||||
// Alcloud connection for performing API stuff.
|
||||
Client *ecs.Client
|
||||
}
|
||||
|
||||
func (a *Artifact) BuilderId() string {
|
||||
return a.BuilderIdValue
|
||||
}
|
||||
|
||||
func (*Artifact) Files() []string {
|
||||
// We have no files
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Artifact) Id() string {
|
||||
parts := make([]string, 0, len(a.AlicloudImages))
|
||||
for region, ecsImageId := range a.AlicloudImages {
|
||||
parts = append(parts, fmt.Sprintf("%s:%s", region, ecsImageId))
|
||||
}
|
||||
|
||||
sort.Strings(parts)
|
||||
return strings.Join(parts, ",")
|
||||
}
|
||||
|
||||
func (a *Artifact) String() string {
|
||||
alicloudImageStrings := make([]string, 0, len(a.AlicloudImages))
|
||||
for region, id := range a.AlicloudImages {
|
||||
single := fmt.Sprintf("%s: %s", region, id)
|
||||
alicloudImageStrings = append(alicloudImageStrings, single)
|
||||
}
|
||||
|
||||
sort.Strings(alicloudImageStrings)
|
||||
return fmt.Sprintf("Alicloud images were created:\n\n%s", strings.Join(alicloudImageStrings, "\n"))
|
||||
}
|
||||
|
||||
func (a *Artifact) State(name string) interface{} {
|
||||
switch name {
|
||||
case "atlas.artifact.metadata":
|
||||
return a.stateAtlasMetadata()
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Artifact) Destroy() error {
|
||||
errors := make([]error, 0)
|
||||
|
||||
for region, imageId := range a.AlicloudImages {
|
||||
log.Printf("Delete alicloud image ID (%s) from region (%s)", imageId, region)
|
||||
|
||||
// Get alicloud image metadata
|
||||
images, _, err := a.Client.DescribeImages(&ecs.DescribeImagesArgs{
|
||||
RegionId: common.Region(region),
|
||||
ImageId: imageId})
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
if len(images) == 0 {
|
||||
err := fmt.Errorf("Error retrieving details for alicloud image(%s), no alicloud images found", imageId)
|
||||
errors = append(errors, err)
|
||||
continue
|
||||
}
|
||||
//Unshared the shared account before destroy
|
||||
sharePermissions, err := a.Client.DescribeImageSharePermission(&ecs.ModifyImageSharePermissionArgs{RegionId: common.Region(region), ImageId: imageId})
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
accountsNumber := len(sharePermissions.Accounts.Account)
|
||||
if accountsNumber > 0 {
|
||||
accounts := make([]string, accountsNumber)
|
||||
for index, account := range sharePermissions.Accounts.Account {
|
||||
accounts[index] = account.AliyunId
|
||||
}
|
||||
err := a.Client.ModifyImageSharePermission(&ecs.ModifyImageSharePermissionArgs{
|
||||
|
||||
RegionId: common.Region(region),
|
||||
ImageId: imageId,
|
||||
RemoveAccount: accounts,
|
||||
})
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
}
|
||||
// Delete alicloud images
|
||||
if err := a.Client.DeleteImage(common.Region(region), imageId); err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
//Delete the snapshot of this images
|
||||
for _, diskDevices := range images[0].DiskDeviceMappings.DiskDeviceMapping {
|
||||
if err := a.Client.DeleteSnapshot(diskDevices.SnapshotId); err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if len(errors) > 0 {
|
||||
if len(errors) == 1 {
|
||||
return errors[0]
|
||||
} else {
|
||||
return &packer.MultiError{Errors: errors}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Artifact) stateAtlasMetadata() interface{} {
|
||||
metadata := make(map[string]string)
|
||||
for region, imageId := range a.AlicloudImages {
|
||||
k := fmt.Sprintf("region.%s", region)
|
||||
metadata[k] = imageId
|
||||
}
|
||||
|
||||
return metadata
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
func TestArtifact_Impl(t *testing.T) {
|
||||
var _ packer.Artifact = new(Artifact)
|
||||
}
|
||||
|
||||
func TestArtifactId(t *testing.T) {
|
||||
expected := `east:foo,west:bar`
|
||||
|
||||
ecsImages := make(map[string]string)
|
||||
ecsImages["east"] = "foo"
|
||||
ecsImages["west"] = "bar"
|
||||
|
||||
a := &Artifact{
|
||||
AlicloudImages: ecsImages,
|
||||
}
|
||||
|
||||
result := a.Id()
|
||||
if result != expected {
|
||||
t.Fatalf("bad: %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactState_atlasMetadata(t *testing.T) {
|
||||
a := &Artifact{
|
||||
AlicloudImages: map[string]string{
|
||||
"east": "foo",
|
||||
"west": "bar",
|
||||
},
|
||||
}
|
||||
|
||||
actual := a.State("atlas.artifact.metadata")
|
||||
expected := map[string]string{
|
||||
"region.east": "foo",
|
||||
"region.west": "bar",
|
||||
}
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad: %#v", actual)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,223 @@
|
|||
// The alicloud contains a packer.Builder implementation that
|
||||
// builds ecs images for alicloud.
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
// The unique ID for this builder
|
||||
const BuilderId = "alibaba.alicloud"
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
AlicloudAccessConfig `mapstructure:",squash"`
|
||||
AlicloudImageConfig `mapstructure:",squash"`
|
||||
RunConfig `mapstructure:",squash"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
type Builder struct {
|
||||
config Config
|
||||
runner multistep.Runner
|
||||
}
|
||||
|
||||
type InstanceNetWork string
|
||||
|
||||
const (
|
||||
ClassicNet = InstanceNetWork("classic")
|
||||
VpcNet = InstanceNetWork("vpc")
|
||||
ALICLOUD_DEFAULT_SHORT_TIMEOUT = 180
|
||||
ALICLOUD_DEFAULT_TIMEOUT = 1800
|
||||
ALICLOUD_DEFAULT_LONG_TIMEOUT = 3600
|
||||
)
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||
Interpolate: true,
|
||||
InterpolateContext: &b.config.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
Exclude: []string{
|
||||
"run_command",
|
||||
},
|
||||
},
|
||||
}, raws...)
|
||||
b.config.ctx.EnableEnv = true
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Accumulate any errors
|
||||
var errs *packer.MultiError
|
||||
errs = packer.MultiErrorAppend(errs, b.config.AlicloudAccessConfig.Prepare(&b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.AlicloudImageConfig.Prepare(&b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...)
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
log.Println(common.ScrubConfig(b.config, b.config.AlicloudAccessKey, b.config.AlicloudSecretKey))
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
|
||||
client, err := b.config.Client()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", b.config)
|
||||
state.Put("client", client)
|
||||
state.Put("hook", hook)
|
||||
state.Put("ui", ui)
|
||||
state.Put("networktype", b.chooseNetworkType())
|
||||
var steps []multistep.Step
|
||||
|
||||
// Build the steps
|
||||
steps = []multistep.Step{
|
||||
&stepPreValidate{
|
||||
AlicloudDestImageName: b.config.AlicloudImageName,
|
||||
ForceDelete: b.config.AlicloudImageForceDetele,
|
||||
},
|
||||
&stepCheckAlicloudSourceImage{
|
||||
SourceECSImageId: b.config.AlicloudSourceImage,
|
||||
},
|
||||
&StepConfigAlicloudKeyPair{
|
||||
Debug: b.config.PackerDebug,
|
||||
KeyPairName: b.config.SSHKeyPairName,
|
||||
PrivateKeyFile: b.config.Comm.SSHPrivateKey,
|
||||
TemporaryKeyPairName: b.config.TemporaryKeyPairName,
|
||||
SSHAgentAuth: b.config.Comm.SSHAgentAuth,
|
||||
//DebugKeyPath: b.config.Com
|
||||
RegionId: b.config.AlicloudRegion,
|
||||
},
|
||||
}
|
||||
if b.chooseNetworkType() == VpcNet {
|
||||
steps = append(steps,
|
||||
&stepConfigAlicloudVPC{
|
||||
VpcId: b.config.VpcId,
|
||||
CidrBlock: b.config.CidrBlock,
|
||||
VpcName: b.config.VpcName,
|
||||
},
|
||||
&stepConfigAlicloudVSwitch{
|
||||
VSwitchId: b.config.VSwitchId,
|
||||
ZoneId: b.config.ZoneId,
|
||||
CidrBlock: b.config.CidrBlock,
|
||||
VSwitchName: b.config.VSwitchName,
|
||||
})
|
||||
}
|
||||
steps = append(steps,
|
||||
&stepConfigAlicloudSecurityGroup{
|
||||
SecurityGroupId: b.config.SecurityGroupId,
|
||||
SecurityGroupName: b.config.SecurityGroupId,
|
||||
RegionId: b.config.AlicloudRegion,
|
||||
},
|
||||
&stepCreateAlicloudInstance{
|
||||
IOOptimized: b.config.IOOptimized,
|
||||
InstanceType: b.config.InstanceType,
|
||||
UserData: b.config.UserData,
|
||||
UserDataFile: b.config.UserDataFile,
|
||||
RegionId: b.config.AlicloudRegion,
|
||||
InternetChargeType: b.config.InternetChargeType,
|
||||
InternetMaxBandwidthOut: b.config.InternetMaxBandwidthOut,
|
||||
InstnaceName: b.config.InstanceName,
|
||||
ZoneId: b.config.ZoneId,
|
||||
})
|
||||
if b.chooseNetworkType() == VpcNet {
|
||||
steps = append(steps, &setpConfigAlicloudEIP{
|
||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||
RegionId: b.config.AlicloudRegion,
|
||||
InternetChargeType: b.config.InternetChargeType,
|
||||
})
|
||||
} else {
|
||||
steps = append(steps, &stepConfigAlicloudPublicIP{
|
||||
RegionId: b.config.AlicloudRegion,
|
||||
})
|
||||
}
|
||||
steps = append(steps,
|
||||
&stepRunAlicloudInstance{},
|
||||
&stepMountAlicloudDisk{},
|
||||
&stepAttachKeyPar{},
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.RunConfig.Comm,
|
||||
Host: SSHHost(
|
||||
client,
|
||||
b.config.SSHPrivateIp),
|
||||
SSHConfig: SSHConfig(
|
||||
b.config.RunConfig.Comm.SSHAgentAuth,
|
||||
b.config.RunConfig.Comm.SSHUsername,
|
||||
b.config.RunConfig.Comm.SSHPassword),
|
||||
},
|
||||
&common.StepProvision{},
|
||||
&stepStopAlicloudInstance{
|
||||
ForceStop: b.config.ForceStopInstance,
|
||||
},
|
||||
&stepDeleteAlicloudImageSnapshots{
|
||||
AlicloudImageForceDeteleSnapshots: b.config.AlicloudImageForceDeteleSnapshots,
|
||||
AlicloudImageForceDetele: b.config.AlicloudImageForceDetele,
|
||||
AlicloudImageName: b.config.AlicloudImageName,
|
||||
},
|
||||
&stepCreateAlicloudImage{},
|
||||
&setpRegionCopyAlicloudImage{
|
||||
AlicloudImageDestinationRegions: b.config.AlicloudImageDestinationRegions,
|
||||
AlicloudImageDestinationNames: b.config.AlicloudImageDestinationNames,
|
||||
RegionId: b.config.AlicloudRegion,
|
||||
},
|
||||
&setpShareAlicloudImage{
|
||||
AlicloudImageShareAccounts: b.config.AlicloudImageShareAccounts,
|
||||
AlicloudImageUNShareAccounts: b.config.AlicloudImageUNShareAccounts,
|
||||
RegionId: b.config.AlicloudRegion,
|
||||
})
|
||||
|
||||
// Run!
|
||||
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
|
||||
b.runner.Run(state)
|
||||
|
||||
// If there was an error, return that
|
||||
if rawErr, ok := state.GetOk("error"); ok {
|
||||
return nil, rawErr.(error)
|
||||
}
|
||||
|
||||
// If there are no ECS images, then just return
|
||||
if _, ok := state.GetOk("alicloudimages"); !ok {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Build the artifact and return it
|
||||
artifact := &Artifact{
|
||||
AlicloudImages: state.Get("alicloudimages").(map[string]string),
|
||||
BuilderIdValue: BuilderId,
|
||||
Client: client,
|
||||
}
|
||||
|
||||
return artifact, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Cancel() {
|
||||
if b.runner != nil {
|
||||
log.Println("Cancelling the step runner...")
|
||||
b.runner.Cancel()
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Builder) chooseNetworkType() InstanceNetWork {
|
||||
//Alicloud userdata require vpc network and public key require userdata, so besides user specific vpc network,
|
||||
//choose vpc networks in those cases
|
||||
if b.config.RunConfig.Comm.SSHPrivateKey != "" || b.config.UserData != "" || b.config.UserDataFile != "" ||
|
||||
b.config.VpcId != "" || b.config.VSwitchId != "" || b.config.TemporaryKeyPairName != "" {
|
||||
return VpcNet
|
||||
} else {
|
||||
return ClassicNet
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,331 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"fmt"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
builderT "github.com/hashicorp/packer/helper/builder/testing"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
func TestBuilderAcc_basic(t *testing.T) {
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() {
|
||||
testAccPreCheck(t)
|
||||
},
|
||||
Builder: &Builder{},
|
||||
Template: testBuilderAccBasic,
|
||||
})
|
||||
}
|
||||
|
||||
//func TestBuilderAcc_windows(t *testing.T) {
|
||||
// builderT.Test(t, builderT.TestCase{
|
||||
// PreCheck: func() {
|
||||
// testAccPreCheck(t)
|
||||
// },
|
||||
// Builder: &Builder{},
|
||||
// Template: testBuilderAccWindows,
|
||||
// })
|
||||
//}
|
||||
|
||||
//func TestBuilderAcc_regionCopy(t *testing.T) {
|
||||
// builderT.Test(t, builderT.TestCase{
|
||||
// PreCheck: func() {
|
||||
// testAccPreCheck(t)
|
||||
// },
|
||||
// Builder: &Builder{},
|
||||
// Template: testBuilderAccRegionCopy,
|
||||
// Check: checkRegionCopy([]string{"cn-hangzhou", "cn-shenzhen"}),
|
||||
// })
|
||||
//}
|
||||
|
||||
func TestBuilderAcc_forceDelete(t *testing.T) {
|
||||
// Build the same alicloud image twice, with ecs_image_force_delete on the second run
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() {
|
||||
testAccPreCheck(t)
|
||||
},
|
||||
Builder: &Builder{},
|
||||
Template: buildForceDeregisterConfig("false", "delete"),
|
||||
SkipArtifactTeardown: true,
|
||||
})
|
||||
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() {
|
||||
testAccPreCheck(t)
|
||||
},
|
||||
Builder: &Builder{},
|
||||
Template: buildForceDeregisterConfig("true", "delete"),
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuilderAcc_ECSImageSharing(t *testing.T) {
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() {
|
||||
testAccPreCheck(t)
|
||||
},
|
||||
Builder: &Builder{},
|
||||
Template: testBuilderAccSharing,
|
||||
Check: checkECSImageSharing("1309208528360047"),
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuilderAcc_forceDeleteSnapshot(t *testing.T) {
|
||||
destImageName := "delete"
|
||||
|
||||
// Build the same alicloud image name twice, with force_delete_snapshot on the second run
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() {
|
||||
testAccPreCheck(t)
|
||||
},
|
||||
Builder: &Builder{},
|
||||
Template: buildForceDeleteSnapshotConfig("false", destImageName),
|
||||
SkipArtifactTeardown: true,
|
||||
})
|
||||
|
||||
// Get image data by image image name
|
||||
client, _ := testAliyunClient()
|
||||
images, _, _ := client.DescribeImages(&ecs.DescribeImagesArgs{
|
||||
ImageName: "packer-test-" + destImageName,
|
||||
RegionId: common.Region("cn-beijing")})
|
||||
|
||||
image := images[0]
|
||||
|
||||
// Get snapshot ids for image
|
||||
snapshotIds := []string{}
|
||||
for _, device := range image.DiskDeviceMappings.DiskDeviceMapping {
|
||||
if device.Device != "" && device.SnapshotId != "" {
|
||||
snapshotIds = append(snapshotIds, device.SnapshotId)
|
||||
}
|
||||
}
|
||||
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() {
|
||||
testAccPreCheck(t)
|
||||
},
|
||||
Builder: &Builder{},
|
||||
Template: buildForceDeleteSnapshotConfig("true", destImageName),
|
||||
Check: checkSnapshotsDeleted(snapshotIds),
|
||||
})
|
||||
}
|
||||
|
||||
func checkSnapshotsDeleted(snapshotIds []string) builderT.TestCheckFunc {
|
||||
return func(artifacts []packer.Artifact) error {
|
||||
// Verify the snapshots are gone
|
||||
client, _ := testAliyunClient()
|
||||
snapshotResp, _, err := client.DescribeSnapshots(
|
||||
&ecs.DescribeSnapshotsArgs{RegionId: common.Region("cn-beijing"), SnapshotIds: snapshotIds},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Query snapshot failed %v", err)
|
||||
}
|
||||
if len(snapshotResp) > 0 {
|
||||
return fmt.Errorf("Snapshots weren't successfully deleted by " +
|
||||
"`ecs_image_force_delete_snapshots`")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func checkECSImageSharing(uid string) builderT.TestCheckFunc {
|
||||
return func(artifacts []packer.Artifact) error {
|
||||
if len(artifacts) > 1 {
|
||||
return fmt.Errorf("more than 1 artifact")
|
||||
}
|
||||
|
||||
// Get the actual *Artifact pointer so we can access the AMIs directly
|
||||
artifactRaw := artifacts[0]
|
||||
artifact, ok := artifactRaw.(*Artifact)
|
||||
if !ok {
|
||||
return fmt.Errorf("unknown artifact: %#v", artifactRaw)
|
||||
}
|
||||
|
||||
// describe the image, get block devices with a snapshot
|
||||
client, _ := testAliyunClient()
|
||||
imageSharePermissionResponse, err := client.DescribeImageSharePermission(
|
||||
&ecs.ModifyImageSharePermissionArgs{
|
||||
RegionId: "cn-beijing",
|
||||
ImageId: artifact.AlicloudImages["cn-beijing"],
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error retrieving Image Attributes for ECS Image Artifact (%#v) "+
|
||||
"in ECS Image Sharing Test: %s", artifact, err)
|
||||
}
|
||||
|
||||
if len(imageSharePermissionResponse.Accounts.Account) != 1 &&
|
||||
imageSharePermissionResponse.Accounts.Account[0].AliyunId != uid {
|
||||
return fmt.Errorf("share account is incorrect %d",
|
||||
len(imageSharePermissionResponse.Accounts.Account))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func checkRegionCopy(regions []string) builderT.TestCheckFunc {
|
||||
return func(artifacts []packer.Artifact) error {
|
||||
if len(artifacts) > 1 {
|
||||
return fmt.Errorf("more than 1 artifact")
|
||||
}
|
||||
|
||||
// Get the actual *Artifact pointer so we can access the AMIs directly
|
||||
artifactRaw := artifacts[0]
|
||||
artifact, ok := artifactRaw.(*Artifact)
|
||||
if !ok {
|
||||
return fmt.Errorf("unknown artifact: %#v", artifactRaw)
|
||||
}
|
||||
|
||||
// Verify that we copied to only the regions given
|
||||
regionSet := make(map[string]struct{})
|
||||
for _, r := range regions {
|
||||
regionSet[r] = struct{}{}
|
||||
}
|
||||
for r := range artifact.AlicloudImages {
|
||||
if r == "cn-beijing" {
|
||||
delete(regionSet, r)
|
||||
continue
|
||||
}
|
||||
if _, ok := regionSet[r]; !ok {
|
||||
return fmt.Errorf("unknown region: %s", r)
|
||||
}
|
||||
|
||||
delete(regionSet, r)
|
||||
}
|
||||
if len(regionSet) > 0 {
|
||||
return fmt.Errorf("didn't copy to: %#v", regionSet)
|
||||
}
|
||||
client, _ := testAliyunClient()
|
||||
for key, value := range artifact.AlicloudImages {
|
||||
client.WaitForImageReady(common.Region(key), value, 1800)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccPreCheck(t *testing.T) {
|
||||
if v := os.Getenv("ALICLOUD_ACCESS_KEY"); v == "" {
|
||||
t.Fatal("ALICLOUD_ACCESS_KEY must be set for acceptance tests")
|
||||
}
|
||||
|
||||
if v := os.Getenv("ALICLOUD_SECRET_KEY"); v == "" {
|
||||
t.Fatal("ALICLOUD_SECRET_KEY must be set for acceptance tests")
|
||||
}
|
||||
}
|
||||
|
||||
func testAliyunClient() (*ecs.Client, error) {
|
||||
access := &AlicloudAccessConfig{AlicloudRegion: "cn-beijing"}
|
||||
err := access.Config()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client, err := access.Client()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
const testBuilderAccBasic = `
|
||||
{ "builders": [{
|
||||
"type": "test",
|
||||
"region": "cn-beijing",
|
||||
"instance_type": "ecs.n1.tiny",
|
||||
"source_image":"ubuntu_16_0402_64_40G_base_20170222.vhd",
|
||||
"ssh_username": "ubuntu",
|
||||
"io_optimized":"true",
|
||||
"ssh_username":"root",
|
||||
"image_name": "packer-test_{{timestamp}}"
|
||||
}]
|
||||
}`
|
||||
|
||||
const testBuilderAccRegionCopy = `
|
||||
{
|
||||
"builders": [{
|
||||
"type": "test",
|
||||
"region": "cn-beijing",
|
||||
"instance_type": "ecs.n1.tiny",
|
||||
"source_image":"ubuntu_16_0402_64_40G_base_20170222.vhd",
|
||||
"io_optimized":"true",
|
||||
"ssh_username":"root",
|
||||
"image_name": "packer-test_{{timestamp}}",
|
||||
"image_copy_regions": ["cn-hangzhou", "cn-shenzhen"]
|
||||
}]
|
||||
}
|
||||
`
|
||||
|
||||
const testBuilderAccForceDelete = `
|
||||
{
|
||||
"builders": [{
|
||||
"type": "test",
|
||||
"region": "cn-beijing",
|
||||
"instance_type": "ecs.n1.tiny",
|
||||
"source_image":"ubuntu_16_0402_64_40G_base_20170222.vhd",
|
||||
"io_optimized":"true",
|
||||
"ssh_username":"root",
|
||||
"image_force_delete": "%s",
|
||||
"image_name": "packer-test_%s"
|
||||
}]
|
||||
}
|
||||
`
|
||||
|
||||
const testBuilderAccForceDeleteSnapshot = `
|
||||
{
|
||||
"builders": [{
|
||||
"type": "test",
|
||||
"region": "cn-beijing",
|
||||
"instance_type": "ecs.n1.tiny",
|
||||
"source_image":"ubuntu_16_0402_64_40G_base_20170222.vhd",
|
||||
"io_optimized":"true",
|
||||
"ssh_username":"root",
|
||||
"image_force_delete_snapshots": "%s",
|
||||
"image_force_delete": "%s",
|
||||
"image_name": "packer-test-%s"
|
||||
}]
|
||||
}
|
||||
`
|
||||
|
||||
// share with catsby
|
||||
const testBuilderAccSharing = `
|
||||
{
|
||||
"builders": [{
|
||||
"type": "test",
|
||||
"region": "cn-beijing",
|
||||
"instance_type": "ecs.n1.tiny",
|
||||
"source_image":"ubuntu_16_0402_64_40G_base_20170222.vhd",
|
||||
"io_optimized":"true",
|
||||
"ssh_username":"root",
|
||||
"image_name": "packer-test_{{timestamp}}",
|
||||
"image_share_account":["1309208528360047"]
|
||||
}]
|
||||
}
|
||||
`
|
||||
|
||||
func buildForceDeregisterConfig(val, name string) string {
|
||||
return fmt.Sprintf(testBuilderAccForceDelete, val, name)
|
||||
}
|
||||
|
||||
func buildForceDeleteSnapshotConfig(val, name string) string {
|
||||
return fmt.Sprintf(testBuilderAccForceDeleteSnapshot, val, val, name)
|
||||
}
|
||||
|
||||
const testBuilderAccWindows = `
|
||||
{ "builders": [{
|
||||
"type": "test",
|
||||
"region": "cn-beijing",
|
||||
"instance_type": "ecs.n1.tiny",
|
||||
"source_image":"win2008_64_ent_r2_zh-cn_40G_alibase_20170301.vhd",
|
||||
"io_optimized":"true",
|
||||
"image_force_delete":"true",
|
||||
"communicator": "winrm",
|
||||
"winrm_port": 5985,
|
||||
"winrm_username": "Administrator",
|
||||
"winrm_password": "Test1234",
|
||||
"image_name": "packer-test_{{timestamp}}"
|
||||
}]
|
||||
}`
|
|
@ -0,0 +1,95 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
func testBuilderConfig() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"access_key": "foo",
|
||||
"secret_key": "bar",
|
||||
"source_image": "foo",
|
||||
"instance_type": "ecs.n1.tiny",
|
||||
"region": "cn-beijing",
|
||||
"ssh_username": "root",
|
||||
"image_name": "foo",
|
||||
"io_optimized": true,
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilder_ImplementsBuilder(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &Builder{}
|
||||
if _, ok := raw.(packer.Builder); !ok {
|
||||
t.Fatalf("Builder should be a builder")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilder_Prepare_BadType(t *testing.T) {
|
||||
b := &Builder{}
|
||||
c := map[string]interface{}{
|
||||
"access_key": []string{},
|
||||
}
|
||||
|
||||
warnings, err := b.Prepare(c)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatalf("prepare should fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_ECSImageName(t *testing.T) {
|
||||
var b Builder
|
||||
config := testBuilderConfig()
|
||||
|
||||
// Test good
|
||||
config["image_name"] = "ecs.n1.tiny"
|
||||
warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
// Test bad
|
||||
config["ecs_image_name"] = "foo {{"
|
||||
b = Builder{}
|
||||
warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test bad
|
||||
delete(config, "image_name")
|
||||
b = Builder{}
|
||||
warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_InvalidKey(t *testing.T) {
|
||||
var b Builder
|
||||
config := testBuilderConfig()
|
||||
|
||||
// Add a random key
|
||||
config["i_should_not_be_valid"] = true
|
||||
warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type AlicloudDiskDevice struct {
|
||||
DiskName string `mapstructure:"disk_name"`
|
||||
DiskCategory string `mapstructure:"disk_category"`
|
||||
DiskSize int `mapstructure:"disk_size"`
|
||||
SnapshotId string `mapstructure:"disk_snapshot_id"`
|
||||
Description string `mapstructure:"disk_description"`
|
||||
DeleteWithInstance bool `mapstructure:"disk_delete_with_instance"`
|
||||
Device string `mapstructure:"disk_device"`
|
||||
}
|
||||
|
||||
type AlicloudDiskDevices struct {
|
||||
ECSImagesDiskMappings []AlicloudDiskDevice `mapstructure:"image_disk_mappings"`
|
||||
}
|
||||
|
||||
type AlicloudImageConfig struct {
|
||||
AlicloudImageName string `mapstructure:"image_name"`
|
||||
AlicloudImageVersion string `mapstructure:"image_version"`
|
||||
AlicloudImageDescription string `mapstructure:"image_description"`
|
||||
AlicloudImageShareAccounts []string `mapstructure:"image_share_account"`
|
||||
AlicloudImageUNShareAccounts []string `mapstructure:"image_unshare_account"`
|
||||
AlicloudImageDestinationRegions []string `mapstructure:"image_copy_regions"`
|
||||
AlicloudImageDestinationNames []string `mapstructure:"image_copy_names"`
|
||||
AlicloudImageForceDetele bool `mapstructure:"image_force_delete"`
|
||||
AlicloudImageForceDeteleSnapshots bool `mapstructure:"image_force_delete_snapshots"`
|
||||
AlicloudImageForceDeleteInstances bool `mapstructure:"image_force_delete_instances"`
|
||||
AlicloudImageSkipRegionValidation bool `mapstructure:"skip_region_validation"`
|
||||
AlicloudDiskDevices `mapstructure:",squash"`
|
||||
}
|
||||
|
||||
func (c *AlicloudImageConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
var errs []error
|
||||
if c.AlicloudImageName == "" {
|
||||
errs = append(errs, fmt.Errorf("image_name must be specified"))
|
||||
} else if len(c.AlicloudImageName) < 2 || len(c.AlicloudImageName) > 128 {
|
||||
errs = append(errs, fmt.Errorf("image_name must less than 128 letters and more than 1 letters"))
|
||||
} else if strings.HasPrefix(c.AlicloudImageName, "http://") ||
|
||||
strings.HasPrefix(c.AlicloudImageName, "https://") {
|
||||
errs = append(errs, fmt.Errorf("image_name can't start with 'http://' or 'https://'"))
|
||||
}
|
||||
reg := regexp.MustCompile("\\s+")
|
||||
if reg.FindString(c.AlicloudImageName) != "" {
|
||||
errs = append(errs, fmt.Errorf("image_name can't include spaces"))
|
||||
}
|
||||
|
||||
if len(c.AlicloudImageDestinationRegions) > 0 {
|
||||
regionSet := make(map[string]struct{})
|
||||
regions := make([]string, 0, len(c.AlicloudImageDestinationRegions))
|
||||
|
||||
for _, region := range c.AlicloudImageDestinationRegions {
|
||||
// If we already saw the region, then don't look again
|
||||
if _, ok := regionSet[region]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Mark that we saw the region
|
||||
regionSet[region] = struct{}{}
|
||||
|
||||
if !c.AlicloudImageSkipRegionValidation {
|
||||
// Verify the region is real
|
||||
if valid := validateRegion(region); valid != nil {
|
||||
errs = append(errs, fmt.Errorf("Unknown region: %s", region))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
regions = append(regions, region)
|
||||
}
|
||||
|
||||
c.AlicloudImageDestinationRegions = regions
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateRegion(region string) error {
|
||||
|
||||
for _, valid := range common.ValidRegions {
|
||||
if region == string(valid) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("Not a valid alicloud region: %s", region)
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
)
|
||||
|
||||
func testAlicloudImageConfig() *AlicloudImageConfig {
|
||||
return &AlicloudImageConfig{
|
||||
AlicloudImageName: "foo",
|
||||
}
|
||||
}
|
||||
|
||||
func TestECSImageConfigPrepare_name(t *testing.T) {
|
||||
c := testAlicloudImageConfig()
|
||||
if err := c.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err)
|
||||
}
|
||||
|
||||
c.AlicloudImageName = ""
|
||||
if err := c.Prepare(nil); err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAMIConfigPrepare_regions(t *testing.T) {
|
||||
c := testAlicloudImageConfig()
|
||||
c.AlicloudImageDestinationRegions = nil
|
||||
if err := c.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err)
|
||||
}
|
||||
|
||||
c.AlicloudImageDestinationRegions = regionsToString()
|
||||
if err := c.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err)
|
||||
}
|
||||
|
||||
c.AlicloudImageDestinationRegions = []string{"foo"}
|
||||
if err := c.Prepare(nil); err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
c.AlicloudImageDestinationRegions = []string{"cn-beijing", "cn-hangzhou", "eu-central-1"}
|
||||
if err := c.Prepare(nil); err != nil {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
|
||||
c.AlicloudImageDestinationRegions = []string{"unknow"}
|
||||
c.AlicloudImageSkipRegionValidation = true
|
||||
if err := c.Prepare(nil); err != nil {
|
||||
t.Fatal("shouldn't have error")
|
||||
}
|
||||
c.AlicloudImageSkipRegionValidation = false
|
||||
|
||||
}
|
||||
|
||||
func regionsToString() []string {
|
||||
var regions []string
|
||||
for _, region := range common.ValidRegions {
|
||||
regions = append(regions, string(region))
|
||||
}
|
||||
return regions
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
func message(state multistep.StateBag, module string) {
|
||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
||||
_, halted := state.GetOk(multistep.StateHalted)
|
||||
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
if cancelled || halted {
|
||||
ui.Say(fmt.Sprintf("Deleting %s because of cancellation or error...", module))
|
||||
} else {
|
||||
ui.Say(fmt.Sprintf("Cleaning up '%s'", module))
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/hashicorp/packer/common/uuid"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type RunConfig struct {
|
||||
AssociatePublicIpAddress bool `mapstructure:"associate_public_ip_address"`
|
||||
ZoneId string `mapstructure:"zone_id"`
|
||||
IOOptimized bool `mapstructure:"io_optimized"`
|
||||
InstanceType string `mapstructure:"instance_type"`
|
||||
Description string `mapstructure:"description"`
|
||||
AlicloudSourceImage string `mapstructure:"source_image"`
|
||||
ForceStopInstance bool `mapstructure:"force_stop_instance"`
|
||||
SecurityGroupId string `mapstructure:"security_group_id"`
|
||||
SecurityGroupName string `mapstructure:"security_group_name"`
|
||||
UserData string `mapstructure:"user_data"`
|
||||
UserDataFile string `mapstructure:"user_data_file"`
|
||||
VpcId string `mapstructure:"vpc_id"`
|
||||
VpcName string `mapstructure:"vpc_name"`
|
||||
CidrBlock string `mapstructure:"vpc_cidr_block"`
|
||||
VSwitchId string `mapstructure:"vswitch_id"`
|
||||
VSwitchName string `mapstructure:"vswitch_id"`
|
||||
InstanceName string `mapstructure:"instance_name"`
|
||||
InternetChargeType string `mapstructure:"internet_charge_type"`
|
||||
InternetMaxBandwidthOut int `mapstructure:"internet_max_bandwith_out"`
|
||||
TemporaryKeyPairName string `mapstructure:"temporary_key_pair_name"`
|
||||
|
||||
// Communicator settings
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
SSHKeyPairName string `mapstructure:"ssh_keypair_name"`
|
||||
SSHPrivateIp bool `mapstructure:"ssh_private_ip"`
|
||||
}
|
||||
|
||||
func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
if c.SSHKeyPairName == "" && c.TemporaryKeyPairName == "" &&
|
||||
c.Comm.SSHPrivateKey == "" && c.Comm.SSHPassword == "" && c.Comm.WinRMPassword == "" {
|
||||
|
||||
c.TemporaryKeyPairName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())
|
||||
}
|
||||
|
||||
// Validation
|
||||
errs := c.Comm.Prepare(ctx)
|
||||
if c.AlicloudSourceImage == "" {
|
||||
errs = append(errs, errors.New("A source_image must be specified"))
|
||||
}
|
||||
|
||||
if strings.TrimSpace(c.AlicloudSourceImage) != c.AlicloudSourceImage {
|
||||
errs = append(errs, errors.New("The source_image can't include spaces"))
|
||||
}
|
||||
|
||||
if c.InstanceType == "" {
|
||||
errs = append(errs, errors.New("An aliclod_instance_type must be specified"))
|
||||
}
|
||||
|
||||
if c.UserData != "" && c.UserDataFile != "" {
|
||||
errs = append(errs, fmt.Errorf("Only one of user_data or user_data_file can be specified."))
|
||||
} else if c.UserDataFile != "" {
|
||||
if _, err := os.Stat(c.UserDataFile); err != nil {
|
||||
errs = append(errs, fmt.Errorf("user_data_file not found: %s", c.UserDataFile))
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
)
|
||||
|
||||
func testConfig() *RunConfig {
|
||||
return &RunConfig{
|
||||
AlicloudSourceImage: "alicloud_images",
|
||||
InstanceType: "ecs.n1.tiny",
|
||||
Comm: communicator.Config{
|
||||
SSHUsername: "alicloud",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare(t *testing.T) {
|
||||
c := testConfig()
|
||||
err := c.Prepare(nil)
|
||||
if len(err) > 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_InstanceType(t *testing.T) {
|
||||
c := testConfig()
|
||||
c.InstanceType = ""
|
||||
if err := c.Prepare(nil); len(err) != 1 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_SourceECSImage(t *testing.T) {
|
||||
c := testConfig()
|
||||
c.AlicloudSourceImage = ""
|
||||
if err := c.Prepare(nil); len(err) != 1 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_SSHPort(t *testing.T) {
|
||||
c := testConfig()
|
||||
c.Comm.SSHPort = 0
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if c.Comm.SSHPort != 22 {
|
||||
t.Fatalf("invalid value: %d", c.Comm.SSHPort)
|
||||
}
|
||||
|
||||
c.Comm.SSHPort = 44
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if c.Comm.SSHPort != 44 {
|
||||
t.Fatalf("invalid value: %d", c.Comm.SSHPort)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_UserData(t *testing.T) {
|
||||
c := testConfig()
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer tf.Close()
|
||||
|
||||
c.UserData = "foo"
|
||||
c.UserDataFile = tf.Name()
|
||||
if err := c.Prepare(nil); len(err) != 1 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_UserDataFile(t *testing.T) {
|
||||
c := testConfig()
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
c.UserDataFile = "idontexistidontthink"
|
||||
if err := c.Prepare(nil); len(err) != 1 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer tf.Close()
|
||||
|
||||
c.UserDataFile = tf.Name()
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_TemporaryKeyPairName(t *testing.T) {
|
||||
c := testConfig()
|
||||
c.TemporaryKeyPairName = ""
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if c.TemporaryKeyPairName == "" {
|
||||
t.Fatal("keypair name is empty")
|
||||
}
|
||||
|
||||
c.TemporaryKeyPairName = "ssh-key-123"
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if c.TemporaryKeyPairName != "ssh-key-123" {
|
||||
t.Fatal("keypair name does not match")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
packerssh "github.com/hashicorp/packer/communicator/ssh"
|
||||
"github.com/mitchellh/multistep"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
)
|
||||
|
||||
var (
|
||||
// modified in tests
|
||||
sshHostSleepDuration = time.Second
|
||||
)
|
||||
|
||||
type alicloudSSHHelper interface {
|
||||
}
|
||||
|
||||
// SSHHost returns a function that can be given to the SSH communicator
|
||||
func SSHHost(e alicloudSSHHelper, private bool) func(multistep.StateBag) (string, error) {
|
||||
return func(state multistep.StateBag) (string, error) {
|
||||
ipAddress := state.Get("ipaddress").(string)
|
||||
return ipAddress, nil
|
||||
}
|
||||
}
|
||||
|
||||
// SSHConfig returns a function that can be used for the SSH communicator
|
||||
// config for connecting to the instance created over SSH using the private key
|
||||
// or password.
|
||||
func SSHConfig(useAgent bool, username, password string) func(multistep.StateBag) (*ssh.ClientConfig, error) {
|
||||
return func(state multistep.StateBag) (*ssh.ClientConfig, error) {
|
||||
if useAgent {
|
||||
authSock := os.Getenv("SSH_AUTH_SOCK")
|
||||
if authSock == "" {
|
||||
return nil, fmt.Errorf("SSH_AUTH_SOCK is not set")
|
||||
}
|
||||
|
||||
sshAgent, err := net.Dial("unix", authSock)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Cannot connect to SSH Agent socket %q: %s", authSock, err)
|
||||
}
|
||||
|
||||
return &ssh.ClientConfig{
|
||||
User: username,
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.PublicKeysCallback(agent.NewClient(sshAgent).Signers),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
privateKey, hasKey := state.GetOk("privateKey")
|
||||
if hasKey {
|
||||
|
||||
signer, err := ssh.ParsePrivateKey([]byte(privateKey.(string)))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error setting up SSH config: %s", err)
|
||||
}
|
||||
return &ssh.ClientConfig{
|
||||
User: username,
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.PublicKeys(signer),
|
||||
},
|
||||
}, nil
|
||||
|
||||
} else {
|
||||
return &ssh.ClientConfig{
|
||||
User: username,
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.Password(password),
|
||||
ssh.KeyboardInteractive(
|
||||
packerssh.PasswordKeyboardInteractive(password)),
|
||||
}}, nil
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
type stepAttachKeyPar struct {
|
||||
}
|
||||
|
||||
func (s *stepAttachKeyPar) Run(state multistep.StateBag) multistep.StepAction {
|
||||
keyPairName := state.Get("keyPair").(string)
|
||||
if keyPairName == "" {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
config := state.Get("config").(Config)
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
retry_times := 3
|
||||
for {
|
||||
err := client.AttachKeyPair(&ecs.AttachKeyPairArgs{RegionId: common.Region(config.AlicloudRegion),
|
||||
KeyPairName: keyPairName, InstanceIds: "[\"" + instance.InstanceId + "\"]"})
|
||||
if err != nil {
|
||||
e, _ := err.(*common.Error)
|
||||
if (!(e.Code == "MissingParameter" || e.Code == "DependencyViolation.WindowsInstance" ||
|
||||
e.Code == "InvalidKeyPairName.NotFound" || e.Code == "InvalidRegionId.NotFound")) &&
|
||||
retry_times > 0 {
|
||||
retry_times = retry_times - 1
|
||||
continue
|
||||
}
|
||||
err := fmt.Errorf("Error attaching keypair %s to instance %s : %s",
|
||||
keyPairName, instance.InstanceId, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
ui.Message(fmt.Sprintf("Attach keypair %s to instance: %s", keyPairName, instance.InstanceId))
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepAttachKeyPar) Cleanup(state multistep.StateBag) {
|
||||
keyPairName := state.Get("keyPair").(string)
|
||||
if keyPairName == "" {
|
||||
return
|
||||
}
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
config := state.Get("config").(Config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
|
||||
err := client.DetachKeyPair(&ecs.DetachKeyPairArgs{RegionId: common.Region(config.AlicloudRegion),
|
||||
KeyPairName: keyPairName, InstanceIds: "[\"" + instance.InstanceId + "\"]"})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error Detaching keypair %s to instance %s : %s", keyPairName,
|
||||
instance.InstanceId, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
ui.Message(fmt.Sprintf("Detach keypair %s from instance: %s", keyPairName, instance.InstanceId))
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
type stepCheckAlicloudSourceImage struct {
|
||||
SourceECSImageId string
|
||||
}
|
||||
|
||||
func (s *stepCheckAlicloudSourceImage) Run(state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
config := state.Get("config").(Config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
images, _, err := client.DescribeImages(&ecs.DescribeImagesArgs{RegionId: common.Region(config.AlicloudRegion),
|
||||
ImageId: config.AlicloudSourceImage})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error querying alicloud image: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if len(images) == 0 {
|
||||
err := fmt.Errorf("No alicloud image was found matching filters: %v", config.AlicloudSourceImage)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Message(fmt.Sprintf("Found image ID: %s", images[0].ImageId))
|
||||
|
||||
state.Put("source_image", &images[0])
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepCheckAlicloudSourceImage) Cleanup(multistep.StateBag) {}
|
|
@ -0,0 +1,79 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
type setpConfigAlicloudEIP struct {
|
||||
AssociatePublicIpAddress bool
|
||||
RegionId string
|
||||
InternetChargeType string
|
||||
allocatedId string
|
||||
}
|
||||
|
||||
func (s *setpConfigAlicloudEIP) Run(state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
ui.Say("Allocating eip")
|
||||
ipaddress, allocateId, err := client.AllocateEipAddress(&ecs.AllocateEipAddressArgs{
|
||||
RegionId: common.Region(s.RegionId), InternetChargeType: common.InternetChargeType(s.InternetChargeType),
|
||||
})
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Say(fmt.Sprintf("Error allocating eip: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
s.allocatedId = allocateId
|
||||
if err = client.WaitForEip(common.Region(s.RegionId), allocateId,
|
||||
ecs.EipStatusAvailable, ALICLOUD_DEFAULT_SHORT_TIMEOUT); err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Say(fmt.Sprintf("Error allocating eip: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if err = client.AssociateEipAddress(allocateId, instance.InstanceId); err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Say(fmt.Sprintf("Error binding eip: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if err = client.WaitForEip(common.Region(s.RegionId), allocateId,
|
||||
ecs.EipStatusInUse, ALICLOUD_DEFAULT_SHORT_TIMEOUT); err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Say(fmt.Sprintf("Error associating eip: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
ui.Say(fmt.Sprintf("Allocated eip %s", ipaddress))
|
||||
state.Put("ipaddress", ipaddress)
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *setpConfigAlicloudEIP) Cleanup(state multistep.StateBag) {
|
||||
if len(s.allocatedId) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
message(state, "EIP")
|
||||
|
||||
if err := client.UnassociateEipAddress(s.allocatedId, instance.InstanceId); err != nil {
|
||||
ui.Say(fmt.Sprintf("Failed to unassociate eip."))
|
||||
}
|
||||
|
||||
if err := client.WaitForEip(common.Region(s.RegionId), s.allocatedId, ecs.EipStatusAvailable, ALICLOUD_DEFAULT_SHORT_TIMEOUT); err != nil {
|
||||
ui.Say(fmt.Sprintf("Timeout while unassociating eip."))
|
||||
}
|
||||
if err := client.ReleaseEipAddress(s.allocatedId); err != nil {
|
||||
ui.Say(fmt.Sprintf("Failed to release eip."))
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
type StepConfigAlicloudKeyPair struct {
|
||||
Debug bool
|
||||
SSHAgentAuth bool
|
||||
DebugKeyPath string
|
||||
TemporaryKeyPairName string
|
||||
KeyPairName string
|
||||
PrivateKeyFile string
|
||||
RegionId string
|
||||
|
||||
keyName string
|
||||
}
|
||||
|
||||
func (s *StepConfigAlicloudKeyPair) Run(state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
if s.PrivateKeyFile != "" {
|
||||
ui.Say("Using existing SSH private key")
|
||||
privateKeyBytes, err := ioutil.ReadFile(s.PrivateKeyFile)
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf(
|
||||
"Error loading configured private key file: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
state.Put("keyPair", s.KeyPairName)
|
||||
state.Put("privateKey", string(privateKeyBytes))
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
if s.SSHAgentAuth && s.KeyPairName == "" {
|
||||
ui.Say("Using SSH Agent with key pair in source image")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
if s.SSHAgentAuth && s.KeyPairName != "" {
|
||||
ui.Say(fmt.Sprintf("Using SSH Agent for existing key pair %s", s.KeyPairName))
|
||||
state.Put("keyPair", s.KeyPairName)
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
if s.TemporaryKeyPairName == "" {
|
||||
ui.Say("Not using temporary keypair")
|
||||
state.Put("keyPair", "")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
|
||||
ui.Say(fmt.Sprintf("Creating temporary keypair: %s", s.TemporaryKeyPairName))
|
||||
keyResp, err := client.CreateKeyPair(&ecs.CreateKeyPairArgs{
|
||||
KeyPairName: s.TemporaryKeyPairName,
|
||||
RegionId: common.Region(s.RegionId),
|
||||
})
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("Error creating temporary keypair: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Set the keyname so we know to delete it later
|
||||
s.keyName = s.TemporaryKeyPairName
|
||||
|
||||
// Set some state data for use in future steps
|
||||
state.Put("keyPair", s.keyName)
|
||||
state.Put("privateKey", keyResp.PrivateKeyBody)
|
||||
|
||||
// If we're in debug mode, output the private key to the working
|
||||
// directory.
|
||||
if s.Debug {
|
||||
ui.Message(fmt.Sprintf("Saving key for debug purposes: %s", s.DebugKeyPath))
|
||||
f, err := os.Create(s.DebugKeyPath)
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// Write the key out
|
||||
if _, err := f.Write([]byte(keyResp.PrivateKeyBody)); err != nil {
|
||||
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Chmod it so that it is SSH ready
|
||||
if runtime.GOOS != "windows" {
|
||||
if err := f.Chmod(0600); err != nil {
|
||||
state.Put("error", fmt.Errorf("Error setting permissions of debug key: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepConfigAlicloudKeyPair) Cleanup(state multistep.StateBag) {
|
||||
// If no key name is set, then we never created it, so just return
|
||||
// If we used an SSH private key file, do not go about deleting
|
||||
// keypairs
|
||||
if s.PrivateKeyFile != "" || (s.KeyPairName == "" && s.keyName == "") {
|
||||
return
|
||||
}
|
||||
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
// Remove the keypair
|
||||
ui.Say("Deleting temporary keypair...")
|
||||
err := client.DeleteKeyPairs(&ecs.DeleteKeyPairsArgs{
|
||||
RegionId: common.Region(s.RegionId),
|
||||
KeyPairNames: "[\"" + s.keyName + "\"]",
|
||||
})
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf(
|
||||
"Error cleaning up keypair. Please delete the key manually: %s", s.keyName))
|
||||
}
|
||||
|
||||
// Also remove the physical key if we're debugging.
|
||||
if s.Debug {
|
||||
if err := os.Remove(s.DebugKeyPath); err != nil {
|
||||
ui.Error(fmt.Sprintf(
|
||||
"Error removing debug key '%s': %s", s.DebugKeyPath, err))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
type stepConfigAlicloudPublicIP struct {
|
||||
publicIPAdress string
|
||||
RegionId string
|
||||
}
|
||||
|
||||
func (s *stepConfigAlicloudPublicIP) Run(state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
|
||||
ipaddress, err := client.AllocatePublicIpAddress(instance.InstanceId)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Say(fmt.Sprintf("Error allocating public ip: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
s.publicIPAdress = ipaddress
|
||||
ui.Say(fmt.Sprintf("Allocated public ip address %s.", ipaddress))
|
||||
state.Put("ipaddress", ipaddress)
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepConfigAlicloudPublicIP) Cleanup(state multistep.StateBag) {
|
||||
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
type stepConfigAlicloudSecurityGroup struct {
|
||||
SecurityGroupId string
|
||||
SecurityGroupName string
|
||||
Description string
|
||||
VpcId string
|
||||
RegionId string
|
||||
isCreate bool
|
||||
}
|
||||
|
||||
func (s *stepConfigAlicloudSecurityGroup) Run(state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
networkType := state.Get("networktype").(InstanceNetWork)
|
||||
|
||||
var securityGroupItems []ecs.SecurityGroupItemType
|
||||
var err error
|
||||
if len(s.SecurityGroupId) != 0 {
|
||||
if networkType == VpcNet {
|
||||
vpcId := state.Get("vpcid").(string)
|
||||
securityGroupItems, _, err = client.DescribeSecurityGroups(&ecs.DescribeSecurityGroupsArgs{
|
||||
VpcId: vpcId,
|
||||
RegionId: common.Region(s.RegionId),
|
||||
})
|
||||
} else {
|
||||
securityGroupItems, _, err = client.DescribeSecurityGroups(&ecs.DescribeSecurityGroupsArgs{
|
||||
RegionId: common.Region(s.RegionId),
|
||||
})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
ui.Say(fmt.Sprintf("Failed querying security group: %s", err))
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
for _, securityGroupItem := range securityGroupItems {
|
||||
if securityGroupItem.SecurityGroupId == s.SecurityGroupId {
|
||||
state.Put("securitygroupid", s.SecurityGroupId)
|
||||
s.isCreate = false
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
}
|
||||
s.isCreate = false
|
||||
message := fmt.Sprintf("The specified security group {%s} doesn't exist.", s.SecurityGroupId)
|
||||
state.Put("error", errors.New(message))
|
||||
ui.Say(message)
|
||||
return multistep.ActionHalt
|
||||
|
||||
}
|
||||
var securityGroupId string
|
||||
ui.Say("Creating security groups...")
|
||||
if networkType == VpcNet {
|
||||
vpcId := state.Get("vpcid").(string)
|
||||
securityGroupId, err = client.CreateSecurityGroup(&ecs.CreateSecurityGroupArgs{
|
||||
RegionId: common.Region(s.RegionId),
|
||||
SecurityGroupName: s.SecurityGroupName,
|
||||
VpcId: vpcId,
|
||||
})
|
||||
} else {
|
||||
securityGroupId, err = client.CreateSecurityGroup(&ecs.CreateSecurityGroupArgs{
|
||||
RegionId: common.Region(s.RegionId),
|
||||
SecurityGroupName: s.SecurityGroupName,
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Say(fmt.Sprintf("Failed creating security group %s.", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
state.Put("securitygroupid", securityGroupId)
|
||||
s.isCreate = true
|
||||
s.SecurityGroupId = securityGroupId
|
||||
err = client.AuthorizeSecurityGroupEgress(&ecs.AuthorizeSecurityGroupEgressArgs{
|
||||
SecurityGroupId: securityGroupId,
|
||||
RegionId: common.Region(s.RegionId),
|
||||
IpProtocol: ecs.IpProtocolAll,
|
||||
PortRange: "-1/-1",
|
||||
NicType: ecs.NicTypeInternet,
|
||||
DestCidrIp: "0.0.0.0/0", //The input parameter "DestGroupId" or "DestCidrIp" cannot be both blank.
|
||||
})
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Say(fmt.Sprintf("Failed authorizing security group: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
err = client.AuthorizeSecurityGroup(&ecs.AuthorizeSecurityGroupArgs{
|
||||
SecurityGroupId: securityGroupId,
|
||||
RegionId: common.Region(s.RegionId),
|
||||
IpProtocol: ecs.IpProtocolAll,
|
||||
PortRange: "-1/-1",
|
||||
NicType: ecs.NicTypeInternet,
|
||||
SourceCidrIp: "0.0.0.0/0", //The input parameter "SourceGroupId" or "SourceCidrIp" cannot be both blank.
|
||||
})
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Say(fmt.Sprintf("Failed authorizing security group: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepConfigAlicloudSecurityGroup) Cleanup(state multistep.StateBag) {
|
||||
if !s.isCreate {
|
||||
return
|
||||
}
|
||||
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
message(state, "security group")
|
||||
start := time.Now().Add(10 * time.Second)
|
||||
for {
|
||||
if err := client.DeleteSecurityGroup(common.Region(s.RegionId), s.SecurityGroupId); err != nil {
|
||||
e, _ := err.(*common.Error)
|
||||
if e.Code == "DependencyViolation" && time.Now().Before(start) {
|
||||
time.Sleep(1 * time.Second)
|
||||
continue
|
||||
}
|
||||
ui.Error(fmt.Sprintf("Failed to delete security group, it may still be around: %s", err))
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
type stepConfigAlicloudVPC struct {
|
||||
VpcId string
|
||||
CidrBlock string //192.168.0.0/16 or 172.16.0.0/16 (default)
|
||||
VpcName string
|
||||
isCreate bool
|
||||
}
|
||||
|
||||
func (s *stepConfigAlicloudVPC) Run(state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(Config)
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
if len(s.VpcId) != 0 {
|
||||
vpcs, _, err := client.DescribeVpcs(&ecs.DescribeVpcsArgs{
|
||||
VpcId: s.VpcId,
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
})
|
||||
if err != nil {
|
||||
ui.Say(fmt.Sprintf("Failed querying vpcs: %s", err))
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
if len(vpcs) > 0 {
|
||||
vpc := vpcs[0]
|
||||
state.Put("vpcid", vpc.VpcId)
|
||||
s.isCreate = false
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
message := fmt.Sprintf("The specified vpc {%s} doesn't exist.", s.VpcId)
|
||||
state.Put("error", errors.New(message))
|
||||
ui.Say(message)
|
||||
return multistep.ActionHalt
|
||||
|
||||
}
|
||||
ui.Say("Creating vpc")
|
||||
vpc, err := client.CreateVpc(&ecs.CreateVpcArgs{
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
CidrBlock: s.CidrBlock,
|
||||
VpcName: s.VpcName,
|
||||
})
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Say(fmt.Sprintf("Failed creating vpc: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
err = client.WaitForVpcAvailable(common.Region(config.AlicloudRegion), vpc.VpcId, ALICLOUD_DEFAULT_SHORT_TIMEOUT)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Say(fmt.Sprintf("Failed waiting for vpc to become available: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
state.Put("vpcid", vpc.VpcId)
|
||||
s.isCreate = true
|
||||
s.VpcId = vpc.VpcId
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepConfigAlicloudVPC) Cleanup(state multistep.StateBag) {
|
||||
if !s.isCreate {
|
||||
return
|
||||
}
|
||||
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
message(state, "VPC")
|
||||
start := time.Now().Add(10 * time.Second)
|
||||
for {
|
||||
if err := client.DeleteVpc(s.VpcId); err != nil {
|
||||
e, _ := err.(*common.Error)
|
||||
if (e.Code == "DependencyViolation.Instance" || e.Code == "DependencyViolation.RouteEntry" ||
|
||||
e.Code == "DependencyViolation.VSwitch" ||
|
||||
e.Code == "DependencyViolation.SecurityGroup") && time.Now().Before(start) {
|
||||
time.Sleep(1 * time.Second)
|
||||
continue
|
||||
}
|
||||
ui.Error(fmt.Sprintf("Error deleting vpc, it may still be around: %s", err))
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
type stepConfigAlicloudVSwitch struct {
|
||||
VSwitchId string
|
||||
ZoneId string
|
||||
isCreate bool
|
||||
CidrBlock string
|
||||
VSwitchName string
|
||||
}
|
||||
|
||||
func (s *stepConfigAlicloudVSwitch) Run(state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
vpcId := state.Get("vpcid").(string)
|
||||
config := state.Get("config").(Config)
|
||||
|
||||
if len(s.VSwitchId) != 0 {
|
||||
vswitchs, _, err := client.DescribeVSwitches(&ecs.DescribeVSwitchesArgs{
|
||||
VpcId: vpcId,
|
||||
VSwitchId: s.VSwitchId,
|
||||
ZoneId: s.ZoneId,
|
||||
})
|
||||
if err != nil {
|
||||
ui.Say(fmt.Sprintf("Failed querying vswitch: %s", err))
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
if len(vswitchs) > 0 {
|
||||
vswitch := vswitchs[0]
|
||||
state.Put("vswitchid", vswitch.VSwitchId)
|
||||
s.isCreate = false
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
s.isCreate = false
|
||||
message := fmt.Sprintf("The specified vswitch {%s} doesn't exist.", s.VSwitchId)
|
||||
state.Put("error", errors.New(message))
|
||||
ui.Say(message)
|
||||
return multistep.ActionHalt
|
||||
|
||||
}
|
||||
if s.ZoneId == "" {
|
||||
|
||||
zones, err := client.DescribeZones(common.Region(config.AlicloudRegion))
|
||||
if err != nil {
|
||||
ui.Say(fmt.Sprintf("Query for available zones failed: %s", err))
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
var instanceTypes []string
|
||||
for _, zone := range zones {
|
||||
isVSwitchSupported := false
|
||||
for _, resourceType := range zone.AvailableResourceCreation.ResourceTypes {
|
||||
if resourceType == ecs.ResourceTypeVSwitch {
|
||||
isVSwitchSupported = true
|
||||
}
|
||||
}
|
||||
if isVSwitchSupported {
|
||||
for _, instanceType := range zone.AvailableInstanceTypes.InstanceTypes {
|
||||
if instanceType == config.InstanceType {
|
||||
s.ZoneId = zone.ZoneId
|
||||
break
|
||||
}
|
||||
instanceTypes = append(instanceTypes, instanceType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if s.ZoneId == "" {
|
||||
if len(instanceTypes) > 0 {
|
||||
ui.Say(fmt.Sprintf("The instance type %s isn't available in this region."+
|
||||
"\n You can either change the instance to one of following: %v \n"+
|
||||
"or choose another region.", config.InstanceType, instanceTypes))
|
||||
|
||||
state.Put("error", fmt.Errorf("The instance type %s isn't available in this region."+
|
||||
"\n You can either change the instance to one of following: %v \n"+
|
||||
"or choose another region.", config.InstanceType, instanceTypes))
|
||||
return multistep.ActionHalt
|
||||
} else {
|
||||
ui.Say(fmt.Sprintf("The instance type %s isn't available in this region."+
|
||||
"\n You can change to other regions.", config.InstanceType))
|
||||
|
||||
state.Put("error", fmt.Errorf("The instance type %s isn't available in this region."+
|
||||
"\n You can change to other regions.", config.InstanceType))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
}
|
||||
if config.CidrBlock == "" {
|
||||
s.CidrBlock = "172.16.0.0/24" //use the default CirdBlock
|
||||
}
|
||||
ui.Say("Creating vswitch...")
|
||||
vswitchId, err := client.CreateVSwitch(&ecs.CreateVSwitchArgs{
|
||||
CidrBlock: s.CidrBlock,
|
||||
ZoneId: s.ZoneId,
|
||||
VpcId: vpcId,
|
||||
VSwitchName: s.VSwitchName,
|
||||
})
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Say(fmt.Sprintf("Create vswitch failed %v", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
if err := client.WaitForVSwitchAvailable(vpcId, s.VSwitchId, ALICLOUD_DEFAULT_TIMEOUT); err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Error(fmt.Sprintf("Timeout waiting for vswitch to become avaiable: %v", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
state.Put("vswitchid", vswitchId)
|
||||
s.isCreate = true
|
||||
s.VSwitchId = vswitchId
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepConfigAlicloudVSwitch) Cleanup(state multistep.StateBag) {
|
||||
if !s.isCreate {
|
||||
return
|
||||
}
|
||||
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
message(state, "vSwitch")
|
||||
start := time.Now().Add(10 * time.Second)
|
||||
for {
|
||||
if err := client.DeleteVSwitch(s.VSwitchId); err != nil {
|
||||
e, _ := err.(*common.Error)
|
||||
if (e.Code == "IncorrectVSwitchStatus" || e.Code == "DependencyViolation" ||
|
||||
e.Code == "DependencyViolation.HaVip" ||
|
||||
e.Code == "IncorretRouteEntryStatus") && time.Now().Before(start) {
|
||||
time.Sleep(1 * time.Second)
|
||||
continue
|
||||
}
|
||||
ui.Error(fmt.Sprintf("Error deleting vswitch, it may still be around: %s", err))
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
type stepCreateAlicloudImage struct {
|
||||
image *ecs.ImageType
|
||||
}
|
||||
|
||||
func (s *stepCreateAlicloudImage) Run(state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(Config)
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
// Create the alicloud image
|
||||
ui.Say(fmt.Sprintf("Creating image: %s", config.AlicloudImageName))
|
||||
var imageId string
|
||||
var err error
|
||||
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
imageId, err = client.CreateImage(&ecs.CreateImageArgs{
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
InstanceId: instance.InstanceId,
|
||||
ImageName: config.AlicloudImageName,
|
||||
ImageVersion: config.AlicloudImageVersion,
|
||||
Description: config.AlicloudImageDescription})
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating image: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
err = client.WaitForImageReady(common.Region(config.AlicloudRegion),
|
||||
imageId, ALICLOUD_DEFAULT_LONG_TIMEOUT)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Timeout waiting for image to be created: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
images, _, err := client.DescribeImages(&ecs.DescribeImagesArgs{
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
ImageId: imageId})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error querying created image: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if len(images) == 0 {
|
||||
err := fmt.Errorf("Unable to find created image: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
s.image = &images[0]
|
||||
|
||||
state.Put("alicloudimage", imageId)
|
||||
alicloudImages := make(map[string]string)
|
||||
alicloudImages[config.AlicloudRegion] = images[0].ImageId
|
||||
state.Put("alicloudimages", alicloudImages)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepCreateAlicloudImage) Cleanup(state multistep.StateBag) {
|
||||
if s.image == nil {
|
||||
return
|
||||
}
|
||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
||||
_, halted := state.GetOk(multistep.StateHalted)
|
||||
if !cancelled && !halted {
|
||||
return
|
||||
}
|
||||
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
config := state.Get("config").(Config)
|
||||
|
||||
ui.Say("Deleting the image because of cancellation or error...")
|
||||
if err := client.DeleteImage(common.Region(config.AlicloudRegion), s.image.ImageId); err != nil {
|
||||
ui.Error(fmt.Sprintf("Error deleting image, it may still be around: %s", err))
|
||||
return
|
||||
}
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
type stepCreateAlicloudInstance struct {
|
||||
IOOptimized bool
|
||||
InstanceType string
|
||||
UserData string
|
||||
UserDataFile string
|
||||
instanceId string
|
||||
RegionId string
|
||||
InternetChargeType string
|
||||
InternetMaxBandwidthOut int
|
||||
InstnaceName string
|
||||
ZoneId string
|
||||
instance *ecs.InstanceAttributesType
|
||||
}
|
||||
|
||||
func (s *stepCreateAlicloudInstance) Run(state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
config := state.Get("config").(Config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
source_image := state.Get("source_image").(*ecs.ImageType)
|
||||
network_type := state.Get("networktype").(InstanceNetWork)
|
||||
securityGroupId := state.Get("securitygroupid").(string)
|
||||
var instanceId string
|
||||
var err error
|
||||
|
||||
ioOptimized := ecs.IoOptimizedNone
|
||||
if s.IOOptimized {
|
||||
ioOptimized = ecs.IoOptimizedOptimized
|
||||
}
|
||||
password := config.Comm.SSHPassword
|
||||
if password == "" && config.Comm.WinRMPassword != "" {
|
||||
password = config.Comm.WinRMPassword
|
||||
}
|
||||
ui.Say("Creating instance.")
|
||||
if network_type == VpcNet {
|
||||
userData, err := s.getUserData(state)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
vswitchId := state.Get("vswitchid").(string)
|
||||
instanceId, err = client.CreateInstance(&ecs.CreateInstanceArgs{
|
||||
RegionId: common.Region(s.RegionId),
|
||||
ImageId: source_image.ImageId,
|
||||
InstanceType: s.InstanceType,
|
||||
InternetChargeType: common.InternetChargeType(s.InternetChargeType), //"PayByTraffic",
|
||||
InternetMaxBandwidthOut: s.InternetMaxBandwidthOut,
|
||||
UserData: userData,
|
||||
IoOptimized: ioOptimized,
|
||||
VSwitchId: vswitchId,
|
||||
SecurityGroupId: securityGroupId,
|
||||
InstanceName: s.InstnaceName,
|
||||
Password: password,
|
||||
ZoneId: s.ZoneId,
|
||||
DataDisk: diskDeviceToDiskType(config.AlicloudImageConfig.ECSImagesDiskMappings),
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating instance: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
} else {
|
||||
if s.InstanceType == "" {
|
||||
s.InstanceType = "PayByTraffic"
|
||||
}
|
||||
if s.InternetMaxBandwidthOut == 0 {
|
||||
s.InternetMaxBandwidthOut = 5
|
||||
}
|
||||
instanceId, err = client.CreateInstance(&ecs.CreateInstanceArgs{
|
||||
RegionId: common.Region(s.RegionId),
|
||||
ImageId: source_image.ImageId,
|
||||
InstanceType: s.InstanceType,
|
||||
InternetChargeType: common.InternetChargeType(s.InternetChargeType), //"PayByTraffic",
|
||||
InternetMaxBandwidthOut: s.InternetMaxBandwidthOut,
|
||||
IoOptimized: ioOptimized,
|
||||
SecurityGroupId: securityGroupId,
|
||||
InstanceName: s.InstnaceName,
|
||||
Password: password,
|
||||
ZoneId: s.ZoneId,
|
||||
DataDisk: diskDeviceToDiskType(config.AlicloudImageConfig.ECSImagesDiskMappings),
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating instance: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
err = client.WaitForInstance(instanceId, ecs.Stopped, ALICLOUD_DEFAULT_TIMEOUT)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating instance: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
instance, err := client.DescribeInstanceAttribute(instanceId)
|
||||
if err != nil {
|
||||
ui.Say(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
s.instance = instance
|
||||
state.Put("instance", instance)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepCreateAlicloudInstance) Cleanup(state multistep.StateBag) {
|
||||
if s.instance == nil {
|
||||
return
|
||||
}
|
||||
message(state, "instance")
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
err := client.DeleteInstance(s.instance.InstanceId)
|
||||
if err != nil {
|
||||
ui.Say(fmt.Sprintf("Failed to clean up instance %s: %v", s.instance.InstanceId, err.Error()))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *stepCreateAlicloudInstance) getUserData(state multistep.StateBag) (string, error) {
|
||||
userData := s.UserData
|
||||
if s.UserDataFile != "" {
|
||||
data, err := ioutil.ReadFile(s.UserDataFile)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
userData = string(data)
|
||||
}
|
||||
log.Printf(userData)
|
||||
return userData, nil
|
||||
|
||||
}
|
||||
|
||||
func diskDeviceToDiskType(diskDevices []AlicloudDiskDevice) []ecs.DataDiskType {
|
||||
result := make([]ecs.DataDiskType, len(diskDevices))
|
||||
for _, diskDevice := range diskDevices {
|
||||
result = append(result, ecs.DataDiskType{
|
||||
DiskName: diskDevice.DiskName,
|
||||
Category: ecs.DiskCategory(diskDevice.DiskCategory),
|
||||
Size: diskDevice.DiskSize,
|
||||
SnapshotId: diskDevice.SnapshotId,
|
||||
Description: diskDevice.Description,
|
||||
DeleteWithInstance: diskDevice.DeleteWithInstance,
|
||||
Device: diskDevice.Device,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
type stepDeleteAlicloudImageSnapshots struct {
|
||||
AlicloudImageForceDetele bool
|
||||
AlicloudImageForceDeteleSnapshots bool
|
||||
AlicloudImageName string
|
||||
}
|
||||
|
||||
func (s *stepDeleteAlicloudImageSnapshots) Run(state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
config := state.Get("config").(Config)
|
||||
ui.Say("Deleting image snapshots.")
|
||||
// Check for force delete
|
||||
if s.AlicloudImageForceDetele {
|
||||
images, _, err := client.DescribeImages(&ecs.DescribeImagesArgs{
|
||||
RegionId: common.Region(config.AlicloudRegion),
|
||||
ImageName: s.AlicloudImageName,
|
||||
})
|
||||
if len(images) < 1 {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
for _, image := range images {
|
||||
if image.ImageOwnerAlias != string(ecs.ImageOwnerSelf) {
|
||||
log.Printf("You can only delete instances based on customized images %s ", image.ImageId)
|
||||
continue
|
||||
}
|
||||
err = client.DeleteImage(common.Region(config.AlicloudRegion), image.ImageId)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Failed to delete image: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
if s.AlicloudImageForceDeteleSnapshots {
|
||||
for _, diskDevice := range image.DiskDeviceMappings.DiskDeviceMapping {
|
||||
if err := client.DeleteSnapshot(diskDevice.SnapshotId); err != nil {
|
||||
err := fmt.Errorf("Deleting ECS snapshot failed: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepDeleteAlicloudImageSnapshots) Cleanup(state multistep.StateBag) {
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
type stepMountAlicloudDisk struct {
|
||||
}
|
||||
|
||||
func (s *stepMountAlicloudDisk) Run(state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
config := state.Get("config").(Config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
alicloudDiskDevices := config.ECSImagesDiskMappings
|
||||
if len(config.ECSImagesDiskMappings) == 0 {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
ui.Say("Mounting disks.")
|
||||
disks, _, err := client.DescribeDisks(&ecs.DescribeDisksArgs{InstanceId: instance.InstanceId,
|
||||
RegionId: instance.RegionId})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error querying disks: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
for _, disk := range disks {
|
||||
if disk.Status == ecs.DiskStatusAvailable {
|
||||
if err := client.AttachDisk(&ecs.AttachDiskArgs{DiskId: disk.DiskId,
|
||||
InstanceId: instance.InstanceId,
|
||||
Device: getDevice(&disk, alicloudDiskDevices),
|
||||
}); err != nil {
|
||||
err := fmt.Errorf("Error mounting disks: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, disk := range disks {
|
||||
if err := client.WaitForDisk(instance.RegionId, disk.DiskId, ecs.DiskStatusInUse, ALICLOUD_DEFAULT_SHORT_TIMEOUT); err != nil {
|
||||
err := fmt.Errorf("Timeout waiting for mount: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
ui.Say("Finished mounting disks.")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepMountAlicloudDisk) Cleanup(state multistep.StateBag) {
|
||||
|
||||
}
|
||||
|
||||
func getDevice(disk *ecs.DiskItemType, diskDevices []AlicloudDiskDevice) string {
|
||||
if disk.Device != "" {
|
||||
return disk.Device
|
||||
}
|
||||
for _, alicloudDiskDevice := range diskDevices {
|
||||
if alicloudDiskDevice.DiskName == disk.DiskName || alicloudDiskDevice.SnapshotId == disk.SourceSnapshotId {
|
||||
return alicloudDiskDevice.Device
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
type stepPreValidate struct {
|
||||
AlicloudDestImageName string
|
||||
ForceDelete bool
|
||||
}
|
||||
|
||||
func (s *stepPreValidate) Run(state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
if s.ForceDelete {
|
||||
ui.Say("Force delete flag found, skipping prevalidating image name.")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
config := state.Get("config").(Config)
|
||||
ui.Say("Prevalidating image name...")
|
||||
images, _, err := client.DescribeImages(&ecs.DescribeImagesArgs{
|
||||
ImageName: s.AlicloudDestImageName,
|
||||
RegionId: common.Region(config.AlicloudRegion)})
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error querying alicloud image: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if len(images) > 0 {
|
||||
err := fmt.Errorf("Error: name conflicts with an existing alicloud image: %s", images[0].ImageId)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepPreValidate) Cleanup(multistep.StateBag) {}
|
|
@ -0,0 +1,71 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
type setpRegionCopyAlicloudImage struct {
|
||||
AlicloudImageDestinationRegions []string
|
||||
AlicloudImageDestinationNames []string
|
||||
RegionId string
|
||||
}
|
||||
|
||||
func (s *setpRegionCopyAlicloudImage) Run(state multistep.StateBag) multistep.StepAction {
|
||||
if len(s.AlicloudImageDestinationRegions) == 0 {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
imageId := state.Get("alicloudimage").(string)
|
||||
alicloudImages := state.Get("alicloudimages").(map[string]string)
|
||||
region := common.Region(s.RegionId)
|
||||
|
||||
numberOfName := len(s.AlicloudImageDestinationNames)
|
||||
for index, destinationRegion := range s.AlicloudImageDestinationRegions {
|
||||
if destinationRegion == s.RegionId {
|
||||
continue
|
||||
}
|
||||
ecsImageName := ""
|
||||
if numberOfName > 0 && index < numberOfName {
|
||||
ecsImageName = s.AlicloudImageDestinationNames[index]
|
||||
}
|
||||
imageId, err := client.CopyImage(
|
||||
&ecs.CopyImageArgs{
|
||||
RegionId: region,
|
||||
ImageId: imageId,
|
||||
DestinationRegionId: common.Region(destinationRegion),
|
||||
DestinationImageName: ecsImageName,
|
||||
})
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Say(fmt.Sprintf("Error copying images: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
alicloudImages[destinationRegion] = imageId
|
||||
}
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *setpRegionCopyAlicloudImage) Cleanup(state multistep.StateBag) {
|
||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
||||
_, halted := state.GetOk(multistep.StateHalted)
|
||||
if cancelled || halted {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
alicloudImages := state.Get("alicloudimages").(map[string]string)
|
||||
ui.Say(fmt.Sprintf("Stopping copy image because cancellation or error..."))
|
||||
for copyedRegionId, copyedImageId := range alicloudImages {
|
||||
if copyedRegionId == s.RegionId {
|
||||
continue
|
||||
}
|
||||
if err := client.CancelCopyImage(common.Region(copyedRegionId), copyedImageId); err != nil {
|
||||
ui.Say(fmt.Sprintf("Error cancelling copy image: %v", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
type stepRunAlicloudInstance struct {
|
||||
}
|
||||
|
||||
func (s *stepRunAlicloudInstance) Run(state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
|
||||
err := client.StartInstance(instance.InstanceId)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error starting instance: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
ui.Say("Starting instance.")
|
||||
err = client.WaitForInstance(instance.InstanceId, ecs.Running, ALICLOUD_DEFAULT_TIMEOUT)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Timeout waiting for instance to start: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepRunAlicloudInstance) Cleanup(state multistep.StateBag) {
|
||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
||||
_, halted := state.GetOk(multistep.StateHalted)
|
||||
if cancelled || halted {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
instanceAttrubite, _ := client.DescribeInstanceAttribute(instance.InstanceId)
|
||||
if instanceAttrubite.Status == ecs.Starting || instanceAttrubite.Status == ecs.Running {
|
||||
if err := client.StopInstance(instance.InstanceId, true); err != nil {
|
||||
ui.Say(fmt.Sprintf("Error stopping instance %s, it may still be around %s", instance.InstanceId, err))
|
||||
return
|
||||
}
|
||||
if err := client.WaitForInstance(instance.InstanceId, ecs.Stopped, ALICLOUD_DEFAULT_TIMEOUT); err != nil {
|
||||
ui.Say(fmt.Sprintf("Error stopping instance %s, it may still be around %s", instance.InstanceId, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
type setpShareAlicloudImage struct {
|
||||
AlicloudImageShareAccounts []string
|
||||
AlicloudImageUNShareAccounts []string
|
||||
RegionId string
|
||||
}
|
||||
|
||||
func (s *setpShareAlicloudImage) Run(state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
alicloudImages := state.Get("alicloudimages").(map[string]string)
|
||||
for copyedRegion, copyedImageId := range alicloudImages {
|
||||
err := client.ModifyImageSharePermission(
|
||||
&ecs.ModifyImageSharePermissionArgs{
|
||||
RegionId: common.Region(copyedRegion),
|
||||
ImageId: copyedImageId,
|
||||
AddAccount: s.AlicloudImageShareAccounts,
|
||||
RemoveAccount: s.AlicloudImageUNShareAccounts,
|
||||
})
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Say(fmt.Sprintf("Failed modifying image share permissions: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *setpShareAlicloudImage) Cleanup(state multistep.StateBag) {
|
||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
||||
_, halted := state.GetOk(multistep.StateHalted)
|
||||
if cancelled || halted {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
alicloudImages := state.Get("alicloudimages").(map[string]string)
|
||||
ui.Say("Restoring image share permission because cancellations or error...")
|
||||
for copyedRegion, copyedImageId := range alicloudImages {
|
||||
err := client.ModifyImageSharePermission(
|
||||
&ecs.ModifyImageSharePermissionArgs{
|
||||
RegionId: common.Region(copyedRegion),
|
||||
ImageId: copyedImageId,
|
||||
AddAccount: s.AlicloudImageUNShareAccounts,
|
||||
RemoveAccount: s.AlicloudImageShareAccounts,
|
||||
})
|
||||
if err != nil {
|
||||
ui.Say(fmt.Sprintf("Restoring image share permission failed: %s", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
type stepStopAlicloudInstance struct {
|
||||
ForceStop bool
|
||||
}
|
||||
|
||||
func (s *stepStopAlicloudInstance) Run(state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("client").(*ecs.Client)
|
||||
instance := state.Get("instance").(*ecs.InstanceAttributesType)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
err := client.StopInstance(instance.InstanceId, s.ForceStop)
|
||||
if err != nil {
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error stopping alicloud instance: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
err = client.WaitForInstance(instance.InstanceId, ecs.Stopped, ALICLOUD_DEFAULT_TIMEOUT)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error waiting for alicloud instance to stop: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepStopAlicloudInstance) Cleanup(multistep.StateBag) {
|
||||
// No cleanup...
|
||||
}
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/packer/plugin"
|
||||
|
||||
alicloudecsbuilder "github.com/hashicorp/packer/builder/alicloud/ecs"
|
||||
amazonchrootbuilder "github.com/hashicorp/packer/builder/amazon/chroot"
|
||||
amazonebsbuilder "github.com/hashicorp/packer/builder/amazon/ebs"
|
||||
amazonebssurrogatebuilder "github.com/hashicorp/packer/builder/amazon/ebssurrogate"
|
||||
|
@ -37,6 +38,7 @@ import (
|
|||
virtualboxovfbuilder "github.com/hashicorp/packer/builder/virtualbox/ovf"
|
||||
vmwareisobuilder "github.com/hashicorp/packer/builder/vmware/iso"
|
||||
vmwarevmxbuilder "github.com/hashicorp/packer/builder/vmware/vmx"
|
||||
alicloudimportpostprocessor "github.com/hashicorp/packer/post-processor/alicloud-import"
|
||||
amazonimportpostprocessor "github.com/hashicorp/packer/post-processor/amazon-import"
|
||||
artificepostprocessor "github.com/hashicorp/packer/post-processor/artifice"
|
||||
atlaspostprocessor "github.com/hashicorp/packer/post-processor/atlas"
|
||||
|
@ -73,6 +75,7 @@ type PluginCommand struct {
|
|||
}
|
||||
|
||||
var Builders = map[string]packer.Builder{
|
||||
"alicloud-ecs": new(alicloudecsbuilder.Builder),
|
||||
"amazon-chroot": new(amazonchrootbuilder.Builder),
|
||||
"amazon-ebs": new(amazonebsbuilder.Builder),
|
||||
"amazon-ebssurrogate": new(amazonebssurrogatebuilder.Builder),
|
||||
|
@ -117,6 +120,7 @@ var Provisioners = map[string]packer.Provisioner{
|
|||
}
|
||||
|
||||
var PostProcessors = map[string]packer.PostProcessor{
|
||||
"alicloud-import": new(alicloudimportpostprocessor.PostProcessor),
|
||||
"amazon-import": new(amazonimportpostprocessor.PostProcessor),
|
||||
"artifice": new(artificepostprocessor.PostProcessor),
|
||||
"atlas": new(atlaspostprocessor.PostProcessor),
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"variables": {
|
||||
"access_key": "{{env `ALICLOUD_ACCESS_KEY`}}",
|
||||
"secret_key": "{{env `ALICLOUD_SECRET_KEY`}}"
|
||||
},
|
||||
"builders": [{
|
||||
"type":"alicloud-ecs",
|
||||
"access_key":"{{user `access_key`}}",
|
||||
"secret_key":"{{user `secret_key`}}",
|
||||
"region":"cn-beijing",
|
||||
"image_name":"packer_basi",
|
||||
"source_image":"ubuntu_16_0402_64_40G_base_20170222.vhd",
|
||||
"ssh_username":"root",
|
||||
"instance_type":"ecs.n1.tiny",
|
||||
"io_optimized":"true"
|
||||
}],
|
||||
"provisioners": [{
|
||||
"type": "shell",
|
||||
"inline": [
|
||||
"sleep 30",
|
||||
"apt-get update -yy"
|
||||
]
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"variables": {
|
||||
"access_key": "{{env `ALICLOUD_ACCESS_KEY`}}",
|
||||
"secret_key": "{{env `ALICLOUD_SECRET_KEY`}}"
|
||||
},
|
||||
"builders": [{
|
||||
"type":"alicloud-ecs",
|
||||
"access_key":"{{user `access_key`}}",
|
||||
"secret_key":"{{user `secret_key`}}",
|
||||
"region":"cn-beijing",
|
||||
"image_name":"packer_test",
|
||||
"source_image":"win2012_64_datactr_r2_en_40G_alibase_20160622.vhd",
|
||||
"instance_type":"ecs.n1.tiny",
|
||||
"io_optimized":"true",
|
||||
"image_force_delete":"true",
|
||||
"communicator": "winrm",
|
||||
"winrm_port": 5985,
|
||||
"winrm_username": "Administrator",
|
||||
"winrm_password": "Test1234"
|
||||
}],
|
||||
"provisioners": [{
|
||||
"type": "powershell",
|
||||
"inline": ["dir c:\\"]
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"variables": {
|
||||
"access_key": "{{env `ALICLOUD_ACCESS_KEY`}}",
|
||||
"secret_key": "{{env `ALICLOUD_SECRET_KEY`}}"
|
||||
},
|
||||
"builders": [{
|
||||
"type":"alicloud-ecs",
|
||||
"access_key":"{{user `access_key`}}",
|
||||
"secret_key":"{{user `secret_key`}}",
|
||||
"region":"cn-beijing",
|
||||
"image_name":"packer_with_data_disk",
|
||||
"source_image":"ubuntu_16_0402_64_40G_base_20170222.vhd",
|
||||
"ssh_username":"root",
|
||||
"instance_type":"ecs.n1.tiny",
|
||||
"io_optimized":"true",
|
||||
"image_disk_mappings":[{"disk_name":"data1","disk_size":20},{"disk_name":"data1","disk_size":20,"disk_device":"/dev/xvdz"}]
|
||||
}],
|
||||
"provisioners": [{
|
||||
"type": "shell",
|
||||
"inline": [
|
||||
"sleep 30",
|
||||
"apt-get update -yy"
|
||||
]
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"variables": {
|
||||
"access_key": "{{env `ALICLOUD_ACCESS_KEY`}}",
|
||||
"secret_key": "{{env `ALICLOUD_SECRET_KEY`}}"
|
||||
},
|
||||
"builders": [{
|
||||
"type":"alicloud-ecs",
|
||||
"access_key":"{{user `access_key`}}",
|
||||
"secret_key":"{{user `secret_key`}}",
|
||||
"region":"cn-beijing",
|
||||
"image_name":"packer_chef2",
|
||||
"source_image":"ubuntu_14_0405_64_40G_base_20170222.vhd",
|
||||
"ssh_username":"root",
|
||||
"instance_type":"ecs.n1.medium",
|
||||
"io_optimized":"true",
|
||||
"image_force_delete":"true",
|
||||
"ssh_password":"Test1234",
|
||||
"user_data_file":"examples/alicloud/chef/user_data.sh"
|
||||
}],
|
||||
"provisioners": [{
|
||||
"type": "file",
|
||||
"source": "examples/alicloud/chef/chef.sh",
|
||||
"destination": "/root/"
|
||||
},{
|
||||
"type": "shell",
|
||||
"inline": [
|
||||
"cd /root/",
|
||||
"chmod 755 chef.sh",
|
||||
"./chef.sh",
|
||||
"chef-server-ctl reconfigure"
|
||||
]
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
#!/bin/sh
|
||||
HOSTNAME=`ifconfig eth1|grep 'inet addr'|cut -d ":" -f2|cut -d " " -f1`
|
||||
if [ not $HOSTNAME ] ; then
|
||||
HOSTNAME=`ifconfig eth0|grep 'inet addr'|cut -d ":" -f2|cut -d " " -f1`
|
||||
fi
|
||||
CHEF_SERVER_URL='http://dubbo.oss-cn-shenzhen.aliyuncs.com/chef-server-core_12.8.0-1_amd64.deb'
|
||||
CHEF_CONSOLE_URL='http://dubbo.oss-cn-shenzhen.aliyuncs.com/chef-manage_2.4.3-1_amd64.deb'
|
||||
CHEF_SERVER_ADMIN='admin'
|
||||
CHEF_SERVER_ADMIN_PASSWORD='vmADMIN123'
|
||||
ORGANIZATION='aliyun'
|
||||
ORGANIZATION_FULL_NAME='Aliyun, Inc'
|
||||
#specify hostname
|
||||
hostname $HOSTNAME
|
||||
|
||||
mkdir ~/.pemfile
|
||||
#install chef server
|
||||
wget $CHEF_SERVER_URL
|
||||
sudo dpkg -i chef-server-core_*.deb
|
||||
sudo chef-server-ctl reconfigure
|
||||
|
||||
#create admin user
|
||||
sudo chef-server-ctl user-create $CHEF_SERVER_ADMIN $CHEF_SERVER_ADMIN $CHEF_SERVER_ADMIN 641002259@qq.com $CHEF_SERVER_ADMIN_PASSWORD -f ~/.pemfile/admin.pem
|
||||
|
||||
#create aliyun organization
|
||||
sudo chef-server-ctl org-create $ORGANIZATION $ORGANIZATION_FULL_NAME --association_user $CHEF_SERVER_ADMIN -f ~/.pemfile/aliyun-validator.pem
|
||||
|
||||
#install chef management console
|
||||
wget $CHEF_CONSOLE_URL
|
||||
sudo dpkg -i chef-manage_*.deb
|
||||
sudo chef-server-ctl reconfigure
|
||||
|
||||
type expect >/dev/null 2>&1 || { echo >&2 "Install Expect..."; apt-get -y install expect; }
|
||||
echo "spawn sudo chef-manage-ctl reconfigure" >> chef-manage-confirm.exp
|
||||
echo "expect \"*Press any key to continue\"" >> chef-manage-confirm.exp
|
||||
echo "send \"a\\\n\"" >> chef-manage-confirm.exp
|
||||
echo "expect \".*chef-manage 2.4.3 license: \\\"Chef-MLSA\\\".*\"" >> chef-manage-confirm.exp
|
||||
echo "send \"q\"" >> chef-manage-confirm.exp
|
||||
echo "expect \".*Type 'yes' to accept the software license agreement, or anything else to cancel.\"" >> chef-manage-confirm.exp
|
||||
echo "send \"yes\\\n\"" >> chef-manage-confirm.exp
|
||||
echo "interact" >> chef-manage-confirm.exp
|
||||
expect chef-manage-confirm.exp
|
||||
rm -f chef-manage-confirm.exp
|
||||
|
||||
#clean
|
||||
rm -rf chef-manage_2.4.3-1_amd64.deb
|
||||
rm -rf chef-server-core_12.8.0-1_amd64.deb
|
|
@ -0,0 +1,6 @@
|
|||
HOSTNAME=`ifconfig eth1|grep 'inet addr'|cut -d ":" -f2|cut -d " " -f1`
|
||||
if [ not $HOSTNAME ] ; then
|
||||
HOSTNAME=`ifconfig eth0|grep 'inet addr'|cut -d ":" -f2|cut -d " " -f1`
|
||||
fi
|
||||
hostname $HOSTNAME
|
||||
chef-server-ctl reconfigure
|
|
@ -0,0 +1,363 @@
|
|||
package alicloudimport
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||||
packercommon "github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/ecs"
|
||||
"github.com/denverdino/aliyungo/ram"
|
||||
packerecs "github.com/hashicorp/packer/builder/alicloud/ecs"
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
)
|
||||
|
||||
const (
|
||||
BuilderId = "packer.post-processor.alicloud-import"
|
||||
OSSSuffix = "oss-"
|
||||
RAWFileFormat = "raw"
|
||||
VHDFileFormat = "vhd"
|
||||
BUSINESSINFO = "packer"
|
||||
AliyunECSImageImportDefaultRolePolicy = `{
|
||||
"Statement": [
|
||||
{
|
||||
"Action": "sts:AssumeRole",
|
||||
"Effect": "Allow",
|
||||
"Principal": {
|
||||
"Service": [
|
||||
"ecs.aliyuncs.com"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"Version": "1"
|
||||
}`
|
||||
)
|
||||
|
||||
// Configuration of this post processor
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
packerecs.Config `mapstructure:",squash"`
|
||||
|
||||
// Variables specific to this post processor
|
||||
OSSBucket string `mapstructure:"oss_bucket_name"`
|
||||
OSSKey string `mapstructure:"oss_key_name"`
|
||||
SkipClean bool `mapstructure:"skip_clean"`
|
||||
Tags map[string]string `mapstructure:"tags"`
|
||||
AlicloudImageName string `mapstructure:"image_name"`
|
||||
AlicloudImageVersion string `mapstructure:"image_version"`
|
||||
AlicloudImageDescription string `mapstructure:"image_description"`
|
||||
AlicloudImageShareAccounts []string `mapstructure:"image_share_account"`
|
||||
AlicloudImageDestinationRegions []string `mapstructure:"image_copy_regions"`
|
||||
OSType string `mapstructure:"image_os_type"`
|
||||
Platform string `mapstructure:"image_platform"`
|
||||
Architecture string `mapstructure:"image_architecture"`
|
||||
Size string `mapstructure:"image_system_size"`
|
||||
Format string `mapstructure:"format"`
|
||||
AlicloudImageForceDetele bool `mapstructure:"image_force_delete"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
type PostProcessor struct {
|
||||
config Config
|
||||
DiskDeviceMapping []ecs.DiskDeviceMapping
|
||||
}
|
||||
|
||||
// Entry point for configuration parsing when we've defined
|
||||
func (p *PostProcessor) Configure(raws ...interface{}) error {
|
||||
err := config.Decode(&p.config, &config.DecodeOpts{
|
||||
Interpolate: true,
|
||||
InterpolateContext: &p.config.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
Exclude: []string{
|
||||
"oss_key_name",
|
||||
},
|
||||
},
|
||||
}, raws...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
errs := new(packer.MultiError)
|
||||
|
||||
// Check and render oss_key_name
|
||||
if err = interpolate.Validate(p.config.OSSKey, &p.config.ctx); err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Error parsing oss_key_name template: %s", err))
|
||||
}
|
||||
|
||||
// Check we have alicloud access variables defined somewhere
|
||||
errs = packer.MultiErrorAppend(errs, p.config.AlicloudAccessConfig.Prepare(&p.config.ctx)...)
|
||||
|
||||
// define all our required parameters
|
||||
templates := map[string]*string{
|
||||
"oss_bucket_name": &p.config.OSSBucket,
|
||||
}
|
||||
// Check out required params are defined
|
||||
for key, ptr := range templates {
|
||||
if *ptr == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("%s must be set", key))
|
||||
}
|
||||
}
|
||||
|
||||
// Anything which flagged return back up the stack
|
||||
if len(errs.Errors) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
log.Println(common.ScrubConfig(p.config, p.config.AlicloudAccessKey, p.config.AlicloudSecretKey))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
|
||||
var err error
|
||||
|
||||
// Render this key since we didn't in the configure phase
|
||||
p.config.OSSKey, err = interpolate.Render(p.config.OSSKey, &p.config.ctx)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("Error rendering oss_key_name template: %s", err)
|
||||
}
|
||||
if p.config.OSSKey == "" {
|
||||
p.config.OSSKey = "Packer_" + strconv.Itoa(time.Now().Nanosecond())
|
||||
}
|
||||
log.Printf("Rendered oss_key_name as %s", p.config.OSSKey)
|
||||
|
||||
log.Println("Looking for RAW or VHD in artifact")
|
||||
// Locate the files output from the builder
|
||||
source := ""
|
||||
for _, path := range artifact.Files() {
|
||||
if strings.HasSuffix(path, VHDFileFormat) || strings.HasSuffix(path, RAWFileFormat) {
|
||||
source = path
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Hope we found something useful
|
||||
if source == "" {
|
||||
return nil, false, fmt.Errorf("No vhd or raw file found in artifact from builder")
|
||||
}
|
||||
|
||||
ecsClient, err := p.config.AlicloudAccessConfig.Client()
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("Failed to connect alicloud ecs %s", err)
|
||||
}
|
||||
ecsClient.SetBusinessInfo(BUSINESSINFO)
|
||||
|
||||
images, _, err := ecsClient.DescribeImages(&ecs.DescribeImagesArgs{
|
||||
RegionId: packercommon.Region(p.config.AlicloudRegion),
|
||||
ImageName: p.config.AlicloudImageName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("Failed to start import from %s/%s: %s",
|
||||
getEndPonit(p.config.OSSBucket), p.config.OSSKey, err)
|
||||
}
|
||||
|
||||
if len(images) > 0 && !p.config.AlicloudImageForceDetele {
|
||||
return nil, false, fmt.Errorf("Duplicated image exists, please delete the existing images " +
|
||||
"or set the 'image_force_delete' value as true")
|
||||
}
|
||||
|
||||
// Set up the OSS client
|
||||
log.Println("Creating OSS Client")
|
||||
client, err := oss.New(getEndPonit(p.config.AlicloudRegion), p.config.AlicloudAccessKey,
|
||||
p.config.AlicloudSecretKey)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("Creating oss connection failed: %s", err)
|
||||
}
|
||||
bucket, err := queryOrCreateBucket(p.config.OSSBucket, client)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("Failed to query or create bucket %s: %s", p.config.OSSBucket, err)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("Failed to open %s: %s", source, err)
|
||||
}
|
||||
|
||||
err = bucket.PutObjectFromFile(p.config.OSSKey, source)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("Failed to upload image %s: %s", source, err)
|
||||
}
|
||||
if len(images) > 0 && p.config.AlicloudImageForceDetele {
|
||||
if err = ecsClient.DeleteImage(packercommon.Region(p.config.AlicloudRegion),
|
||||
images[0].ImageId); err != nil {
|
||||
return nil, false, fmt.Errorf("Delete duplicated image %s failed", images[0].ImageName)
|
||||
}
|
||||
}
|
||||
|
||||
diskDeviceMapping := ecs.DiskDeviceMapping{
|
||||
Size: p.config.Size,
|
||||
Format: p.config.Format,
|
||||
OSSBucket: p.config.OSSBucket,
|
||||
OSSObject: p.config.OSSKey,
|
||||
}
|
||||
imageImageArgs := &ecs.ImportImageArgs{
|
||||
RegionId: packercommon.Region(p.config.AlicloudRegion),
|
||||
ImageName: p.config.AlicloudImageName,
|
||||
ImageVersion: p.config.AlicloudImageVersion,
|
||||
Description: p.config.AlicloudImageDescription,
|
||||
Architecture: p.config.Architecture,
|
||||
OSType: p.config.OSType,
|
||||
Platform: p.config.Platform,
|
||||
}
|
||||
imageImageArgs.DiskDeviceMappings.DiskDeviceMapping = []ecs.DiskDeviceMapping{
|
||||
diskDeviceMapping,
|
||||
}
|
||||
imageId, err := ecsClient.ImportImage(imageImageArgs)
|
||||
|
||||
if err != nil {
|
||||
e, _ := err.(*packercommon.Error)
|
||||
if e.Code == "NoSetRoletoECSServiceAcount" {
|
||||
ramClient := ram.NewClient(p.config.AlicloudAccessKey, p.config.AlicloudSecretKey)
|
||||
roleResponse, err := ramClient.GetRole(ram.RoleQueryRequest{
|
||||
RoleName: "AliyunECSImageImportDefaultRole",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("Failed to start import from %s/%s: %s",
|
||||
getEndPonit(p.config.OSSBucket), p.config.OSSKey, err)
|
||||
}
|
||||
if roleResponse.Role.RoleId == "" {
|
||||
if _, err = ramClient.CreateRole(ram.RoleRequest{
|
||||
RoleName: "AliyunECSImageImportDefaultRole",
|
||||
AssumeRolePolicyDocument: AliyunECSImageImportDefaultRolePolicy,
|
||||
}); err != nil {
|
||||
return nil, false, fmt.Errorf("Failed to start import from %s/%s: %s",
|
||||
getEndPonit(p.config.OSSBucket), p.config.OSSKey, err)
|
||||
}
|
||||
if _, err := ramClient.AttachPolicyToRole(ram.AttachPolicyToRoleRequest{
|
||||
ram.PolicyRequest{
|
||||
PolicyName: "AliyunECSImageImportRolePolicy",
|
||||
PolicyType: "System",
|
||||
}, "AliyunECSImageImportDefaultRole",
|
||||
}); err != nil {
|
||||
return nil, false, fmt.Errorf("Failed to start import from %s/%s: %s",
|
||||
getEndPonit(p.config.OSSBucket), p.config.OSSKey, err)
|
||||
}
|
||||
} else {
|
||||
policyListResponse, err := ramClient.ListPoliciesForRole(ram.RoleQueryRequest{
|
||||
"AliyunECSImageImportDefaultRole",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("Failed to start import from %s/%s: %s",
|
||||
getEndPonit(p.config.OSSBucket), p.config.OSSKey, err)
|
||||
}
|
||||
isAliyunECSImageImportRolePolicyNotExit := true
|
||||
for _, policy := range policyListResponse.Policies.Policy {
|
||||
if policy.PolicyName == "AliyunECSImageImportRolePolicy" &&
|
||||
policy.PolicyType == "System" {
|
||||
isAliyunECSImageImportRolePolicyNotExit = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if isAliyunECSImageImportRolePolicyNotExit {
|
||||
if _, err := ramClient.AttachPolicyToRole(ram.AttachPolicyToRoleRequest{
|
||||
ram.PolicyRequest{
|
||||
PolicyName: "AliyunECSImageImportRolePolicy",
|
||||
PolicyType: "System",
|
||||
}, "AliyunECSImageImportDefaultRole",
|
||||
}); err != nil {
|
||||
return nil, false, fmt.Errorf("Failed to start import from %s/%s: %s",
|
||||
getEndPonit(p.config.OSSBucket), p.config.OSSKey, err)
|
||||
}
|
||||
}
|
||||
if _, err = ramClient.UpdateRole(
|
||||
ram.UpdateRoleRequest{
|
||||
RoleName: "AliyunECSImageImportDefaultRole",
|
||||
NewAssumeRolePolicyDocument: AliyunECSImageImportDefaultRolePolicy,
|
||||
}); err != nil {
|
||||
return nil, false, fmt.Errorf("Failed to start import from %s/%s: %s",
|
||||
getEndPonit(p.config.OSSBucket), p.config.OSSKey, err)
|
||||
}
|
||||
}
|
||||
for i := 10; i > 0; i = i - 1 {
|
||||
imageId, err = ecsClient.ImportImage(imageImageArgs)
|
||||
if err != nil {
|
||||
e, _ = err.(*packercommon.Error)
|
||||
if e.Code == "NoSetRoletoECSServiceAcount" {
|
||||
time.Sleep(5 * time.Second)
|
||||
continue
|
||||
} else if e.Code == "ImageIsImporting" ||
|
||||
e.Code == "InvalidImageName.Duplicated" {
|
||||
break
|
||||
}
|
||||
return nil, false, fmt.Errorf("Failed to start import from %s/%s: %s",
|
||||
getEndPonit(p.config.OSSBucket), p.config.OSSKey, err)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
return nil, false, fmt.Errorf("Failed to start import from %s/%s: %s",
|
||||
getEndPonit(p.config.OSSBucket), p.config.OSSKey, err)
|
||||
}
|
||||
}
|
||||
|
||||
err = ecsClient.WaitForImageReady(packercommon.Region(p.config.AlicloudRegion),
|
||||
imageId, packerecs.ALICLOUD_DEFAULT_LONG_TIMEOUT)
|
||||
// Add the reported Alicloud image ID to the artifact list
|
||||
log.Printf("Importing created alicloud image ID %s in region %s Finished.", imageId, p.config.AlicloudRegion)
|
||||
artifact = &packerecs.Artifact{
|
||||
AlicloudImages: map[string]string{
|
||||
p.config.AlicloudRegion: imageId,
|
||||
},
|
||||
BuilderIdValue: BuilderId,
|
||||
Client: ecsClient,
|
||||
}
|
||||
|
||||
if !p.config.SkipClean {
|
||||
ui.Message(fmt.Sprintf("Deleting import source %s/%s/%s",
|
||||
getEndPonit(p.config.AlicloudRegion), p.config.OSSBucket, p.config.OSSKey))
|
||||
if err = bucket.DeleteObject(p.config.OSSKey); err != nil {
|
||||
return nil, false, fmt.Errorf("Failed to delete %s/%s/%s: %s",
|
||||
getEndPonit(p.config.AlicloudRegion), p.config.OSSBucket, p.config.OSSKey, err)
|
||||
}
|
||||
}
|
||||
|
||||
return artifact, false, nil
|
||||
}
|
||||
|
||||
func queryOrCreateBucket(bucketName string, client *oss.Client) (*oss.Bucket, error) {
|
||||
isExist, err := client.IsBucketExist(bucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isExist {
|
||||
err = client.CreateBucket(bucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
bucket, err := client.Bucket(bucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bucket, nil
|
||||
|
||||
}
|
||||
|
||||
func getEndPonit(region string) string {
|
||||
return "https://" + GetOSSRegion(region) + ".aliyuncs.com"
|
||||
}
|
||||
|
||||
func GetOSSRegion(region string) string {
|
||||
if strings.HasPrefix(region, OSSSuffix) {
|
||||
return region
|
||||
}
|
||||
return OSSSuffix + region
|
||||
}
|
||||
|
||||
func GetECSRegion(region string) string {
|
||||
if strings.HasPrefix(region, OSSSuffix) {
|
||||
return strings.TrimSuffix(region, OSSSuffix)
|
||||
}
|
||||
return region
|
||||
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
package oss
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"hash"
|
||||
"io"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 用于signHeader的字典排序存放容器。
|
||||
type headerSorter struct {
|
||||
Keys []string
|
||||
Vals []string
|
||||
}
|
||||
|
||||
// 生成签名方法(直接设置请求的Header)。
|
||||
func (conn Conn) signHeader(req *http.Request, canonicalizedResource string) {
|
||||
// Find out the "x-oss-"'s address in this request'header
|
||||
temp := make(map[string]string)
|
||||
|
||||
for k, v := range req.Header {
|
||||
if strings.HasPrefix(strings.ToLower(k), "x-oss-") {
|
||||
temp[strings.ToLower(k)] = v[0]
|
||||
}
|
||||
}
|
||||
hs := newHeaderSorter(temp)
|
||||
|
||||
// Sort the temp by the Ascending Order
|
||||
hs.Sort()
|
||||
|
||||
// Get the CanonicalizedOSSHeaders
|
||||
canonicalizedOSSHeaders := ""
|
||||
for i := range hs.Keys {
|
||||
canonicalizedOSSHeaders += hs.Keys[i] + ":" + hs.Vals[i] + "\n"
|
||||
}
|
||||
|
||||
// Give other parameters values
|
||||
date := req.Header.Get(HTTPHeaderDate)
|
||||
contentType := req.Header.Get(HTTPHeaderContentType)
|
||||
contentMd5 := req.Header.Get(HTTPHeaderContentMD5)
|
||||
|
||||
signStr := req.Method + "\n" + contentMd5 + "\n" + contentType + "\n" + date + "\n" + canonicalizedOSSHeaders + canonicalizedResource
|
||||
h := hmac.New(func() hash.Hash { return sha1.New() }, []byte(conn.config.AccessKeySecret))
|
||||
io.WriteString(h, signStr)
|
||||
signedStr := base64.StdEncoding.EncodeToString(h.Sum(nil))
|
||||
|
||||
// Get the final Authorization' string
|
||||
authorizationStr := "OSS " + conn.config.AccessKeyID + ":" + signedStr
|
||||
|
||||
// Give the parameter "Authorization" value
|
||||
req.Header.Set(HTTPHeaderAuthorization, authorizationStr)
|
||||
}
|
||||
|
||||
// Additional function for function SignHeader.
|
||||
func newHeaderSorter(m map[string]string) *headerSorter {
|
||||
hs := &headerSorter{
|
||||
Keys: make([]string, 0, len(m)),
|
||||
Vals: make([]string, 0, len(m)),
|
||||
}
|
||||
|
||||
for k, v := range m {
|
||||
hs.Keys = append(hs.Keys, k)
|
||||
hs.Vals = append(hs.Vals, v)
|
||||
}
|
||||
return hs
|
||||
}
|
||||
|
||||
// Additional function for function SignHeader.
|
||||
func (hs *headerSorter) Sort() {
|
||||
sort.Sort(hs)
|
||||
}
|
||||
|
||||
// Additional function for function SignHeader.
|
||||
func (hs *headerSorter) Len() int {
|
||||
return len(hs.Vals)
|
||||
}
|
||||
|
||||
// Additional function for function SignHeader.
|
||||
func (hs *headerSorter) Less(i, j int) bool {
|
||||
return bytes.Compare([]byte(hs.Keys[i]), []byte(hs.Keys[j])) < 0
|
||||
}
|
||||
|
||||
// Additional function for function SignHeader.
|
||||
func (hs *headerSorter) Swap(i, j int) {
|
||||
hs.Vals[i], hs.Vals[j] = hs.Vals[j], hs.Vals[i]
|
||||
hs.Keys[i], hs.Keys[j] = hs.Keys[j], hs.Keys[i]
|
||||
}
|
|
@ -0,0 +1,633 @@
|
|||
package oss
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"encoding/xml"
|
||||
"hash"
|
||||
"hash/crc64"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Bucket implements the operations of object.
|
||||
type Bucket struct {
|
||||
Client Client
|
||||
BucketName string
|
||||
}
|
||||
|
||||
//
|
||||
// PutObject 新建Object,如果Object已存在,覆盖原有Object。
|
||||
//
|
||||
// objectKey 上传对象的名称,使用UTF-8编码、长度必须在1-1023字节之间、不能以“/”或者“\”字符开头。
|
||||
// reader io.Reader读取object的数据。
|
||||
// options 上传对象时可以指定对象的属性,可用选项有CacheControl、ContentDisposition、ContentEncoding、
|
||||
// Expires、ServerSideEncryption、ObjectACL、Meta,具体含义请参看
|
||||
// https://help.aliyun.com/document_detail/oss/api-reference/object/PutObject.html
|
||||
//
|
||||
// error 操作无错误为nil,非nil为错误信息。
|
||||
//
|
||||
func (bucket Bucket) PutObject(objectKey string, reader io.Reader, options ...Option) error {
|
||||
opts := addContentType(options, objectKey)
|
||||
|
||||
request := &PutObjectRequest{
|
||||
ObjectKey: objectKey,
|
||||
Reader: reader,
|
||||
}
|
||||
resp, err := bucket.DoPutObject(request, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
//
|
||||
// PutObjectFromFile 新建Object,内容从本地文件中读取。
|
||||
//
|
||||
// objectKey 上传对象的名称。
|
||||
// filePath 本地文件,上传对象的值为该文件内容。
|
||||
// options 上传对象时可以指定对象的属性。详见PutObject的options。
|
||||
//
|
||||
// error 操作无错误为nil,非nil为错误信息。
|
||||
//
|
||||
func (bucket Bucket) PutObjectFromFile(objectKey, filePath string, options ...Option) error {
|
||||
fd, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
opts := addContentType(options, filePath, objectKey)
|
||||
|
||||
request := &PutObjectRequest{
|
||||
ObjectKey: objectKey,
|
||||
Reader: fd,
|
||||
}
|
||||
resp, err := bucket.DoPutObject(request, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
//
|
||||
// DoPutObject 上传文件。
|
||||
//
|
||||
// request 上传请求。
|
||||
// options 上传选项。
|
||||
//
|
||||
// Response 上传请求返回值。
|
||||
// error 操作无错误为nil,非nil为错误信息。
|
||||
//
|
||||
func (bucket Bucket) DoPutObject(request *PutObjectRequest, options []Option) (*Response, error) {
|
||||
isOptSet, _, _ := isOptionSet(options, HTTPHeaderContentType)
|
||||
if !isOptSet {
|
||||
options = addContentType(options, request.ObjectKey)
|
||||
}
|
||||
|
||||
listener := getProgressListener(options)
|
||||
|
||||
resp, err := bucket.do("PUT", request.ObjectKey, "", "", options, request.Reader, listener)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if bucket.getConfig().IsEnableCRC {
|
||||
err = checkCRC(resp, "DoPutObject")
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
|
||||
err = checkRespCode(resp.StatusCode, []int{http.StatusOK})
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
//
|
||||
// GetObject 下载文件。
|
||||
//
|
||||
// objectKey 下载的文件名称。
|
||||
// options 对象的属性限制项,可选值有Range、IfModifiedSince、IfUnmodifiedSince、IfMatch、
|
||||
// IfNoneMatch、AcceptEncoding,详细请参考
|
||||
// https://help.aliyun.com/document_detail/oss/api-reference/object/GetObject.html
|
||||
//
|
||||
// io.ReadCloser reader,读取数据后需要close。error为nil时有效。
|
||||
// error 操作无错误为nil,非nil为错误信息。
|
||||
//
|
||||
func (bucket Bucket) GetObject(objectKey string, options ...Option) (io.ReadCloser, error) {
|
||||
result, err := bucket.DoGetObject(&GetObjectRequest{objectKey}, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.Response.Body, nil
|
||||
}
|
||||
|
||||
//
|
||||
// GetObjectToFile 下载文件。
|
||||
//
|
||||
// objectKey 下载的文件名称。
|
||||
// filePath 下载对象的内容写到该本地文件。
|
||||
// options 对象的属性限制项。详见GetObject的options。
|
||||
//
|
||||
// error 操作无错误时返回error为nil,非nil为错误说明。
|
||||
//
|
||||
func (bucket Bucket) GetObjectToFile(objectKey, filePath string, options ...Option) error {
|
||||
tempFilePath := filePath + TempFileSuffix
|
||||
|
||||
// 读取Object内容
|
||||
result, err := bucket.DoGetObject(&GetObjectRequest{objectKey}, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer result.Response.Body.Close()
|
||||
|
||||
// 如果文件不存在则创建,存在则清空
|
||||
fd, err := os.OpenFile(tempFilePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, FilePermMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 存储数据到文件
|
||||
_, err = io.Copy(fd, result.Response.Body)
|
||||
fd.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 比较CRC值
|
||||
hasRange, _, _ := isOptionSet(options, HTTPHeaderRange)
|
||||
if bucket.getConfig().IsEnableCRC && !hasRange {
|
||||
result.Response.ClientCRC = result.ClientCRC.Sum64()
|
||||
err = checkCRC(result.Response, "GetObjectToFile")
|
||||
if err != nil {
|
||||
os.Remove(tempFilePath)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return os.Rename(tempFilePath, filePath)
|
||||
}
|
||||
|
||||
//
|
||||
// DoGetObject 下载文件
|
||||
//
|
||||
// request 下载请求
|
||||
// options 对象的属性限制项。详见GetObject的options。
|
||||
//
|
||||
// GetObjectResult 下载请求返回值。
|
||||
// error 操作无错误为nil,非nil为错误信息。
|
||||
//
|
||||
func (bucket Bucket) DoGetObject(request *GetObjectRequest, options []Option) (*GetObjectResult, error) {
|
||||
resp, err := bucket.do("GET", request.ObjectKey, "", "", options, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := &GetObjectResult{
|
||||
Response: resp,
|
||||
}
|
||||
|
||||
// crc
|
||||
var crcCalc hash.Hash64
|
||||
hasRange, _, _ := isOptionSet(options, HTTPHeaderRange)
|
||||
if bucket.getConfig().IsEnableCRC && !hasRange {
|
||||
crcCalc = crc64.New(crcTable())
|
||||
result.ServerCRC = resp.ServerCRC
|
||||
result.ClientCRC = crcCalc
|
||||
}
|
||||
|
||||
// progress
|
||||
listener := getProgressListener(options)
|
||||
|
||||
contentLen, _ := strconv.ParseInt(resp.Headers.Get(HTTPHeaderContentLength), 10, 64)
|
||||
resp.Body = ioutil.NopCloser(TeeReader(resp.Body, crcCalc, contentLen, listener, nil))
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
//
|
||||
// CopyObject 同一个bucket内拷贝Object。
|
||||
//
|
||||
// srcObjectKey Copy的源对象。
|
||||
// destObjectKey Copy的目标对象。
|
||||
// options Copy对象时,您可以指定源对象的限制条件,满足限制条件时copy,不满足时返回错误,您可以选择如下选项CopySourceIfMatch、
|
||||
// CopySourceIfNoneMatch、CopySourceIfModifiedSince、CopySourceIfUnmodifiedSince、MetadataDirective。
|
||||
// Copy对象时,您可以指定目标对象的属性,如CacheControl、ContentDisposition、ContentEncoding、Expires、
|
||||
// ServerSideEncryption、ObjectACL、Meta,选项的含义请参看
|
||||
// https://help.aliyun.com/document_detail/oss/api-reference/object/CopyObject.html
|
||||
//
|
||||
// error 操作无错误为nil,非nil为错误信息。
|
||||
//
|
||||
func (bucket Bucket) CopyObject(srcObjectKey, destObjectKey string, options ...Option) (CopyObjectResult, error) {
|
||||
var out CopyObjectResult
|
||||
options = append(options, CopySource(bucket.BucketName, url.QueryEscape(srcObjectKey)))
|
||||
resp, err := bucket.do("PUT", destObjectKey, "", "", options, nil, nil)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = xmlUnmarshal(resp.Body, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
//
|
||||
// CopyObjectTo bucket间拷贝object。
|
||||
//
|
||||
// srcObjectKey 源Object名称。源Bucket名称为Bucket.BucketName。
|
||||
// destBucketName 目标Bucket名称。
|
||||
// destObjectKey 目标Object名称。
|
||||
// options Copy选项,详见CopyObject的options。
|
||||
//
|
||||
// error 操作无错误为nil,非nil为错误信息。
|
||||
//
|
||||
func (bucket Bucket) CopyObjectTo(destBucketName, destObjectKey, srcObjectKey string, options ...Option) (CopyObjectResult, error) {
|
||||
return bucket.copy(srcObjectKey, destBucketName, destObjectKey, options...)
|
||||
}
|
||||
|
||||
//
|
||||
// CopyObjectFrom bucket间拷贝object。
|
||||
//
|
||||
// srcBucketName 源Bucket名称。
|
||||
// srcObjectKey 源Object名称。
|
||||
// destObjectKey 目标Object名称。目标Bucket名称为Bucket.BucketName。
|
||||
// options Copy选项,详见CopyObject的options。
|
||||
//
|
||||
// error 操作无错误为nil,非nil为错误信息。
|
||||
//
|
||||
func (bucket Bucket) CopyObjectFrom(srcBucketName, srcObjectKey, destObjectKey string, options ...Option) (CopyObjectResult, error) {
|
||||
destBucketName := bucket.BucketName
|
||||
var out CopyObjectResult
|
||||
srcBucket, err := bucket.Client.Bucket(srcBucketName)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
|
||||
return srcBucket.copy(srcObjectKey, destBucketName, destObjectKey, options...)
|
||||
}
|
||||
|
||||
func (bucket Bucket) copy(srcObjectKey, destBucketName, destObjectKey string, options ...Option) (CopyObjectResult, error) {
|
||||
var out CopyObjectResult
|
||||
options = append(options, CopySource(bucket.BucketName, url.QueryEscape(srcObjectKey)))
|
||||
headers := make(map[string]string)
|
||||
err := handleOptions(headers, options)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
resp, err := bucket.Client.Conn.Do("PUT", destBucketName, destObjectKey, "", "", headers, nil, 0, nil)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = xmlUnmarshal(resp.Body, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
//
|
||||
// AppendObject 追加方式上传。
|
||||
//
|
||||
// AppendObject参数必须包含position,其值指定从何处进行追加。首次追加操作的position必须为0,
|
||||
// 后续追加操作的position是Object的当前长度。例如,第一次Append Object请求指定position值为0,
|
||||
// content-length是65536;那么,第二次Append Object需要指定position为65536。
|
||||
// 每次操作成功后,响应头部x-oss-next-append-position也会标明下一次追加的position。
|
||||
//
|
||||
// objectKey 需要追加的Object。
|
||||
// reader io.Reader,读取追的内容。
|
||||
// appendPosition object追加的起始位置。
|
||||
// destObjectProperties 第一次追加时指定新对象的属性,如CacheControl、ContentDisposition、ContentEncoding、
|
||||
// Expires、ServerSideEncryption、ObjectACL。
|
||||
//
|
||||
// int64 下次追加的开始位置,error为nil空时有效。
|
||||
// error 操作无错误为nil,非nil为错误信息。
|
||||
//
|
||||
func (bucket Bucket) AppendObject(objectKey string, reader io.Reader, appendPosition int64, options ...Option) (int64, error) {
|
||||
request := &AppendObjectRequest{
|
||||
ObjectKey: objectKey,
|
||||
Reader: reader,
|
||||
Position: appendPosition,
|
||||
}
|
||||
|
||||
result, err := bucket.DoAppendObject(request, options)
|
||||
|
||||
return result.NextPosition, err
|
||||
}
|
||||
|
||||
//
|
||||
// DoAppendObject 追加上传。
|
||||
//
|
||||
// request 追加上传请求。
|
||||
// options 追加上传选项。
|
||||
//
|
||||
// AppendObjectResult 追加上传请求返回值。
|
||||
// error 操作无错误为nil,非nil为错误信息。
|
||||
//
|
||||
func (bucket Bucket) DoAppendObject(request *AppendObjectRequest, options []Option) (*AppendObjectResult, error) {
|
||||
params := "append&position=" + strconv.FormatInt(request.Position, 10)
|
||||
headers := make(map[string]string)
|
||||
|
||||
opts := addContentType(options, request.ObjectKey)
|
||||
handleOptions(headers, opts)
|
||||
|
||||
var initCRC uint64
|
||||
isCRCSet, initCRCOpt, _ := isOptionSet(options, initCRC64)
|
||||
if isCRCSet {
|
||||
initCRC = initCRCOpt.(uint64)
|
||||
}
|
||||
|
||||
listener := getProgressListener(options)
|
||||
|
||||
handleOptions(headers, opts)
|
||||
resp, err := bucket.Client.Conn.Do("POST", bucket.BucketName, request.ObjectKey, params, params, headers,
|
||||
request.Reader, initCRC, listener)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
nextPosition, _ := strconv.ParseInt(resp.Headers.Get(HTTPHeaderOssNextAppendPosition), 10, 64)
|
||||
result := &AppendObjectResult{
|
||||
NextPosition: nextPosition,
|
||||
CRC: resp.ServerCRC,
|
||||
}
|
||||
|
||||
if bucket.getConfig().IsEnableCRC && isCRCSet {
|
||||
err = checkCRC(resp, "AppendObject")
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
//
|
||||
// DeleteObject 删除Object。
|
||||
//
|
||||
// objectKey 待删除Object。
|
||||
//
|
||||
// error 操作无错误为nil,非nil为错误信息。
|
||||
//
|
||||
func (bucket Bucket) DeleteObject(objectKey string) error {
|
||||
resp, err := bucket.do("DELETE", objectKey, "", "", nil, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return checkRespCode(resp.StatusCode, []int{http.StatusNoContent})
|
||||
}
|
||||
|
||||
//
|
||||
// DeleteObjects 批量删除object。
|
||||
//
|
||||
// objectKeys 待删除object类表。
|
||||
// options 删除选项,DeleteObjectsQuiet,是否是安静模式,默认不使用。
|
||||
//
|
||||
// DeleteObjectsResult 非安静模式的的返回值。
|
||||
// error 操作无错误为nil,非nil为错误信息。
|
||||
//
|
||||
func (bucket Bucket) DeleteObjects(objectKeys []string, options ...Option) (DeleteObjectsResult, error) {
|
||||
out := DeleteObjectsResult{}
|
||||
dxml := deleteXML{}
|
||||
for _, key := range objectKeys {
|
||||
dxml.Objects = append(dxml.Objects, DeleteObject{Key: key})
|
||||
}
|
||||
isQuiet, _ := findOption(options, deleteObjectsQuiet, false)
|
||||
dxml.Quiet = isQuiet.(bool)
|
||||
encode := "&encoding-type=url"
|
||||
|
||||
bs, err := xml.Marshal(dxml)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
buffer := new(bytes.Buffer)
|
||||
buffer.Write(bs)
|
||||
|
||||
contentType := http.DetectContentType(buffer.Bytes())
|
||||
options = append(options, ContentType(contentType))
|
||||
sum := md5.Sum(bs)
|
||||
b64 := base64.StdEncoding.EncodeToString(sum[:])
|
||||
options = append(options, ContentMD5(b64))
|
||||
resp, err := bucket.do("POST", "", "delete"+encode, "delete", options, buffer, nil)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if !dxml.Quiet {
|
||||
if err = xmlUnmarshal(resp.Body, &out); err == nil {
|
||||
err = decodeDeleteObjectsResult(&out)
|
||||
}
|
||||
}
|
||||
return out, err
|
||||
}
|
||||
|
||||
//
|
||||
// IsObjectExist object是否存在。
|
||||
//
|
||||
// bool object是否存在,true存在,false不存在。error为nil时有效。
|
||||
//
|
||||
// error 操作无错误为nil,非nil为错误信息。
|
||||
//
|
||||
func (bucket Bucket) IsObjectExist(objectKey string) (bool, error) {
|
||||
listRes, err := bucket.ListObjects(Prefix(objectKey), MaxKeys(1))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if len(listRes.Objects) == 1 && listRes.Objects[0].Key == objectKey {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
//
|
||||
// ListObjects 获得Bucket下筛选后所有的object的列表。
|
||||
//
|
||||
// options ListObject的筛选行为。Prefix指定的前缀、MaxKeys最大数目、Marker第一个开始、Delimiter对Object名字进行分组的字符。
|
||||
//
|
||||
// 您有如下8个object,my-object-1, my-object-11, my-object-2, my-object-21,
|
||||
// my-object-22, my-object-3, my-object-31, my-object-32。如果您指定了Prefix为my-object-2,
|
||||
// 则返回my-object-2, my-object-21, my-object-22三个object。如果您指定了Marker为my-object-22,
|
||||
// 则返回my-object-3, my-object-31, my-object-32三个object。如果您指定MaxKeys则每次最多返回MaxKeys个,
|
||||
// 最后一次可能不足。这三个参数可以组合使用,实现分页等功能。如果把prefix设为某个文件夹名,就可以罗列以此prefix开头的文件,
|
||||
// 即该文件夹下递归的所有的文件和子文件夹。如果再把delimiter设置为"/"时,返回值就只罗列该文件夹下的文件,该文件夹下的子文件名
|
||||
// 返回在CommonPrefixes部分,子文件夹下递归的文件和文件夹不被显示。例如一个bucket存在三个object,fun/test.jpg、
|
||||
// fun/movie/001.avi、fun/movie/007.avi。若设定prefix为"fun/",则返回三个object;如果增加设定
|
||||
// delimiter为"/",则返回文件"fun/test.jpg"和前缀"fun/movie/",即实现了文件夹的逻辑。
|
||||
//
|
||||
// 常用场景,请参数示例sample/list_object.go。
|
||||
//
|
||||
// ListObjectsResponse 操作成功后的返回值,成员Objects为bucket中对象列表。error为nil时该返回值有效。
|
||||
//
|
||||
func (bucket Bucket) ListObjects(options ...Option) (ListObjectsResult, error) {
|
||||
var out ListObjectsResult
|
||||
|
||||
options = append(options, EncodingType("url"))
|
||||
params, err := handleParams(options)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
|
||||
resp, err := bucket.do("GET", "", params, "", nil, nil, nil)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = xmlUnmarshal(resp.Body, &out)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
|
||||
err = decodeListObjectsResult(&out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
//
|
||||
// SetObjectMeta 设置Object的Meta。
|
||||
//
|
||||
// objectKey object
|
||||
// options 指定对象的属性,有以下可选项CacheControl、ContentDisposition、ContentEncoding、Expires、
|
||||
// ServerSideEncryption、Meta。
|
||||
//
|
||||
// error 操作无错误时error为nil,非nil为错误信息。
|
||||
//
|
||||
func (bucket Bucket) SetObjectMeta(objectKey string, options ...Option) error {
|
||||
options = append(options, MetadataDirective(MetaReplace))
|
||||
_, err := bucket.CopyObject(objectKey, objectKey, options...)
|
||||
return err
|
||||
}
|
||||
|
||||
//
|
||||
// GetObjectDetailedMeta 查询Object的头信息。
|
||||
//
|
||||
// objectKey object名称。
|
||||
// objectPropertyConstraints 对象的属性限制项,满足时正常返回,不满足时返回错误。现在项有IfModifiedSince、IfUnmodifiedSince、
|
||||
// IfMatch、IfNoneMatch。具体含义请参看 https://help.aliyun.com/document_detail/oss/api-reference/object/HeadObject.html
|
||||
//
|
||||
// http.Header 对象的meta,error为nil时有效。
|
||||
// error 操作无错误为nil,非nil为错误信息。
|
||||
//
|
||||
func (bucket Bucket) GetObjectDetailedMeta(objectKey string, options ...Option) (http.Header, error) {
|
||||
resp, err := bucket.do("HEAD", objectKey, "", "", options, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return resp.Headers, nil
|
||||
}
|
||||
|
||||
//
|
||||
// GetObjectMeta 查询Object的头信息。
|
||||
//
|
||||
// GetObjectMeta相比GetObjectDetailedMeta更轻量,仅返回指定Object的少量基本meta信息,
|
||||
// 包括该Object的ETag、Size(对象大小)、LastModified,其中Size由响应头Content-Length的数值表示。
|
||||
//
|
||||
// objectKey object名称。
|
||||
//
|
||||
// http.Header 对象的meta,error为nil时有效。
|
||||
// error 操作无错误为nil,非nil为错误信息。
|
||||
//
|
||||
func (bucket Bucket) GetObjectMeta(objectKey string) (http.Header, error) {
|
||||
resp, err := bucket.do("GET", objectKey, "?objectMeta", "", nil, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return resp.Headers, nil
|
||||
}
|
||||
|
||||
//
|
||||
// SetObjectACL 修改Object的ACL权限。
|
||||
//
|
||||
// 只有Bucket Owner才有权限调用PutObjectACL来修改Object的ACL。Object ACL优先级高于Bucket ACL。
|
||||
// 例如Bucket ACL是private的,而Object ACL是public-read-write的,则访问这个Object时,
|
||||
// 先判断Object的ACL,所以所有用户都拥有这个Object的访问权限,即使这个Bucket是private bucket。
|
||||
// 如果某个Object从来没设置过ACL,则访问权限遵循Bucket ACL。
|
||||
//
|
||||
// Object的读操作包括GetObject,HeadObject,CopyObject和UploadPartCopy中的对source object的读;
|
||||
// Object的写操作包括:PutObject,PostObject,AppendObject,DeleteObject,
|
||||
// DeleteMultipleObjects,CompleteMultipartUpload以及CopyObject对新的Object的写。
|
||||
//
|
||||
// objectKey 设置权限的object。
|
||||
// objectAcl 对象权限。可选值PrivateACL(私有读写)、PublicReadACL(公共读私有写)、PublicReadWriteACL(公共读写)。
|
||||
//
|
||||
// error 操作无错误为nil,非nil为错误信息。
|
||||
//
|
||||
func (bucket Bucket) SetObjectACL(objectKey string, objectACL ACLType) error {
|
||||
options := []Option{ObjectACL(objectACL)}
|
||||
resp, err := bucket.do("PUT", objectKey, "acl", "acl", options, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return checkRespCode(resp.StatusCode, []int{http.StatusOK})
|
||||
}
|
||||
|
||||
//
|
||||
// GetObjectACL 获取对象的ACL权限。
|
||||
//
|
||||
// objectKey 获取权限的object。
|
||||
//
|
||||
// GetObjectAclResponse 获取权限操作返回值,error为nil时有效。GetObjectAclResponse.Acl为对象的权限。
|
||||
// error 操作无错误为nil,非nil为错误信息。
|
||||
//
|
||||
func (bucket Bucket) GetObjectACL(objectKey string) (GetObjectACLResult, error) {
|
||||
var out GetObjectACLResult
|
||||
resp, err := bucket.do("GET", objectKey, "acl", "acl", nil, nil, nil)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = xmlUnmarshal(resp.Body, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// Private
|
||||
func (bucket Bucket) do(method, objectName, urlParams, subResource string, options []Option,
|
||||
data io.Reader, listener ProgressListener) (*Response, error) {
|
||||
headers := make(map[string]string)
|
||||
err := handleOptions(headers, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bucket.Client.Conn.Do(method, bucket.BucketName, objectName,
|
||||
urlParams, subResource, headers, data, 0, listener)
|
||||
}
|
||||
|
||||
func (bucket Bucket) getConfig() *Config {
|
||||
return bucket.Client.Config
|
||||
}
|
||||
|
||||
func addContentType(options []Option, keys ...string) []Option {
|
||||
typ := TypeByExtension("")
|
||||
for _, key := range keys {
|
||||
typ = TypeByExtension(key)
|
||||
if typ != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if typ == "" {
|
||||
typ = "application/octet-stream"
|
||||
}
|
||||
|
||||
opts := []Option{ContentType(typ)}
|
||||
opts = append(opts, options...)
|
||||
|
||||
return opts
|
||||
}
|
|
@ -0,0 +1,748 @@
|
|||
// Package oss implements functions for access oss service.
|
||||
// It has two main struct Client and Bucket.
|
||||
package oss
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
//
|
||||
// Client Sdk的入口,Client的方法可以完成bucket的各种操作,如create/delete bucket,
|
||||
// set/get acl/lifecycle/referer/logging/website等。文件(object)的上传下载通过Bucket完成。
|
||||
// 用户用oss.New创建Client。
|
||||
//
|
||||
type (
|
||||
// Client oss client
|
||||
Client struct {
|
||||
Config *Config // Oss Client configure
|
||||
Conn *Conn // Send http request
|
||||
}
|
||||
|
||||
// ClientOption client option such as UseCname, Timeout, SecurityToken.
|
||||
ClientOption func(*Client)
|
||||
)
|
||||
|
||||
//
|
||||
// New 生成一个新的Client。
|
||||
//
|
||||
// endpoint 用户Bucket所在数据中心的访问域名,如http://oss-cn-hangzhou.aliyuncs.com。
|
||||
// accessKeyId 用户标识。
|
||||
// accessKeySecret 用户密钥。
|
||||
//
|
||||
// Client 生成的新Client。error为nil时有效。
|
||||
// error 操作无错误时为nil,非nil时表示操作出错。
|
||||
//
|
||||
func New(endpoint, accessKeyID, accessKeySecret string, options ...ClientOption) (*Client, error) {
|
||||
// configuration
|
||||
config := getDefaultOssConfig()
|
||||
config.Endpoint = endpoint
|
||||
config.AccessKeyID = accessKeyID
|
||||
config.AccessKeySecret = accessKeySecret
|
||||
|
||||
// url parse
|
||||
url := &urlMaker{}
|
||||
url.Init(config.Endpoint, config.IsCname, config.IsUseProxy)
|
||||
|
||||
// http connect
|
||||
conn := &Conn{config: config, url: url}
|
||||
|
||||
// oss client
|
||||
client := &Client{
|
||||
config,
|
||||
conn,
|
||||
}
|
||||
|
||||
// client options parse
|
||||
for _, option := range options {
|
||||
option(client)
|
||||
}
|
||||
|
||||
// create http connect
|
||||
err := conn.init(config, url)
|
||||
|
||||
return client, err
|
||||
}
|
||||
|
||||
//
|
||||
// Bucket 取存储空间(Bucket)的对象实例。
|
||||
//
|
||||
// bucketName 存储空间名称。
|
||||
// Bucket 新的Bucket。error为nil时有效。
|
||||
//
|
||||
// error 操作无错误时返回nil,非nil为错误信息。
|
||||
//
|
||||
func (client Client) Bucket(bucketName string) (*Bucket, error) {
|
||||
return &Bucket{
|
||||
client,
|
||||
bucketName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
//
|
||||
// CreateBucket 创建Bucket。
|
||||
//
|
||||
// bucketName bucket名称,在整个OSS中具有全局唯一性,且不能修改。bucket名称的只能包括小写字母,数字和短横线-,
|
||||
// 必须以小写字母或者数字开头,长度必须在3-255字节之间。
|
||||
// options 创建bucket的选项。您可以使用选项ACL,指定bucket的访问权限。Bucket有以下三种访问权限,私有读写(ACLPrivate)、
|
||||
// 公共读私有写(ACLPublicRead),公共读公共写(ACLPublicReadWrite),默认访问权限是私有读写。
|
||||
//
|
||||
// error 操作无错误时返回nil,非nil为错误信息。
|
||||
//
|
||||
func (client Client) CreateBucket(bucketName string, options ...Option) error {
|
||||
headers := make(map[string]string)
|
||||
handleOptions(headers, options)
|
||||
|
||||
resp, err := client.do("PUT", bucketName, "", "", headers, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
return checkRespCode(resp.StatusCode, []int{http.StatusOK})
|
||||
}
|
||||
|
||||
//
|
||||
// ListBuckets 获取当前用户下的bucket。
|
||||
//
|
||||
// options 指定ListBuckets的筛选行为,Prefix、Marker、MaxKeys三个选项。Prefix限定前缀。
|
||||
// Marker设定从Marker之后的第一个开始返回。MaxKeys限定此次返回的最大数目,默认为100。
|
||||
// 常用使用场景的实现,参数示例程序list_bucket.go。
|
||||
// ListBucketsResponse 操作成功后的返回值,error为nil时该返回值有效。
|
||||
//
|
||||
// error 操作无错误时返回nil,非nil为错误信息。
|
||||
//
|
||||
func (client Client) ListBuckets(options ...Option) (ListBucketsResult, error) {
|
||||
var out ListBucketsResult
|
||||
|
||||
params, err := handleParams(options)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
|
||||
resp, err := client.do("GET", "", params, "", nil, nil)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = xmlUnmarshal(resp.Body, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
//
|
||||
// IsBucketExist Bucket是否存在。
|
||||
//
|
||||
// bucketName 存储空间名称。
|
||||
//
|
||||
// bool 存储空间是否存在。error为nil时有效。
|
||||
// error 操作无错误时返回nil,非nil为错误信息。
|
||||
//
|
||||
func (client Client) IsBucketExist(bucketName string) (bool, error) {
|
||||
listRes, err := client.ListBuckets(Prefix(bucketName), MaxKeys(1))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if len(listRes.Buckets) == 1 && listRes.Buckets[0].Name == bucketName {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
//
|
||||
// DeleteBucket 删除空存储空间。非空时请先清理Object、Upload。
|
||||
//
|
||||
// bucketName 存储空间名称。
|
||||
//
|
||||
// error 操作无错误时返回nil,非nil为错误信息。
|
||||
//
|
||||
func (client Client) DeleteBucket(bucketName string) error {
|
||||
resp, err := client.do("DELETE", bucketName, "", "", nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
return checkRespCode(resp.StatusCode, []int{http.StatusNoContent})
|
||||
}
|
||||
|
||||
//
|
||||
// GetBucketLocation 查看Bucket所属数据中心位置的信息。
|
||||
//
|
||||
// 如果您想了解"访问域名和数据中心"详细信息,请参看
|
||||
// https://help.aliyun.com/document_detail/oss/user_guide/oss_concept/endpoint.html
|
||||
//
|
||||
// bucketName 存储空间名称。
|
||||
//
|
||||
// string Bucket所属的数据中心位置信息。
|
||||
// error 操作无错误时返回nil,非nil为错误信息。
|
||||
//
|
||||
func (client Client) GetBucketLocation(bucketName string) (string, error) {
|
||||
resp, err := client.do("GET", bucketName, "location", "location", nil, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var LocationConstraint string
|
||||
err = xmlUnmarshal(resp.Body, &LocationConstraint)
|
||||
return LocationConstraint, err
|
||||
}
|
||||
|
||||
//
|
||||
// SetBucketACL 修改Bucket的访问权限。
|
||||
//
|
||||
// bucketName 存储空间名称。
|
||||
// bucketAcl bucket的访问权限。Bucket有以下三种访问权限,Bucket有以下三种访问权限,私有读写(ACLPrivate)、
|
||||
// 公共读私有写(ACLPublicRead),公共读公共写(ACLPublicReadWrite)。
|
||||
//
|
||||
// error 操作无错误时返回nil,非nil为错误信息。
|
||||
//
|
||||
func (client Client) SetBucketACL(bucketName string, bucketACL ACLType) error {
|
||||
headers := map[string]string{HTTPHeaderOssACL: string(bucketACL)}
|
||||
resp, err := client.do("PUT", bucketName, "", "", headers, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return checkRespCode(resp.StatusCode, []int{http.StatusOK})
|
||||
}
|
||||
|
||||
//
|
||||
// GetBucketACL 获得Bucket的访问权限。
|
||||
//
|
||||
// bucketName 存储空间名称。
|
||||
//
|
||||
// GetBucketAclResponse 操作成功后的返回值,error为nil时该返回值有效。
|
||||
// error 操作无错误时返回nil,非nil为错误信息。
|
||||
//
|
||||
func (client Client) GetBucketACL(bucketName string) (GetBucketACLResult, error) {
|
||||
var out GetBucketACLResult
|
||||
resp, err := client.do("GET", bucketName, "acl", "acl", nil, nil)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = xmlUnmarshal(resp.Body, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
//
|
||||
// SetBucketLifecycle 修改Bucket的生命周期设置。
|
||||
//
|
||||
// OSS提供Object生命周期管理来为用户管理对象。用户可以为某个Bucket定义生命周期配置,来为该Bucket的Object定义各种规则。
|
||||
// Bucket的拥有者可以通过SetBucketLifecycle来设置Bucket的Lifecycle配置。Lifecycle开启后,OSS将按照配置,
|
||||
// 定期自动删除与Lifecycle规则相匹配的Object。如果您想了解更多的生命周期的信息,请参看
|
||||
// https://help.aliyun.com/document_detail/oss/user_guide/manage_object/object_lifecycle.html
|
||||
//
|
||||
// bucketName 存储空间名称。
|
||||
// rules 生命周期规则列表。生命周期规则有两种格式,指定绝对和相对过期时间,分布由days和year/month/day控制。
|
||||
// 具体用法请参考示例程序sample/bucket_lifecycle.go。
|
||||
//
|
||||
// error 操作无错误时返回error为nil,非nil为错误信息。
|
||||
//
|
||||
func (client Client) SetBucketLifecycle(bucketName string, rules []LifecycleRule) error {
|
||||
lxml := lifecycleXML{Rules: convLifecycleRule(rules)}
|
||||
bs, err := xml.Marshal(lxml)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buffer := new(bytes.Buffer)
|
||||
buffer.Write(bs)
|
||||
|
||||
contentType := http.DetectContentType(buffer.Bytes())
|
||||
headers := map[string]string{}
|
||||
headers[HTTPHeaderContentType] = contentType
|
||||
|
||||
resp, err := client.do("PUT", bucketName, "lifecycle", "lifecycle", headers, buffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return checkRespCode(resp.StatusCode, []int{http.StatusOK})
|
||||
}
|
||||
|
||||
//
|
||||
// DeleteBucketLifecycle 删除Bucket的生命周期设置。
|
||||
//
|
||||
//
|
||||
// bucketName 存储空间名称。
|
||||
//
|
||||
// error 操作无错误为nil,非nil为错误信息。
|
||||
//
|
||||
func (client Client) DeleteBucketLifecycle(bucketName string) error {
|
||||
resp, err := client.do("DELETE", bucketName, "lifecycle", "lifecycle", nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return checkRespCode(resp.StatusCode, []int{http.StatusNoContent})
|
||||
}
|
||||
|
||||
//
|
||||
// GetBucketLifecycle 查看Bucket的生命周期设置。
|
||||
//
|
||||
// bucketName 存储空间名称。
|
||||
//
|
||||
// GetBucketLifecycleResponse 操作成功的返回值,error为nil时该返回值有效。Rules为该bucket上的规则列表。
|
||||
// error 操作无错误时为nil,非nil为错误信息。
|
||||
//
|
||||
func (client Client) GetBucketLifecycle(bucketName string) (GetBucketLifecycleResult, error) {
|
||||
var out GetBucketLifecycleResult
|
||||
resp, err := client.do("GET", bucketName, "lifecycle", "lifecycle", nil, nil)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = xmlUnmarshal(resp.Body, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
//
|
||||
// SetBucketReferer 设置bucket的referer访问白名单和是否允许referer字段为空的请求访问。
|
||||
//
|
||||
// 防止用户在OSS上的数据被其他人盗用,OSS支持基于HTTP header中表头字段referer的防盗链方法。可以通过OSS控制台或者API的方式对
|
||||
// 一个bucket设置referer字段的白名单和是否允许referer字段为空的请求访问。例如,对于一个名为oss-example的bucket,
|
||||
// 设置其referer白名单为http://www.aliyun.com。则所有referer为http://www.aliyun.com的请求才能访问oss-example
|
||||
// 这个bucket中的object。如果您还需要了解更多信息,请参看
|
||||
// https://help.aliyun.com/document_detail/oss/user_guide/security_management/referer.html
|
||||
//
|
||||
// bucketName 存储空间名称。
|
||||
// referers 访问白名单列表。一个bucket可以支持多个referer参数。referer参数支持通配符"*"和"?"。
|
||||
// 用法请参看示例sample/bucket_referer.go
|
||||
// allowEmptyReferer 指定是否允许referer字段为空的请求访问。 默认为true。
|
||||
//
|
||||
// error 操作无错误为nil,非nil为错误信息。
|
||||
//
|
||||
func (client Client) SetBucketReferer(bucketName string, referers []string, allowEmptyReferer bool) error {
|
||||
rxml := RefererXML{}
|
||||
rxml.AllowEmptyReferer = allowEmptyReferer
|
||||
if referers == nil {
|
||||
rxml.RefererList = append(rxml.RefererList, "")
|
||||
} else {
|
||||
for _, referer := range referers {
|
||||
rxml.RefererList = append(rxml.RefererList, referer)
|
||||
}
|
||||
}
|
||||
|
||||
bs, err := xml.Marshal(rxml)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buffer := new(bytes.Buffer)
|
||||
buffer.Write(bs)
|
||||
|
||||
contentType := http.DetectContentType(buffer.Bytes())
|
||||
headers := map[string]string{}
|
||||
headers[HTTPHeaderContentType] = contentType
|
||||
|
||||
resp, err := client.do("PUT", bucketName, "referer", "referer", headers, buffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return checkRespCode(resp.StatusCode, []int{http.StatusOK})
|
||||
}
|
||||
|
||||
//
|
||||
// GetBucketReferer 获得Bucket的白名单地址。
|
||||
//
|
||||
// bucketName 存储空间名称。
|
||||
//
|
||||
// GetBucketRefererResponse 操作成功的返回值,error为nil时该返回值有效。
|
||||
// error 操作无错误时为nil,非nil为错误信息。
|
||||
//
|
||||
func (client Client) GetBucketReferer(bucketName string) (GetBucketRefererResult, error) {
|
||||
var out GetBucketRefererResult
|
||||
resp, err := client.do("GET", bucketName, "referer", "referer", nil, nil)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = xmlUnmarshal(resp.Body, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
//
|
||||
// SetBucketLogging 修改Bucket的日志设置。
|
||||
//
|
||||
// OSS为您提供自动保存访问日志记录功能。Bucket的拥有者可以开启访问日志记录功能。当一个bucket开启访问日志记录功能后,
|
||||
// OSS自动将访问这个bucket的请求日志,以小时为单位,按照固定的命名规则,生成一个Object写入用户指定的bucket中。
|
||||
// 如果您需要更多,请参看 https://help.aliyun.com/document_detail/oss/user_guide/security_management/logging.html
|
||||
//
|
||||
// bucketName 需要记录访问日志的Bucket。
|
||||
// targetBucket 访问日志记录到的Bucket。
|
||||
// targetPrefix bucketName中需要存储访问日志记录的object前缀。为空记录所有object的访问日志。
|
||||
//
|
||||
// error 操作无错误为nil,非nil为错误信息。
|
||||
//
|
||||
func (client Client) SetBucketLogging(bucketName, targetBucket, targetPrefix string,
|
||||
isEnable bool) error {
|
||||
var err error
|
||||
var bs []byte
|
||||
if isEnable {
|
||||
lxml := LoggingXML{}
|
||||
lxml.LoggingEnabled.TargetBucket = targetBucket
|
||||
lxml.LoggingEnabled.TargetPrefix = targetPrefix
|
||||
bs, err = xml.Marshal(lxml)
|
||||
} else {
|
||||
lxml := loggingXMLEmpty{}
|
||||
bs, err = xml.Marshal(lxml)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
buffer.Write(bs)
|
||||
|
||||
contentType := http.DetectContentType(buffer.Bytes())
|
||||
headers := map[string]string{}
|
||||
headers[HTTPHeaderContentType] = contentType
|
||||
|
||||
resp, err := client.do("PUT", bucketName, "logging", "logging", headers, buffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return checkRespCode(resp.StatusCode, []int{http.StatusOK})
|
||||
}
|
||||
|
||||
//
|
||||
// DeleteBucketLogging 删除Bucket的日志设置。
|
||||
//
|
||||
// bucketName 需要删除访问日志的Bucket。
|
||||
//
|
||||
// error 操作无错误为nil,非nil为错误信息。
|
||||
//
|
||||
func (client Client) DeleteBucketLogging(bucketName string) error {
|
||||
resp, err := client.do("DELETE", bucketName, "logging", "logging", nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return checkRespCode(resp.StatusCode, []int{http.StatusNoContent})
|
||||
}
|
||||
|
||||
//
|
||||
// GetBucketLogging 获得Bucket的日志设置。
|
||||
//
|
||||
// bucketName 需要删除访问日志的Bucket。
|
||||
// GetBucketLoggingResponse 操作成功的返回值,error为nil时该返回值有效。
|
||||
//
|
||||
// error 操作无错误为nil,非nil为错误信息。
|
||||
//
|
||||
func (client Client) GetBucketLogging(bucketName string) (GetBucketLoggingResult, error) {
|
||||
var out GetBucketLoggingResult
|
||||
resp, err := client.do("GET", bucketName, "logging", "logging", nil, nil)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = xmlUnmarshal(resp.Body, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
//
|
||||
// SetBucketWebsite 设置/修改Bucket的默认首页以及错误页。
|
||||
//
|
||||
// OSS支持静态网站托管,Website操作可以将一个bucket设置成静态网站托管模式 。您可以将自己的Bucket配置成静态网站托管模式。
|
||||
// 如果您需要更多,请参看 https://help.aliyun.com/document_detail/oss/user_guide/static_host_website.html
|
||||
//
|
||||
// bucketName 需要设置Website的Bucket。
|
||||
// indexDocument 索引文档。
|
||||
// errorDocument 错误文档。
|
||||
//
|
||||
// error 操作无错误为nil,非nil为错误信息。
|
||||
//
|
||||
func (client Client) SetBucketWebsite(bucketName, indexDocument, errorDocument string) error {
|
||||
wxml := WebsiteXML{}
|
||||
wxml.IndexDocument.Suffix = indexDocument
|
||||
wxml.ErrorDocument.Key = errorDocument
|
||||
|
||||
bs, err := xml.Marshal(wxml)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buffer := new(bytes.Buffer)
|
||||
buffer.Write(bs)
|
||||
|
||||
contentType := http.DetectContentType(buffer.Bytes())
|
||||
headers := make(map[string]string)
|
||||
headers[HTTPHeaderContentType] = contentType
|
||||
|
||||
resp, err := client.do("PUT", bucketName, "website", "website", headers, buffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return checkRespCode(resp.StatusCode, []int{http.StatusOK})
|
||||
}
|
||||
|
||||
//
|
||||
// DeleteBucketWebsite 删除Bucket的Website设置。
|
||||
//
|
||||
// bucketName 需要删除website设置的Bucket。
|
||||
//
|
||||
// error 操作无错误为nil,非nil为错误信息。
|
||||
//
|
||||
func (client Client) DeleteBucketWebsite(bucketName string) error {
|
||||
resp, err := client.do("DELETE", bucketName, "website", "website", nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return checkRespCode(resp.StatusCode, []int{http.StatusNoContent})
|
||||
}
|
||||
|
||||
//
|
||||
// GetBucketWebsite 获得Bucket的默认首页以及错误页。
|
||||
//
|
||||
// bucketName 存储空间名称。
|
||||
//
|
||||
// GetBucketWebsiteResponse 操作成功的返回值,error为nil时该返回值有效。
|
||||
// error 操作无错误为nil,非nil为错误信息。
|
||||
//
|
||||
func (client Client) GetBucketWebsite(bucketName string) (GetBucketWebsiteResult, error) {
|
||||
var out GetBucketWebsiteResult
|
||||
resp, err := client.do("GET", bucketName, "website", "website", nil, nil)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = xmlUnmarshal(resp.Body, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
//
|
||||
// SetBucketCORS 设置Bucket的跨域访问(CORS)规则。
|
||||
//
|
||||
// 跨域访问的更多信息,请参看 https://help.aliyun.com/document_detail/oss/user_guide/security_management/cors.html
|
||||
//
|
||||
// bucketName 需要设置Website的Bucket。
|
||||
// corsRules 待设置的CORS规则。用法请参看示例代码sample/bucket_cors.go。
|
||||
//
|
||||
// error 操作无错误为nil,非nil为错误信息。
|
||||
//
|
||||
func (client Client) SetBucketCORS(bucketName string, corsRules []CORSRule) error {
|
||||
corsxml := CORSXML{}
|
||||
for _, v := range corsRules {
|
||||
cr := CORSRule{}
|
||||
cr.AllowedMethod = v.AllowedMethod
|
||||
cr.AllowedOrigin = v.AllowedOrigin
|
||||
cr.AllowedHeader = v.AllowedHeader
|
||||
cr.ExposeHeader = v.ExposeHeader
|
||||
cr.MaxAgeSeconds = v.MaxAgeSeconds
|
||||
corsxml.CORSRules = append(corsxml.CORSRules, cr)
|
||||
}
|
||||
|
||||
bs, err := xml.Marshal(corsxml)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buffer := new(bytes.Buffer)
|
||||
buffer.Write(bs)
|
||||
|
||||
contentType := http.DetectContentType(buffer.Bytes())
|
||||
headers := map[string]string{}
|
||||
headers[HTTPHeaderContentType] = contentType
|
||||
|
||||
resp, err := client.do("PUT", bucketName, "cors", "cors", headers, buffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return checkRespCode(resp.StatusCode, []int{http.StatusOK})
|
||||
}
|
||||
|
||||
//
|
||||
// DeleteBucketCORS 删除Bucket的Website设置。
|
||||
//
|
||||
// bucketName 需要删除cors设置的Bucket。
|
||||
//
|
||||
// error 操作无错误为nil,非nil为错误信息。
|
||||
//
|
||||
func (client Client) DeleteBucketCORS(bucketName string) error {
|
||||
resp, err := client.do("DELETE", bucketName, "cors", "cors", nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return checkRespCode(resp.StatusCode, []int{http.StatusNoContent})
|
||||
}
|
||||
|
||||
//
|
||||
// GetBucketCORS 获得Bucket的CORS设置。
|
||||
//
|
||||
//
|
||||
// bucketName 存储空间名称。
|
||||
// GetBucketCORSResult 操作成功的返回值,error为nil时该返回值有效。
|
||||
//
|
||||
// error 操作无错误为nil,非nil为错误信息。
|
||||
//
|
||||
func (client Client) GetBucketCORS(bucketName string) (GetBucketCORSResult, error) {
|
||||
var out GetBucketCORSResult
|
||||
resp, err := client.do("GET", bucketName, "cors", "cors", nil, nil)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = xmlUnmarshal(resp.Body, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
//
|
||||
// GetBucketInfo 获得Bucket的信息。
|
||||
//
|
||||
// bucketName 存储空间名称。
|
||||
// GetBucketInfoResult 操作成功的返回值,error为nil时该返回值有效。
|
||||
//
|
||||
// error 操作无错误为nil,非nil为错误信息。
|
||||
//
|
||||
func (client Client) GetBucketInfo(bucketName string) (GetBucketInfoResult, error) {
|
||||
var out GetBucketInfoResult
|
||||
resp, err := client.do("GET", bucketName, "bucketInfo", "bucketInfo", nil, nil)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = xmlUnmarshal(resp.Body, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
//
|
||||
// UseCname 设置是否使用CNAME,默认不使用。
|
||||
//
|
||||
// isUseCname true设置endpoint格式是cname格式,false为非cname格式,默认false
|
||||
//
|
||||
func UseCname(isUseCname bool) ClientOption {
|
||||
return func(client *Client) {
|
||||
client.Config.IsCname = isUseCname
|
||||
client.Conn.url.Init(client.Config.Endpoint, client.Config.IsCname, client.Config.IsUseProxy)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Timeout 设置HTTP超时时间。
|
||||
//
|
||||
// connectTimeoutSec HTTP链接超时时间,单位是秒,默认10秒。0表示永不超时。
|
||||
// readWriteTimeout HTTP发送接受数据超时时间,单位是秒,默认20秒。0表示永不超时。
|
||||
//
|
||||
func Timeout(connectTimeoutSec, readWriteTimeout int64) ClientOption {
|
||||
return func(client *Client) {
|
||||
client.Config.HTTPTimeout.ConnectTimeout =
|
||||
time.Second * time.Duration(connectTimeoutSec)
|
||||
client.Config.HTTPTimeout.ReadWriteTimeout =
|
||||
time.Second * time.Duration(readWriteTimeout)
|
||||
client.Config.HTTPTimeout.HeaderTimeout =
|
||||
time.Second * time.Duration(readWriteTimeout)
|
||||
client.Config.HTTPTimeout.LongTimeout =
|
||||
time.Second * time.Duration(readWriteTimeout*10)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// SecurityToken 临时用户设置SecurityToken。
|
||||
//
|
||||
// token STS token
|
||||
//
|
||||
func SecurityToken(token string) ClientOption {
|
||||
return func(client *Client) {
|
||||
client.Config.SecurityToken = strings.TrimSpace(token)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// EnableMD5 是否启用MD5校验,默认启用。
|
||||
//
|
||||
// isEnableMD5 true启用MD5校验,false不启用MD5校验
|
||||
//
|
||||
func EnableMD5(isEnableMD5 bool) ClientOption {
|
||||
return func(client *Client) {
|
||||
client.Config.IsEnableMD5 = isEnableMD5
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// MD5ThresholdCalcInMemory 使用内存计算MD5值的上限,默认16MB。
|
||||
//
|
||||
// threshold 单位Byte。上传内容小于threshold在MD5在内存中计算,大于使用临时文件计算MD5
|
||||
//
|
||||
func MD5ThresholdCalcInMemory(threshold int64) ClientOption {
|
||||
return func(client *Client) {
|
||||
client.Config.MD5Threshold = threshold
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// EnableCRC 上传是否启用CRC校验,默认启用。
|
||||
//
|
||||
// isEnableCRC true启用CRC校验,false不启用CRC校验
|
||||
//
|
||||
func EnableCRC(isEnableCRC bool) ClientOption {
|
||||
return func(client *Client) {
|
||||
client.Config.IsEnableCRC = isEnableCRC
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// UserAgent 指定UserAgent,默认如下aliyun-sdk-go/1.2.0 (windows/-/amd64;go1.5.2)。
|
||||
//
|
||||
// userAgent user agent字符串。
|
||||
//
|
||||
func UserAgent(userAgent string) ClientOption {
|
||||
return func(client *Client) {
|
||||
client.Config.UserAgent = userAgent
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Proxy 设置代理服务器,默认不使用代理。
|
||||
//
|
||||
// proxyHost 代理服务器地址,格式是host或host:port
|
||||
//
|
||||
func Proxy(proxyHost string) ClientOption {
|
||||
return func(client *Client) {
|
||||
client.Config.IsUseProxy = true
|
||||
client.Config.ProxyHost = proxyHost
|
||||
client.Conn.url.Init(client.Config.Endpoint, client.Config.IsCname, client.Config.IsUseProxy)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// AuthProxy 设置需要认证的代理服务器,默认不使用代理。
|
||||
//
|
||||
// proxyHost 代理服务器地址,格式是host或host:port
|
||||
// proxyUser 代理服务器认证的用户名
|
||||
// proxyPassword 代理服务器认证的用户密码
|
||||
//
|
||||
func AuthProxy(proxyHost, proxyUser, proxyPassword string) ClientOption {
|
||||
return func(client *Client) {
|
||||
client.Config.IsUseProxy = true
|
||||
client.Config.ProxyHost = proxyHost
|
||||
client.Config.IsAuthProxy = true
|
||||
client.Config.ProxyUser = proxyUser
|
||||
client.Config.ProxyPassword = proxyPassword
|
||||
client.Conn.url.Init(client.Config.Endpoint, client.Config.IsCname, client.Config.IsUseProxy)
|
||||
}
|
||||
}
|
||||
|
||||
// Private
|
||||
func (client Client) do(method, bucketName, urlParams, subResource string,
|
||||
headers map[string]string, data io.Reader) (*Response, error) {
|
||||
return client.Conn.Do(method, bucketName, "", urlParams,
|
||||
subResource, headers, data, 0, nil)
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
package oss
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// HTTPTimeout http timeout
|
||||
type HTTPTimeout struct {
|
||||
ConnectTimeout time.Duration
|
||||
ReadWriteTimeout time.Duration
|
||||
HeaderTimeout time.Duration
|
||||
LongTimeout time.Duration
|
||||
}
|
||||
|
||||
// Config oss configure
|
||||
type Config struct {
|
||||
Endpoint string // oss地址
|
||||
AccessKeyID string // accessId
|
||||
AccessKeySecret string // accessKey
|
||||
RetryTimes uint // 失败重试次数,默认5
|
||||
UserAgent string // SDK名称/版本/系统信息
|
||||
IsDebug bool // 是否开启调试模式,默认false
|
||||
Timeout uint // 超时时间,默认60s
|
||||
SecurityToken string // STS Token
|
||||
IsCname bool // Endpoint是否是CNAME
|
||||
HTTPTimeout HTTPTimeout // HTTP的超时时间设置
|
||||
IsUseProxy bool // 是否使用代理
|
||||
ProxyHost string // 代理服务器地址
|
||||
IsAuthProxy bool // 代理服务器是否使用用户认证
|
||||
ProxyUser string // 代理服务器认证用户名
|
||||
ProxyPassword string // 代理服务器认证密码
|
||||
IsEnableMD5 bool // 上传数据时是否启用MD5校验
|
||||
MD5Threshold int64 // 内存中计算MD5的上线大小,大于该值启用临时文件,单位Byte
|
||||
IsEnableCRC bool // 上传数据时是否启用CRC64校验
|
||||
}
|
||||
|
||||
// 获取默认配置
|
||||
func getDefaultOssConfig() *Config {
|
||||
config := Config{}
|
||||
|
||||
config.Endpoint = ""
|
||||
config.AccessKeyID = ""
|
||||
config.AccessKeySecret = ""
|
||||
config.RetryTimes = 5
|
||||
config.IsDebug = false
|
||||
config.UserAgent = userAgent
|
||||
config.Timeout = 60 // seconds
|
||||
config.SecurityToken = ""
|
||||
config.IsCname = false
|
||||
|
||||
config.HTTPTimeout.ConnectTimeout = time.Second * 30 // 30s
|
||||
config.HTTPTimeout.ReadWriteTimeout = time.Second * 60 // 60s
|
||||
config.HTTPTimeout.HeaderTimeout = time.Second * 60 // 60s
|
||||
config.HTTPTimeout.LongTimeout = time.Second * 300 // 300s
|
||||
|
||||
config.IsUseProxy = false
|
||||
config.ProxyHost = ""
|
||||
config.IsAuthProxy = false
|
||||
config.ProxyUser = ""
|
||||
config.ProxyPassword = ""
|
||||
|
||||
config.MD5Threshold = 16 * 1024 * 1024 // 16MB
|
||||
config.IsEnableMD5 = false
|
||||
config.IsEnableCRC = true
|
||||
|
||||
return &config
|
||||
}
|
|
@ -0,0 +1,437 @@
|
|||
package oss
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Conn oss conn
|
||||
type Conn struct {
|
||||
config *Config
|
||||
url *urlMaker
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
// init 初始化Conn
|
||||
func (conn *Conn) init(config *Config, urlMaker *urlMaker) error {
|
||||
httpTimeOut := conn.config.HTTPTimeout
|
||||
|
||||
// new Transport
|
||||
transport := &http.Transport{
|
||||
Dial: func(netw, addr string) (net.Conn, error) {
|
||||
conn, err := net.DialTimeout(netw, addr, httpTimeOut.ConnectTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newTimeoutConn(conn, httpTimeOut.ReadWriteTimeout, httpTimeOut.LongTimeout), nil
|
||||
},
|
||||
ResponseHeaderTimeout: httpTimeOut.HeaderTimeout,
|
||||
}
|
||||
|
||||
// Proxy
|
||||
if conn.config.IsUseProxy {
|
||||
proxyURL, err := url.Parse(config.ProxyHost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
transport.Proxy = http.ProxyURL(proxyURL)
|
||||
}
|
||||
|
||||
conn.config = config
|
||||
conn.url = urlMaker
|
||||
conn.client = &http.Client{Transport: transport}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Do 处理请求,返回响应结果。
|
||||
func (conn Conn) Do(method, bucketName, objectName, urlParams, subResource string, headers map[string]string,
|
||||
data io.Reader, initCRC uint64, listener ProgressListener) (*Response, error) {
|
||||
uri := conn.url.getURL(bucketName, objectName, urlParams)
|
||||
resource := conn.url.getResource(bucketName, objectName, subResource)
|
||||
return conn.doRequest(method, uri, resource, headers, data, initCRC, listener)
|
||||
}
|
||||
|
||||
func (conn Conn) doRequest(method string, uri *url.URL, canonicalizedResource string, headers map[string]string,
|
||||
data io.Reader, initCRC uint64, listener ProgressListener) (*Response, error) {
|
||||
method = strings.ToUpper(method)
|
||||
if !conn.config.IsUseProxy {
|
||||
uri.Opaque = uri.Path
|
||||
}
|
||||
req := &http.Request{
|
||||
Method: method,
|
||||
URL: uri,
|
||||
Proto: "HTTP/1.1",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
Header: make(http.Header),
|
||||
Host: uri.Host,
|
||||
}
|
||||
|
||||
tracker := &readerTracker{completedBytes: 0}
|
||||
fd, crc := conn.handleBody(req, data, initCRC, listener, tracker)
|
||||
if fd != nil {
|
||||
defer func() {
|
||||
fd.Close()
|
||||
os.Remove(fd.Name())
|
||||
}()
|
||||
}
|
||||
|
||||
if conn.config.IsAuthProxy {
|
||||
auth := conn.config.ProxyUser + ":" + conn.config.ProxyPassword
|
||||
basic := "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))
|
||||
req.Header.Set("Proxy-Authorization", basic)
|
||||
}
|
||||
|
||||
date := time.Now().UTC().Format(http.TimeFormat)
|
||||
req.Header.Set(HTTPHeaderDate, date)
|
||||
req.Header.Set(HTTPHeaderHost, conn.config.Endpoint)
|
||||
req.Header.Set(HTTPHeaderUserAgent, conn.config.UserAgent)
|
||||
if conn.config.SecurityToken != "" {
|
||||
req.Header.Set(HTTPHeaderOssSecurityToken, conn.config.SecurityToken)
|
||||
}
|
||||
|
||||
if headers != nil {
|
||||
for k, v := range headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
conn.signHeader(req, canonicalizedResource)
|
||||
|
||||
// transfer started
|
||||
event := newProgressEvent(TransferStartedEvent, 0, req.ContentLength)
|
||||
publishProgress(listener, event)
|
||||
|
||||
resp, err := conn.client.Do(req)
|
||||
if err != nil {
|
||||
// transfer failed
|
||||
event = newProgressEvent(TransferFailedEvent, tracker.completedBytes, req.ContentLength)
|
||||
publishProgress(listener, event)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// transfer completed
|
||||
event = newProgressEvent(TransferCompletedEvent, tracker.completedBytes, req.ContentLength)
|
||||
publishProgress(listener, event)
|
||||
|
||||
return conn.handleResponse(resp, crc)
|
||||
}
|
||||
|
||||
// handle request body
|
||||
func (conn Conn) handleBody(req *http.Request, body io.Reader, initCRC uint64,
|
||||
listener ProgressListener, tracker *readerTracker) (*os.File, hash.Hash64) {
|
||||
var file *os.File
|
||||
var crc hash.Hash64
|
||||
reader := body
|
||||
|
||||
// length
|
||||
switch v := body.(type) {
|
||||
case *bytes.Buffer:
|
||||
req.ContentLength = int64(v.Len())
|
||||
case *bytes.Reader:
|
||||
req.ContentLength = int64(v.Len())
|
||||
case *strings.Reader:
|
||||
req.ContentLength = int64(v.Len())
|
||||
case *os.File:
|
||||
req.ContentLength = tryGetFileSize(v)
|
||||
case *io.LimitedReader:
|
||||
req.ContentLength = int64(v.N)
|
||||
}
|
||||
req.Header.Set(HTTPHeaderContentLength, strconv.FormatInt(req.ContentLength, 10))
|
||||
|
||||
// md5
|
||||
if body != nil && conn.config.IsEnableMD5 && req.Header.Get(HTTPHeaderContentMD5) == "" {
|
||||
md5 := ""
|
||||
reader, md5, file, _ = calcMD5(body, req.ContentLength, conn.config.MD5Threshold)
|
||||
req.Header.Set(HTTPHeaderContentMD5, md5)
|
||||
}
|
||||
|
||||
// crc
|
||||
if reader != nil && conn.config.IsEnableCRC {
|
||||
crc = NewCRC(crcTable(), initCRC)
|
||||
reader = TeeReader(reader, crc, req.ContentLength, listener, tracker)
|
||||
}
|
||||
|
||||
// http body
|
||||
rc, ok := reader.(io.ReadCloser)
|
||||
if !ok && reader != nil {
|
||||
rc = ioutil.NopCloser(reader)
|
||||
}
|
||||
req.Body = rc
|
||||
|
||||
return file, crc
|
||||
}
|
||||
|
||||
func tryGetFileSize(f *os.File) int64 {
|
||||
fInfo, _ := f.Stat()
|
||||
return fInfo.Size()
|
||||
}
|
||||
|
||||
// handle response
|
||||
func (conn Conn) handleResponse(resp *http.Response, crc hash.Hash64) (*Response, error) {
|
||||
var cliCRC uint64
|
||||
var srvCRC uint64
|
||||
|
||||
statusCode := resp.StatusCode
|
||||
if statusCode >= 400 && statusCode <= 505 {
|
||||
// 4xx and 5xx indicate that the operation has error occurred
|
||||
var respBody []byte
|
||||
respBody, err := readResponseBody(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(respBody) == 0 {
|
||||
// no error in response body
|
||||
err = fmt.Errorf("oss: service returned without a response body (%s)", resp.Status)
|
||||
} else {
|
||||
// response contains storage service error object, unmarshal
|
||||
srvErr, errIn := serviceErrFromXML(respBody, resp.StatusCode,
|
||||
resp.Header.Get(HTTPHeaderOssRequestID))
|
||||
if err != nil { // error unmarshaling the error response
|
||||
err = errIn
|
||||
}
|
||||
err = srvErr
|
||||
}
|
||||
return &Response{
|
||||
StatusCode: resp.StatusCode,
|
||||
Headers: resp.Header,
|
||||
Body: ioutil.NopCloser(bytes.NewReader(respBody)), // restore the body
|
||||
}, err
|
||||
} else if statusCode >= 300 && statusCode <= 307 {
|
||||
// oss use 3xx, but response has no body
|
||||
err := fmt.Errorf("oss: service returned %d,%s", resp.StatusCode, resp.Status)
|
||||
return &Response{
|
||||
StatusCode: resp.StatusCode,
|
||||
Headers: resp.Header,
|
||||
Body: resp.Body,
|
||||
}, err
|
||||
}
|
||||
|
||||
if conn.config.IsEnableCRC && crc != nil {
|
||||
cliCRC = crc.Sum64()
|
||||
}
|
||||
srvCRC, _ = strconv.ParseUint(resp.Header.Get(HTTPHeaderOssCRC64), 10, 64)
|
||||
|
||||
// 2xx, successful
|
||||
return &Response{
|
||||
StatusCode: resp.StatusCode,
|
||||
Headers: resp.Header,
|
||||
Body: resp.Body,
|
||||
ClientCRC: cliCRC,
|
||||
ServerCRC: srvCRC,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func calcMD5(body io.Reader, contentLen, md5Threshold int64) (reader io.Reader, b64 string, tempFile *os.File, err error) {
|
||||
if contentLen == 0 || contentLen > md5Threshold {
|
||||
// huge body, use temporary file
|
||||
tempFile, err = ioutil.TempFile(os.TempDir(), TempFilePrefix)
|
||||
if tempFile != nil {
|
||||
io.Copy(tempFile, body)
|
||||
tempFile.Seek(0, os.SEEK_SET)
|
||||
md5 := md5.New()
|
||||
io.Copy(md5, tempFile)
|
||||
sum := md5.Sum(nil)
|
||||
b64 = base64.StdEncoding.EncodeToString(sum[:])
|
||||
tempFile.Seek(0, os.SEEK_SET)
|
||||
reader = tempFile
|
||||
}
|
||||
} else {
|
||||
// small body, use memory
|
||||
buf, _ := ioutil.ReadAll(body)
|
||||
sum := md5.Sum(buf)
|
||||
b64 = base64.StdEncoding.EncodeToString(sum[:])
|
||||
reader = bytes.NewReader(buf)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func readResponseBody(resp *http.Response) ([]byte, error) {
|
||||
defer resp.Body.Close()
|
||||
out, err := ioutil.ReadAll(resp.Body)
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
return out, err
|
||||
}
|
||||
|
||||
func serviceErrFromXML(body []byte, statusCode int, requestID string) (ServiceError, error) {
|
||||
var storageErr ServiceError
|
||||
if err := xml.Unmarshal(body, &storageErr); err != nil {
|
||||
return storageErr, err
|
||||
}
|
||||
storageErr.StatusCode = statusCode
|
||||
storageErr.RequestID = requestID
|
||||
storageErr.RawMessage = string(body)
|
||||
return storageErr, nil
|
||||
}
|
||||
|
||||
func xmlUnmarshal(body io.Reader, v interface{}) error {
|
||||
data, err := ioutil.ReadAll(body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return xml.Unmarshal(data, v)
|
||||
}
|
||||
|
||||
// Handle http timeout
|
||||
type timeoutConn struct {
|
||||
conn net.Conn
|
||||
timeout time.Duration
|
||||
longTimeout time.Duration
|
||||
}
|
||||
|
||||
func newTimeoutConn(conn net.Conn, timeout time.Duration, longTimeout time.Duration) *timeoutConn {
|
||||
conn.SetReadDeadline(time.Now().Add(longTimeout))
|
||||
return &timeoutConn{
|
||||
conn: conn,
|
||||
timeout: timeout,
|
||||
longTimeout: longTimeout,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *timeoutConn) Read(b []byte) (n int, err error) {
|
||||
c.SetReadDeadline(time.Now().Add(c.timeout))
|
||||
n, err = c.conn.Read(b)
|
||||
c.SetReadDeadline(time.Now().Add(c.longTimeout))
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (c *timeoutConn) Write(b []byte) (n int, err error) {
|
||||
c.SetWriteDeadline(time.Now().Add(c.timeout))
|
||||
n, err = c.conn.Write(b)
|
||||
c.SetReadDeadline(time.Now().Add(c.longTimeout))
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (c *timeoutConn) Close() error {
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
func (c *timeoutConn) LocalAddr() net.Addr {
|
||||
return c.conn.LocalAddr()
|
||||
}
|
||||
|
||||
func (c *timeoutConn) RemoteAddr() net.Addr {
|
||||
return c.conn.RemoteAddr()
|
||||
}
|
||||
|
||||
func (c *timeoutConn) SetDeadline(t time.Time) error {
|
||||
return c.conn.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (c *timeoutConn) SetReadDeadline(t time.Time) error {
|
||||
return c.conn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (c *timeoutConn) SetWriteDeadline(t time.Time) error {
|
||||
return c.conn.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
// UrlMaker - build url and resource
|
||||
const (
|
||||
urlTypeCname = 1
|
||||
urlTypeIP = 2
|
||||
urlTypeAliyun = 3
|
||||
)
|
||||
|
||||
type urlMaker struct {
|
||||
Scheme string // http or https
|
||||
NetLoc string // host or ip
|
||||
Type int // 1 CNAME 2 IP 3 ALIYUN
|
||||
IsProxy bool // proxy
|
||||
}
|
||||
|
||||
// Parse endpoint
|
||||
func (um *urlMaker) Init(endpoint string, isCname bool, isProxy bool) {
|
||||
if strings.HasPrefix(endpoint, "http://") {
|
||||
um.Scheme = "http"
|
||||
um.NetLoc = endpoint[len("http://"):]
|
||||
} else if strings.HasPrefix(endpoint, "https://") {
|
||||
um.Scheme = "https"
|
||||
um.NetLoc = endpoint[len("https://"):]
|
||||
} else {
|
||||
um.Scheme = "http"
|
||||
um.NetLoc = endpoint
|
||||
}
|
||||
|
||||
host, _, err := net.SplitHostPort(um.NetLoc)
|
||||
if err != nil {
|
||||
host = um.NetLoc
|
||||
}
|
||||
ip := net.ParseIP(host)
|
||||
if ip != nil {
|
||||
um.Type = urlTypeIP
|
||||
} else if isCname {
|
||||
um.Type = urlTypeCname
|
||||
} else {
|
||||
um.Type = urlTypeAliyun
|
||||
}
|
||||
um.IsProxy = isProxy
|
||||
}
|
||||
|
||||
// Build URL
|
||||
func (um urlMaker) getURL(bucket, object, params string) *url.URL {
|
||||
var host = ""
|
||||
var path = ""
|
||||
|
||||
if !um.IsProxy {
|
||||
object = url.QueryEscape(object)
|
||||
}
|
||||
|
||||
if um.Type == urlTypeCname {
|
||||
host = um.NetLoc
|
||||
path = "/" + object
|
||||
} else if um.Type == urlTypeIP {
|
||||
if bucket == "" {
|
||||
host = um.NetLoc
|
||||
path = "/"
|
||||
} else {
|
||||
host = um.NetLoc
|
||||
path = fmt.Sprintf("/%s/%s", bucket, object)
|
||||
}
|
||||
} else {
|
||||
if bucket == "" {
|
||||
host = um.NetLoc
|
||||
path = "/"
|
||||
} else {
|
||||
host = bucket + "." + um.NetLoc
|
||||
path = "/" + object
|
||||
}
|
||||
}
|
||||
|
||||
uri := &url.URL{
|
||||
Scheme: um.Scheme,
|
||||
Host: host,
|
||||
Path: path,
|
||||
RawQuery: params,
|
||||
}
|
||||
|
||||
return uri
|
||||
}
|
||||
|
||||
// Canonicalized Resource
|
||||
func (um urlMaker) getResource(bucketName, objectName, subResource string) string {
|
||||
if subResource != "" {
|
||||
subResource = "?" + subResource
|
||||
}
|
||||
if bucketName == "" {
|
||||
return fmt.Sprintf("/%s%s", bucketName, subResource)
|
||||
}
|
||||
return fmt.Sprintf("/%s/%s%s", bucketName, objectName, subResource)
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
package oss
|
||||
|
||||
import "os"
|
||||
|
||||
// ACLType Bucket/Object的访问控制
|
||||
type ACLType string
|
||||
|
||||
const (
|
||||
// ACLPrivate 私有读写
|
||||
ACLPrivate ACLType = "private"
|
||||
|
||||
// ACLPublicRead 公共读私有写
|
||||
ACLPublicRead ACLType = "public-read"
|
||||
|
||||
// ACLPublicReadWrite 公共读写
|
||||
ACLPublicReadWrite ACLType = "public-read-write"
|
||||
|
||||
// ACLDefault Object默认权限,Bucket无此权限
|
||||
ACLDefault ACLType = "default"
|
||||
)
|
||||
|
||||
// MetadataDirectiveType 对象COPY时新对象是否使用原对象的Meta
|
||||
type MetadataDirectiveType string
|
||||
|
||||
const (
|
||||
// MetaCopy 目标对象使用源对象的META
|
||||
MetaCopy MetadataDirectiveType = "COPY"
|
||||
|
||||
// MetaReplace 目标对象使用自定义的META
|
||||
MetaReplace MetadataDirectiveType = "REPLACE"
|
||||
)
|
||||
|
||||
// Http头标签
|
||||
const (
|
||||
HTTPHeaderAcceptEncoding string = "Accept-Encoding"
|
||||
HTTPHeaderAuthorization = "Authorization"
|
||||
HTTPHeaderCacheControl = "Cache-Control"
|
||||
HTTPHeaderContentDisposition = "Content-Disposition"
|
||||
HTTPHeaderContentEncoding = "Content-Encoding"
|
||||
HTTPHeaderContentLength = "Content-Length"
|
||||
HTTPHeaderContentMD5 = "Content-MD5"
|
||||
HTTPHeaderContentType = "Content-Type"
|
||||
HTTPHeaderContentLanguage = "Content-Language"
|
||||
HTTPHeaderDate = "Date"
|
||||
HTTPHeaderEtag = "ETag"
|
||||
HTTPHeaderExpires = "Expires"
|
||||
HTTPHeaderHost = "Host"
|
||||
HTTPHeaderLastModified = "Last-Modified"
|
||||
HTTPHeaderRange = "Range"
|
||||
HTTPHeaderLocation = "Location"
|
||||
HTTPHeaderOrigin = "Origin"
|
||||
HTTPHeaderServer = "Server"
|
||||
HTTPHeaderUserAgent = "User-Agent"
|
||||
HTTPHeaderIfModifiedSince = "If-Modified-Since"
|
||||
HTTPHeaderIfUnmodifiedSince = "If-Unmodified-Since"
|
||||
HTTPHeaderIfMatch = "If-Match"
|
||||
HTTPHeaderIfNoneMatch = "If-None-Match"
|
||||
|
||||
HTTPHeaderOssACL = "X-Oss-Acl"
|
||||
HTTPHeaderOssMetaPrefix = "X-Oss-Meta-"
|
||||
HTTPHeaderOssObjectACL = "X-Oss-Object-Acl"
|
||||
HTTPHeaderOssSecurityToken = "X-Oss-Security-Token"
|
||||
HTTPHeaderOssServerSideEncryption = "X-Oss-Server-Side-Encryption"
|
||||
HTTPHeaderOssCopySource = "X-Oss-Copy-Source"
|
||||
HTTPHeaderOssCopySourceRange = "X-Oss-Copy-Source-Range"
|
||||
HTTPHeaderOssCopySourceIfMatch = "X-Oss-Copy-Source-If-Match"
|
||||
HTTPHeaderOssCopySourceIfNoneMatch = "X-Oss-Copy-Source-If-None-Match"
|
||||
HTTPHeaderOssCopySourceIfModifiedSince = "X-Oss-Copy-Source-If-Modified-Since"
|
||||
HTTPHeaderOssCopySourceIfUnmodifiedSince = "X-Oss-Copy-Source-If-Unmodified-Since"
|
||||
HTTPHeaderOssMetadataDirective = "X-Oss-Metadata-Directive"
|
||||
HTTPHeaderOssNextAppendPosition = "X-Oss-Next-Append-Position"
|
||||
HTTPHeaderOssRequestID = "X-Oss-Request-Id"
|
||||
HTTPHeaderOssCRC64 = "X-Oss-Hash-Crc64ecma"
|
||||
)
|
||||
|
||||
// 其它常量
|
||||
const (
|
||||
MaxPartSize = 5 * 1024 * 1024 * 1024 // 文件片最大值,5GB
|
||||
MinPartSize = 100 * 1024 // 文件片最小值,100KBß
|
||||
|
||||
FilePermMode = os.FileMode(0664) // 新建文件默认权限
|
||||
|
||||
TempFilePrefix = "oss-go-temp-" // 临时文件前缀
|
||||
TempFileSuffix = ".temp" // 临时文件后缀
|
||||
|
||||
CheckpointFileSuffix = ".cp" // Checkpoint文件后缀
|
||||
|
||||
Version = "1.3.0" // Go sdk版本
|
||||
)
|
|
@ -0,0 +1,44 @@
|
|||
package oss
|
||||
|
||||
import (
|
||||
"hash"
|
||||
"hash/crc64"
|
||||
)
|
||||
|
||||
// digest represents the partial evaluation of a checksum.
|
||||
type digest struct {
|
||||
crc uint64
|
||||
tab *crc64.Table
|
||||
}
|
||||
|
||||
// NewCRC creates a new hash.Hash64 computing the CRC-64 checksum
|
||||
// using the polynomial represented by the Table.
|
||||
func NewCRC(tab *crc64.Table, init uint64) hash.Hash64 { return &digest{init, tab} }
|
||||
|
||||
// Size returns the number of bytes Sum will return.
|
||||
func (d *digest) Size() int { return crc64.Size }
|
||||
|
||||
// BlockSize returns the hash's underlying block size.
|
||||
// The Write method must be able to accept any amount
|
||||
// of data, but it may operate more efficiently if all writes
|
||||
// are a multiple of the block size.
|
||||
func (d *digest) BlockSize() int { return 1 }
|
||||
|
||||
// Reset resets the Hash to its initial state.
|
||||
func (d *digest) Reset() { d.crc = 0 }
|
||||
|
||||
// Write (via the embedded io.Writer interface) adds more data to the running hash.
|
||||
// It never returns an error.
|
||||
func (d *digest) Write(p []byte) (n int, err error) {
|
||||
d.crc = crc64.Update(d.crc, d.tab, p)
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// Sum64 returns crc64 value.
|
||||
func (d *digest) Sum64() uint64 { return d.crc }
|
||||
|
||||
// Sum returns hash value.
|
||||
func (d *digest) Sum(in []byte) []byte {
|
||||
s := d.Sum64()
|
||||
return append(in, byte(s>>56), byte(s>>48), byte(s>>40), byte(s>>32), byte(s>>24), byte(s>>16), byte(s>>8), byte(s))
|
||||
}
|
|
@ -0,0 +1,464 @@
|
|||
package oss
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
//
|
||||
// DownloadFile 分片下载文件
|
||||
//
|
||||
// objectKey object key。
|
||||
// filePath 本地文件。objectKey下载到文件。
|
||||
// partSize 本次上传文件片的大小,字节数。比如100 * 1024为每片100KB。
|
||||
// options Object的属性限制项。详见GetObject。
|
||||
//
|
||||
// error 操作成功error为nil,非nil为错误信息。
|
||||
//
|
||||
func (bucket Bucket) DownloadFile(objectKey, filePath string, partSize int64, options ...Option) error {
|
||||
if partSize < 1 || partSize > MaxPartSize {
|
||||
return errors.New("oss: part size invalid range (1, 5GB]")
|
||||
}
|
||||
|
||||
cpConf, err := getCpConfig(options, filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
routines := getRoutines(options)
|
||||
|
||||
if cpConf.IsEnable {
|
||||
return bucket.downloadFileWithCp(objectKey, filePath, partSize, options, cpConf.FilePath, routines)
|
||||
}
|
||||
|
||||
return bucket.downloadFile(objectKey, filePath, partSize, options, routines)
|
||||
}
|
||||
|
||||
// ----- 并发无断点的下载 -----
|
||||
|
||||
// 工作协程参数
|
||||
type downloadWorkerArg struct {
|
||||
bucket *Bucket
|
||||
key string
|
||||
filePath string
|
||||
options []Option
|
||||
hook downloadPartHook
|
||||
}
|
||||
|
||||
// Hook用于测试
|
||||
type downloadPartHook func(part downloadPart) error
|
||||
|
||||
var downloadPartHooker downloadPartHook = defaultDownloadPartHook
|
||||
|
||||
func defaultDownloadPartHook(part downloadPart) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 默认ProgressListener,屏蔽GetObject的Options中ProgressListener
|
||||
type defaultDownloadProgressListener struct {
|
||||
}
|
||||
|
||||
// ProgressChanged 静默处理
|
||||
func (listener *defaultDownloadProgressListener) ProgressChanged(event *ProgressEvent) {
|
||||
}
|
||||
|
||||
// 工作协程
|
||||
func downloadWorker(id int, arg downloadWorkerArg, jobs <-chan downloadPart, results chan<- downloadPart, failed chan<- error, die <-chan bool) {
|
||||
for part := range jobs {
|
||||
if err := arg.hook(part); err != nil {
|
||||
failed <- err
|
||||
break
|
||||
}
|
||||
|
||||
// resolve options
|
||||
r := Range(part.Start, part.End)
|
||||
p := Progress(&defaultDownloadProgressListener{})
|
||||
opts := make([]Option, len(arg.options)+2)
|
||||
// append orderly, can not be reversed!
|
||||
opts = append(opts, arg.options...)
|
||||
opts = append(opts, r, p)
|
||||
|
||||
rd, err := arg.bucket.GetObject(arg.key, opts...)
|
||||
if err != nil {
|
||||
failed <- err
|
||||
break
|
||||
}
|
||||
defer rd.Close()
|
||||
|
||||
select {
|
||||
case <-die:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
fd, err := os.OpenFile(arg.filePath, os.O_WRONLY, FilePermMode)
|
||||
if err != nil {
|
||||
failed <- err
|
||||
break
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
_, err = fd.Seek(part.Start, os.SEEK_SET)
|
||||
if err != nil {
|
||||
failed <- err
|
||||
break
|
||||
}
|
||||
|
||||
_, err = io.Copy(fd, rd)
|
||||
if err != nil {
|
||||
failed <- err
|
||||
break
|
||||
}
|
||||
|
||||
results <- part
|
||||
}
|
||||
}
|
||||
|
||||
// 调度协程
|
||||
func downloadScheduler(jobs chan downloadPart, parts []downloadPart) {
|
||||
for _, part := range parts {
|
||||
jobs <- part
|
||||
}
|
||||
close(jobs)
|
||||
}
|
||||
|
||||
// 下载片
|
||||
type downloadPart struct {
|
||||
Index int // 片序号,从0开始编号
|
||||
Start int64 // 片起始位置
|
||||
End int64 // 片结束位置
|
||||
}
|
||||
|
||||
// 文件分片
|
||||
func getDownloadParts(bucket *Bucket, objectKey string, partSize int64) ([]downloadPart, error) {
|
||||
meta, err := bucket.GetObjectDetailedMeta(objectKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
parts := []downloadPart{}
|
||||
objectSize, err := strconv.ParseInt(meta.Get(HTTPHeaderContentLength), 10, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
part := downloadPart{}
|
||||
i := 0
|
||||
for offset := int64(0); offset < objectSize; offset += partSize {
|
||||
part.Index = i
|
||||
part.Start = offset
|
||||
part.End = GetPartEnd(offset, objectSize, partSize)
|
||||
parts = append(parts, part)
|
||||
i++
|
||||
}
|
||||
return parts, nil
|
||||
}
|
||||
|
||||
// 文件大小
|
||||
func getObjectBytes(parts []downloadPart) int64 {
|
||||
var ob int64
|
||||
for _, part := range parts {
|
||||
ob += (part.End - part.Start + 1)
|
||||
}
|
||||
return ob
|
||||
}
|
||||
|
||||
// 并发无断点续传的下载
|
||||
func (bucket Bucket) downloadFile(objectKey, filePath string, partSize int64, options []Option, routines int) error {
|
||||
tempFilePath := filePath + TempFileSuffix
|
||||
listener := getProgressListener(options)
|
||||
|
||||
// 如果文件不存在则创建,存在不清空,下载分片会重写文件内容
|
||||
fd, err := os.OpenFile(tempFilePath, os.O_WRONLY|os.O_CREATE, FilePermMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fd.Close()
|
||||
|
||||
// 分割文件
|
||||
parts, err := getDownloadParts(&bucket, objectKey, partSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
jobs := make(chan downloadPart, len(parts))
|
||||
results := make(chan downloadPart, len(parts))
|
||||
failed := make(chan error)
|
||||
die := make(chan bool)
|
||||
|
||||
var completedBytes int64
|
||||
totalBytes := getObjectBytes(parts)
|
||||
event := newProgressEvent(TransferStartedEvent, 0, totalBytes)
|
||||
publishProgress(listener, event)
|
||||
|
||||
// 启动工作协程
|
||||
arg := downloadWorkerArg{&bucket, objectKey, tempFilePath, options, downloadPartHooker}
|
||||
for w := 1; w <= routines; w++ {
|
||||
go downloadWorker(w, arg, jobs, results, failed, die)
|
||||
}
|
||||
|
||||
// 并发上传分片
|
||||
go downloadScheduler(jobs, parts)
|
||||
|
||||
// 等待分片下载完成
|
||||
completed := 0
|
||||
ps := make([]downloadPart, len(parts))
|
||||
for completed < len(parts) {
|
||||
select {
|
||||
case part := <-results:
|
||||
completed++
|
||||
ps[part.Index] = part
|
||||
completedBytes += (part.End - part.Start + 1)
|
||||
event = newProgressEvent(TransferDataEvent, completedBytes, totalBytes)
|
||||
publishProgress(listener, event)
|
||||
case err := <-failed:
|
||||
close(die)
|
||||
event = newProgressEvent(TransferFailedEvent, completedBytes, totalBytes)
|
||||
publishProgress(listener, event)
|
||||
return err
|
||||
}
|
||||
|
||||
if completed >= len(parts) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
event = newProgressEvent(TransferCompletedEvent, completedBytes, totalBytes)
|
||||
publishProgress(listener, event)
|
||||
|
||||
return os.Rename(tempFilePath, filePath)
|
||||
}
|
||||
|
||||
// ----- 并发有断点的下载 -----
|
||||
|
||||
const downloadCpMagic = "92611BED-89E2-46B6-89E5-72F273D4B0A3"
|
||||
|
||||
type downloadCheckpoint struct {
|
||||
Magic string // magic
|
||||
MD5 string // cp内容的MD5
|
||||
FilePath string // 本地文件
|
||||
Object string // key
|
||||
ObjStat objectStat // 文件状态
|
||||
Parts []downloadPart // 全部分片
|
||||
PartStat []bool // 分片下载是否完成
|
||||
}
|
||||
|
||||
type objectStat struct {
|
||||
Size int64 // 大小
|
||||
LastModified string // 最后修改时间
|
||||
Etag string // etag
|
||||
}
|
||||
|
||||
// CP数据是否有效,CP有效且Object没有更新时有效
|
||||
func (cp downloadCheckpoint) isValid(bucket *Bucket, objectKey string) (bool, error) {
|
||||
// 比较CP的Magic及MD5
|
||||
cpb := cp
|
||||
cpb.MD5 = ""
|
||||
js, _ := json.Marshal(cpb)
|
||||
sum := md5.Sum(js)
|
||||
b64 := base64.StdEncoding.EncodeToString(sum[:])
|
||||
|
||||
if cp.Magic != downloadCpMagic || b64 != cp.MD5 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// 确认object没有更新
|
||||
meta, err := bucket.GetObjectDetailedMeta(objectKey)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
objectSize, err := strconv.ParseInt(meta.Get(HTTPHeaderContentLength), 10, 0)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// 比较Object的大小/最后修改时间/etag
|
||||
if cp.ObjStat.Size != objectSize ||
|
||||
cp.ObjStat.LastModified != meta.Get(HTTPHeaderLastModified) ||
|
||||
cp.ObjStat.Etag != meta.Get(HTTPHeaderEtag) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// 从文件中load
|
||||
func (cp *downloadCheckpoint) load(filePath string) error {
|
||||
contents, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(contents, cp)
|
||||
return err
|
||||
}
|
||||
|
||||
// dump到文件
|
||||
func (cp *downloadCheckpoint) dump(filePath string) error {
|
||||
bcp := *cp
|
||||
|
||||
// 计算MD5
|
||||
bcp.MD5 = ""
|
||||
js, err := json.Marshal(bcp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sum := md5.Sum(js)
|
||||
b64 := base64.StdEncoding.EncodeToString(sum[:])
|
||||
bcp.MD5 = b64
|
||||
|
||||
// 序列化
|
||||
js, err = json.Marshal(bcp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// dump
|
||||
return ioutil.WriteFile(filePath, js, FilePermMode)
|
||||
}
|
||||
|
||||
// 未完成的分片
|
||||
func (cp downloadCheckpoint) todoParts() []downloadPart {
|
||||
dps := []downloadPart{}
|
||||
for i, ps := range cp.PartStat {
|
||||
if !ps {
|
||||
dps = append(dps, cp.Parts[i])
|
||||
}
|
||||
}
|
||||
return dps
|
||||
}
|
||||
|
||||
// 完成的字节数
|
||||
func (cp downloadCheckpoint) getCompletedBytes() int64 {
|
||||
var completedBytes int64
|
||||
for i, part := range cp.Parts {
|
||||
if cp.PartStat[i] {
|
||||
completedBytes += (part.End - part.Start + 1)
|
||||
}
|
||||
}
|
||||
return completedBytes
|
||||
}
|
||||
|
||||
// 初始化下载任务
|
||||
func (cp *downloadCheckpoint) prepare(bucket *Bucket, objectKey, filePath string, partSize int64) error {
|
||||
// cp
|
||||
cp.Magic = downloadCpMagic
|
||||
cp.FilePath = filePath
|
||||
cp.Object = objectKey
|
||||
|
||||
// object
|
||||
meta, err := bucket.GetObjectDetailedMeta(objectKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
objectSize, err := strconv.ParseInt(meta.Get(HTTPHeaderContentLength), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cp.ObjStat.Size = objectSize
|
||||
cp.ObjStat.LastModified = meta.Get(HTTPHeaderLastModified)
|
||||
cp.ObjStat.Etag = meta.Get(HTTPHeaderEtag)
|
||||
|
||||
// parts
|
||||
cp.Parts, err = getDownloadParts(bucket, objectKey, partSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cp.PartStat = make([]bool, len(cp.Parts))
|
||||
for i := range cp.PartStat {
|
||||
cp.PartStat[i] = false
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cp *downloadCheckpoint) complete(cpFilePath, downFilepath string) error {
|
||||
os.Remove(cpFilePath)
|
||||
return os.Rename(downFilepath, cp.FilePath)
|
||||
}
|
||||
|
||||
// 并发带断点的下载
|
||||
func (bucket Bucket) downloadFileWithCp(objectKey, filePath string, partSize int64, options []Option, cpFilePath string, routines int) error {
|
||||
tempFilePath := filePath + TempFileSuffix
|
||||
listener := getProgressListener(options)
|
||||
|
||||
// LOAD CP数据
|
||||
dcp := downloadCheckpoint{}
|
||||
err := dcp.load(cpFilePath)
|
||||
if err != nil {
|
||||
os.Remove(cpFilePath)
|
||||
}
|
||||
|
||||
// LOAD出错或数据无效重新初始化下载
|
||||
valid, err := dcp.isValid(&bucket, objectKey)
|
||||
if err != nil || !valid {
|
||||
if err = dcp.prepare(&bucket, objectKey, filePath, partSize); err != nil {
|
||||
return err
|
||||
}
|
||||
os.Remove(cpFilePath)
|
||||
}
|
||||
|
||||
// 如果文件不存在则创建,存在不清空,下载分片会重写文件内容
|
||||
fd, err := os.OpenFile(tempFilePath, os.O_WRONLY|os.O_CREATE, FilePermMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fd.Close()
|
||||
|
||||
// 未完成的分片
|
||||
parts := dcp.todoParts()
|
||||
jobs := make(chan downloadPart, len(parts))
|
||||
results := make(chan downloadPart, len(parts))
|
||||
failed := make(chan error)
|
||||
die := make(chan bool)
|
||||
|
||||
completedBytes := dcp.getCompletedBytes()
|
||||
event := newProgressEvent(TransferStartedEvent, completedBytes, dcp.ObjStat.Size)
|
||||
publishProgress(listener, event)
|
||||
|
||||
// 启动工作协程
|
||||
arg := downloadWorkerArg{&bucket, objectKey, tempFilePath, options, downloadPartHooker}
|
||||
for w := 1; w <= routines; w++ {
|
||||
go downloadWorker(w, arg, jobs, results, failed, die)
|
||||
}
|
||||
|
||||
// 并发下载分片
|
||||
go downloadScheduler(jobs, parts)
|
||||
|
||||
// 等待分片下载完成
|
||||
completed := 0
|
||||
for completed < len(parts) {
|
||||
select {
|
||||
case part := <-results:
|
||||
completed++
|
||||
dcp.PartStat[part.Index] = true
|
||||
dcp.dump(cpFilePath)
|
||||
completedBytes += (part.End - part.Start + 1)
|
||||
event = newProgressEvent(TransferDataEvent, completedBytes, dcp.ObjStat.Size)
|
||||
publishProgress(listener, event)
|
||||
case err := <-failed:
|
||||
close(die)
|
||||
event = newProgressEvent(TransferFailedEvent, completedBytes, dcp.ObjStat.Size)
|
||||
publishProgress(listener, event)
|
||||
return err
|
||||
}
|
||||
|
||||
if completed >= len(parts) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
event = newProgressEvent(TransferCompletedEvent, completedBytes, dcp.ObjStat.Size)
|
||||
publishProgress(listener, event)
|
||||
|
||||
return dcp.complete(cpFilePath, tempFilePath)
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
package oss
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ServiceError contains fields of the error response from Oss Service REST API.
|
||||
type ServiceError struct {
|
||||
XMLName xml.Name `xml:"Error"`
|
||||
Code string `xml:"Code"` // OSS返回给用户的错误码
|
||||
Message string `xml:"Message"` // OSS给出的详细错误信息
|
||||
RequestID string `xml:"RequestId"` // 用于唯一标识该次请求的UUID
|
||||
HostID string `xml:"HostId"` // 用于标识访问的OSS集群
|
||||
RawMessage string // OSS返回的原始消息内容
|
||||
StatusCode int // HTTP状态码
|
||||
}
|
||||
|
||||
// Implement interface error
|
||||
func (e ServiceError) Error() string {
|
||||
return fmt.Sprintf("oss: service returned error: StatusCode=%d, ErrorCode=%s, ErrorMessage=%s, RequestId=%s",
|
||||
e.StatusCode, e.Code, e.Message, e.RequestID)
|
||||
}
|
||||
|
||||
// UnexpectedStatusCodeError is returned when a storage service responds with neither an error
|
||||
// nor with an HTTP status code indicating success.
|
||||
type UnexpectedStatusCodeError struct {
|
||||
allowed []int // 预期OSS返回HTTP状态码
|
||||
got int // OSS实际返回HTTP状态码
|
||||
}
|
||||
|
||||
// Implement interface error
|
||||
func (e UnexpectedStatusCodeError) Error() string {
|
||||
s := func(i int) string { return fmt.Sprintf("%d %s", i, http.StatusText(i)) }
|
||||
|
||||
got := s(e.got)
|
||||
expected := []string{}
|
||||
for _, v := range e.allowed {
|
||||
expected = append(expected, s(v))
|
||||
}
|
||||
return fmt.Sprintf("oss: status code from service response is %s; was expecting %s",
|
||||
got, strings.Join(expected, " or "))
|
||||
}
|
||||
|
||||
// Got is the actual status code returned by oss.
|
||||
func (e UnexpectedStatusCodeError) Got() int {
|
||||
return e.got
|
||||
}
|
||||
|
||||
// checkRespCode returns UnexpectedStatusError if the given response code is not
|
||||
// one of the allowed status codes; otherwise nil.
|
||||
func checkRespCode(respCode int, allowed []int) error {
|
||||
for _, v := range allowed {
|
||||
if respCode == v {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return UnexpectedStatusCodeError{allowed, respCode}
|
||||
}
|
||||
|
||||
// CRCCheckError is returned when crc check is inconsistent between client and server
|
||||
type CRCCheckError struct {
|
||||
clientCRC uint64 // 客户端计算的CRC64值
|
||||
serverCRC uint64 // 服务端计算的CRC64值
|
||||
operation string // 上传操作,如PutObject/AppendObject/UploadPart等
|
||||
requestID string // 本次操作的RequestID
|
||||
}
|
||||
|
||||
// Implement interface error
|
||||
func (e CRCCheckError) Error() string {
|
||||
return fmt.Sprintf("oss: the crc of %s is inconsistent, client %d but server %d; request id is %s",
|
||||
e.operation, e.clientCRC, e.serverCRC, e.requestID)
|
||||
}
|
||||
|
||||
func checkCRC(resp *Response, operation string) error {
|
||||
if resp.Headers.Get(HTTPHeaderOssCRC64) == "" || resp.ClientCRC == resp.ServerCRC {
|
||||
return nil
|
||||
}
|
||||
return CRCCheckError{resp.ClientCRC, resp.ServerCRC, operation, resp.Headers.Get(HTTPHeaderOssRequestID)}
|
||||
}
|
|
@ -0,0 +1,245 @@
|
|||
package oss
|
||||
|
||||
import (
|
||||
"mime"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var extToMimeType = map[string]string{
|
||||
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
".xltx": "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
|
||||
".potx": "application/vnd.openxmlformats-officedocument.presentationml.template",
|
||||
".ppsx": "application/vnd.openxmlformats-officedocument.presentationml.slideshow",
|
||||
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||
".sldx": "application/vnd.openxmlformats-officedocument.presentationml.slide",
|
||||
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
".dotx": "application/vnd.openxmlformats-officedocument.wordprocessingml.template",
|
||||
".xlam": "application/vnd.ms-excel.addin.macroEnabled.12",
|
||||
".xlsb": "application/vnd.ms-excel.sheet.binary.macroEnabled.12",
|
||||
".apk": "application/vnd.android.package-archive",
|
||||
".hqx": "application/mac-binhex40",
|
||||
".cpt": "application/mac-compactpro",
|
||||
".doc": "application/msword",
|
||||
".ogg": "application/ogg",
|
||||
".pdf": "application/pdf",
|
||||
".rtf": "text/rtf",
|
||||
".mif": "application/vnd.mif",
|
||||
".xls": "application/vnd.ms-excel",
|
||||
".ppt": "application/vnd.ms-powerpoint",
|
||||
".odc": "application/vnd.oasis.opendocument.chart",
|
||||
".odb": "application/vnd.oasis.opendocument.database",
|
||||
".odf": "application/vnd.oasis.opendocument.formula",
|
||||
".odg": "application/vnd.oasis.opendocument.graphics",
|
||||
".otg": "application/vnd.oasis.opendocument.graphics-template",
|
||||
".odi": "application/vnd.oasis.opendocument.image",
|
||||
".odp": "application/vnd.oasis.opendocument.presentation",
|
||||
".otp": "application/vnd.oasis.opendocument.presentation-template",
|
||||
".ods": "application/vnd.oasis.opendocument.spreadsheet",
|
||||
".ots": "application/vnd.oasis.opendocument.spreadsheet-template",
|
||||
".odt": "application/vnd.oasis.opendocument.text",
|
||||
".odm": "application/vnd.oasis.opendocument.text-master",
|
||||
".ott": "application/vnd.oasis.opendocument.text-template",
|
||||
".oth": "application/vnd.oasis.opendocument.text-web",
|
||||
".sxw": "application/vnd.sun.xml.writer",
|
||||
".stw": "application/vnd.sun.xml.writer.template",
|
||||
".sxc": "application/vnd.sun.xml.calc",
|
||||
".stc": "application/vnd.sun.xml.calc.template",
|
||||
".sxd": "application/vnd.sun.xml.draw",
|
||||
".std": "application/vnd.sun.xml.draw.template",
|
||||
".sxi": "application/vnd.sun.xml.impress",
|
||||
".sti": "application/vnd.sun.xml.impress.template",
|
||||
".sxg": "application/vnd.sun.xml.writer.global",
|
||||
".sxm": "application/vnd.sun.xml.math",
|
||||
".sis": "application/vnd.symbian.install",
|
||||
".wbxml": "application/vnd.wap.wbxml",
|
||||
".wmlc": "application/vnd.wap.wmlc",
|
||||
".wmlsc": "application/vnd.wap.wmlscriptc",
|
||||
".bcpio": "application/x-bcpio",
|
||||
".torrent": "application/x-bittorrent",
|
||||
".bz2": "application/x-bzip2",
|
||||
".vcd": "application/x-cdlink",
|
||||
".pgn": "application/x-chess-pgn",
|
||||
".cpio": "application/x-cpio",
|
||||
".csh": "application/x-csh",
|
||||
".dvi": "application/x-dvi",
|
||||
".spl": "application/x-futuresplash",
|
||||
".gtar": "application/x-gtar",
|
||||
".hdf": "application/x-hdf",
|
||||
".jar": "application/x-java-archive",
|
||||
".jnlp": "application/x-java-jnlp-file",
|
||||
".js": "application/x-javascript",
|
||||
".ksp": "application/x-kspread",
|
||||
".chrt": "application/x-kchart",
|
||||
".kil": "application/x-killustrator",
|
||||
".latex": "application/x-latex",
|
||||
".rpm": "application/x-rpm",
|
||||
".sh": "application/x-sh",
|
||||
".shar": "application/x-shar",
|
||||
".swf": "application/x-shockwave-flash",
|
||||
".sit": "application/x-stuffit",
|
||||
".sv4cpio": "application/x-sv4cpio",
|
||||
".sv4crc": "application/x-sv4crc",
|
||||
".tar": "application/x-tar",
|
||||
".tcl": "application/x-tcl",
|
||||
".tex": "application/x-tex",
|
||||
".man": "application/x-troff-man",
|
||||
".me": "application/x-troff-me",
|
||||
".ms": "application/x-troff-ms",
|
||||
".ustar": "application/x-ustar",
|
||||
".src": "application/x-wais-source",
|
||||
".zip": "application/zip",
|
||||
".m3u": "audio/x-mpegurl",
|
||||
".ra": "audio/x-pn-realaudio",
|
||||
".wav": "audio/x-wav",
|
||||
".wma": "audio/x-ms-wma",
|
||||
".wax": "audio/x-ms-wax",
|
||||
".pdb": "chemical/x-pdb",
|
||||
".xyz": "chemical/x-xyz",
|
||||
".bmp": "image/bmp",
|
||||
".gif": "image/gif",
|
||||
".ief": "image/ief",
|
||||
".png": "image/png",
|
||||
".wbmp": "image/vnd.wap.wbmp",
|
||||
".ras": "image/x-cmu-raster",
|
||||
".pnm": "image/x-portable-anymap",
|
||||
".pbm": "image/x-portable-bitmap",
|
||||
".pgm": "image/x-portable-graymap",
|
||||
".ppm": "image/x-portable-pixmap",
|
||||
".rgb": "image/x-rgb",
|
||||
".xbm": "image/x-xbitmap",
|
||||
".xpm": "image/x-xpixmap",
|
||||
".xwd": "image/x-xwindowdump",
|
||||
".css": "text/css",
|
||||
".rtx": "text/richtext",
|
||||
".tsv": "text/tab-separated-values",
|
||||
".jad": "text/vnd.sun.j2me.app-descriptor",
|
||||
".wml": "text/vnd.wap.wml",
|
||||
".wmls": "text/vnd.wap.wmlscript",
|
||||
".etx": "text/x-setext",
|
||||
".mxu": "video/vnd.mpegurl",
|
||||
".flv": "video/x-flv",
|
||||
".wm": "video/x-ms-wm",
|
||||
".wmv": "video/x-ms-wmv",
|
||||
".wmx": "video/x-ms-wmx",
|
||||
".wvx": "video/x-ms-wvx",
|
||||
".avi": "video/x-msvideo",
|
||||
".movie": "video/x-sgi-movie",
|
||||
".ice": "x-conference/x-cooltalk",
|
||||
".3gp": "video/3gpp",
|
||||
".ai": "application/postscript",
|
||||
".aif": "audio/x-aiff",
|
||||
".aifc": "audio/x-aiff",
|
||||
".aiff": "audio/x-aiff",
|
||||
".asc": "text/plain",
|
||||
".atom": "application/atom+xml",
|
||||
".au": "audio/basic",
|
||||
".bin": "application/octet-stream",
|
||||
".cdf": "application/x-netcdf",
|
||||
".cgm": "image/cgm",
|
||||
".class": "application/octet-stream",
|
||||
".dcr": "application/x-director",
|
||||
".dif": "video/x-dv",
|
||||
".dir": "application/x-director",
|
||||
".djv": "image/vnd.djvu",
|
||||
".djvu": "image/vnd.djvu",
|
||||
".dll": "application/octet-stream",
|
||||
".dmg": "application/octet-stream",
|
||||
".dms": "application/octet-stream",
|
||||
".dtd": "application/xml-dtd",
|
||||
".dv": "video/x-dv",
|
||||
".dxr": "application/x-director",
|
||||
".eps": "application/postscript",
|
||||
".exe": "application/octet-stream",
|
||||
".ez": "application/andrew-inset",
|
||||
".gram": "application/srgs",
|
||||
".grxml": "application/srgs+xml",
|
||||
".gz": "application/x-gzip",
|
||||
".htm": "text/html",
|
||||
".html": "text/html",
|
||||
".ico": "image/x-icon",
|
||||
".ics": "text/calendar",
|
||||
".ifb": "text/calendar",
|
||||
".iges": "model/iges",
|
||||
".igs": "model/iges",
|
||||
".jp2": "image/jp2",
|
||||
".jpe": "image/jpeg",
|
||||
".jpeg": "image/jpeg",
|
||||
".jpg": "image/jpeg",
|
||||
".kar": "audio/midi",
|
||||
".lha": "application/octet-stream",
|
||||
".lzh": "application/octet-stream",
|
||||
".m4a": "audio/mp4a-latm",
|
||||
".m4p": "audio/mp4a-latm",
|
||||
".m4u": "video/vnd.mpegurl",
|
||||
".m4v": "video/x-m4v",
|
||||
".mac": "image/x-macpaint",
|
||||
".mathml": "application/mathml+xml",
|
||||
".mesh": "model/mesh",
|
||||
".mid": "audio/midi",
|
||||
".midi": "audio/midi",
|
||||
".mov": "video/quicktime",
|
||||
".mp2": "audio/mpeg",
|
||||
".mp3": "audio/mpeg",
|
||||
".mp4": "video/mp4",
|
||||
".mpe": "video/mpeg",
|
||||
".mpeg": "video/mpeg",
|
||||
".mpg": "video/mpeg",
|
||||
".mpga": "audio/mpeg",
|
||||
".msh": "model/mesh",
|
||||
".nc": "application/x-netcdf",
|
||||
".oda": "application/oda",
|
||||
".ogv": "video/ogv",
|
||||
".pct": "image/pict",
|
||||
".pic": "image/pict",
|
||||
".pict": "image/pict",
|
||||
".pnt": "image/x-macpaint",
|
||||
".pntg": "image/x-macpaint",
|
||||
".ps": "application/postscript",
|
||||
".qt": "video/quicktime",
|
||||
".qti": "image/x-quicktime",
|
||||
".qtif": "image/x-quicktime",
|
||||
".ram": "audio/x-pn-realaudio",
|
||||
".rdf": "application/rdf+xml",
|
||||
".rm": "application/vnd.rn-realmedia",
|
||||
".roff": "application/x-troff",
|
||||
".sgm": "text/sgml",
|
||||
".sgml": "text/sgml",
|
||||
".silo": "model/mesh",
|
||||
".skd": "application/x-koan",
|
||||
".skm": "application/x-koan",
|
||||
".skp": "application/x-koan",
|
||||
".skt": "application/x-koan",
|
||||
".smi": "application/smil",
|
||||
".smil": "application/smil",
|
||||
".snd": "audio/basic",
|
||||
".so": "application/octet-stream",
|
||||
".svg": "image/svg+xml",
|
||||
".t": "application/x-troff",
|
||||
".texi": "application/x-texinfo",
|
||||
".texinfo": "application/x-texinfo",
|
||||
".tif": "image/tiff",
|
||||
".tiff": "image/tiff",
|
||||
".tr": "application/x-troff",
|
||||
".txt": "text/plain",
|
||||
".vrml": "model/vrml",
|
||||
".vxml": "application/voicexml+xml",
|
||||
".webm": "video/webm",
|
||||
".wrl": "model/vrml",
|
||||
".xht": "application/xhtml+xml",
|
||||
".xhtml": "application/xhtml+xml",
|
||||
".xml": "application/xml",
|
||||
".xsl": "application/xml",
|
||||
".xslt": "application/xslt+xml",
|
||||
".xul": "application/vnd.mozilla.xul+xml",
|
||||
}
|
||||
|
||||
// TypeByExtension returns the MIME type associated with the file extension ext.
|
||||
// 获取文件类型,选项ContentType使用
|
||||
func TypeByExtension(filePath string) string {
|
||||
typ := mime.TypeByExtension(path.Ext(filePath))
|
||||
if typ == "" {
|
||||
typ = extToMimeType[strings.ToLower(path.Ext(filePath))]
|
||||
}
|
||||
return typ
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package oss
|
||||
|
||||
import (
|
||||
"hash"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Response Http response from oss
|
||||
type Response struct {
|
||||
StatusCode int
|
||||
Headers http.Header
|
||||
Body io.ReadCloser
|
||||
ClientCRC uint64
|
||||
ServerCRC uint64
|
||||
}
|
||||
|
||||
// PutObjectRequest The request of DoPutObject
|
||||
type PutObjectRequest struct {
|
||||
ObjectKey string
|
||||
Reader io.Reader
|
||||
}
|
||||
|
||||
// GetObjectRequest The request of DoGetObject
|
||||
type GetObjectRequest struct {
|
||||
ObjectKey string
|
||||
}
|
||||
|
||||
// GetObjectResult The result of DoGetObject
|
||||
type GetObjectResult struct {
|
||||
Response *Response
|
||||
ClientCRC hash.Hash64
|
||||
ServerCRC uint64
|
||||
}
|
||||
|
||||
// AppendObjectRequest The requtest of DoAppendObject
|
||||
type AppendObjectRequest struct {
|
||||
ObjectKey string
|
||||
Reader io.Reader
|
||||
Position int64
|
||||
}
|
||||
|
||||
// AppendObjectResult The result of DoAppendObject
|
||||
type AppendObjectResult struct {
|
||||
NextPosition int64
|
||||
CRC uint64
|
||||
}
|
||||
|
||||
// UploadPartRequest The request of DoUploadPart
|
||||
type UploadPartRequest struct {
|
||||
InitResult *InitiateMultipartUploadResult
|
||||
Reader io.Reader
|
||||
PartSize int64
|
||||
PartNumber int
|
||||
}
|
||||
|
||||
// UploadPartResult The result of DoUploadPart
|
||||
type UploadPartResult struct {
|
||||
Part UploadPart
|
||||
}
|
|
@ -0,0 +1,461 @@
|
|||
package oss
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
//
|
||||
// CopyFile 分片复制文件
|
||||
//
|
||||
// srcBucketName 源Bucket名称。
|
||||
// srcObjectKey 源Object名称。
|
||||
// destObjectKey 目标Object名称。目标Bucket名称为Bucket.BucketName。
|
||||
// partSize 复制文件片的大小,字节数。比如100 * 1024为每片100KB。
|
||||
// options Object的属性限制项。详见InitiateMultipartUpload。
|
||||
//
|
||||
// error 操作成功error为nil,非nil为错误信息。
|
||||
//
|
||||
func (bucket Bucket) CopyFile(srcBucketName, srcObjectKey, destObjectKey string, partSize int64, options ...Option) error {
|
||||
destBucketName := bucket.BucketName
|
||||
if partSize < MinPartSize || partSize > MaxPartSize {
|
||||
return errors.New("oss: part size invalid range (1024KB, 5GB]")
|
||||
}
|
||||
|
||||
cpConf, err := getCpConfig(options, filepath.Base(destObjectKey))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
routines := getRoutines(options)
|
||||
|
||||
if cpConf.IsEnable {
|
||||
return bucket.copyFileWithCp(srcBucketName, srcObjectKey, destBucketName, destObjectKey,
|
||||
partSize, options, cpConf.FilePath, routines)
|
||||
}
|
||||
|
||||
return bucket.copyFile(srcBucketName, srcObjectKey, destBucketName, destObjectKey,
|
||||
partSize, options, routines)
|
||||
}
|
||||
|
||||
// ----- 并发无断点的下载 -----
|
||||
|
||||
// 工作协程参数
|
||||
type copyWorkerArg struct {
|
||||
bucket *Bucket
|
||||
imur InitiateMultipartUploadResult
|
||||
srcBucketName string
|
||||
srcObjectKey string
|
||||
options []Option
|
||||
hook copyPartHook
|
||||
}
|
||||
|
||||
// Hook用于测试
|
||||
type copyPartHook func(part copyPart) error
|
||||
|
||||
var copyPartHooker copyPartHook = defaultCopyPartHook
|
||||
|
||||
func defaultCopyPartHook(part copyPart) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 工作协程
|
||||
func copyWorker(id int, arg copyWorkerArg, jobs <-chan copyPart, results chan<- UploadPart, failed chan<- error, die <-chan bool) {
|
||||
for chunk := range jobs {
|
||||
if err := arg.hook(chunk); err != nil {
|
||||
failed <- err
|
||||
break
|
||||
}
|
||||
chunkSize := chunk.End - chunk.Start + 1
|
||||
part, err := arg.bucket.UploadPartCopy(arg.imur, arg.srcBucketName, arg.srcObjectKey,
|
||||
chunk.Start, chunkSize, chunk.Number, arg.options...)
|
||||
if err != nil {
|
||||
failed <- err
|
||||
break
|
||||
}
|
||||
select {
|
||||
case <-die:
|
||||
return
|
||||
default:
|
||||
}
|
||||
results <- part
|
||||
}
|
||||
}
|
||||
|
||||
// 调度协程
|
||||
func copyScheduler(jobs chan copyPart, parts []copyPart) {
|
||||
for _, part := range parts {
|
||||
jobs <- part
|
||||
}
|
||||
close(jobs)
|
||||
}
|
||||
|
||||
// 分片
|
||||
type copyPart struct {
|
||||
Number int // 片序号[1, 10000]
|
||||
Start int64 // 片起始位置
|
||||
End int64 // 片结束位置
|
||||
}
|
||||
|
||||
// 文件分片
|
||||
func getCopyParts(bucket *Bucket, objectKey string, partSize int64) ([]copyPart, error) {
|
||||
meta, err := bucket.GetObjectDetailedMeta(objectKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
parts := []copyPart{}
|
||||
objectSize, err := strconv.ParseInt(meta.Get(HTTPHeaderContentLength), 10, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
part := copyPart{}
|
||||
i := 0
|
||||
for offset := int64(0); offset < objectSize; offset += partSize {
|
||||
part.Number = i + 1
|
||||
part.Start = offset
|
||||
part.End = GetPartEnd(offset, objectSize, partSize)
|
||||
parts = append(parts, part)
|
||||
i++
|
||||
}
|
||||
return parts, nil
|
||||
}
|
||||
|
||||
// 获取源文件大小
|
||||
func getSrcObjectBytes(parts []copyPart) int64 {
|
||||
var ob int64
|
||||
for _, part := range parts {
|
||||
ob += (part.End - part.Start + 1)
|
||||
}
|
||||
return ob
|
||||
}
|
||||
|
||||
// 并发无断点续传的下载
|
||||
func (bucket Bucket) copyFile(srcBucketName, srcObjectKey, destBucketName, destObjectKey string,
|
||||
partSize int64, options []Option, routines int) error {
|
||||
descBucket, err := bucket.Client.Bucket(destBucketName)
|
||||
srcBucket, err := bucket.Client.Bucket(srcBucketName)
|
||||
listener := getProgressListener(options)
|
||||
|
||||
// 分割文件
|
||||
parts, err := getCopyParts(srcBucket, srcObjectKey, partSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 初始化上传任务
|
||||
imur, err := descBucket.InitiateMultipartUpload(destObjectKey, options...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
jobs := make(chan copyPart, len(parts))
|
||||
results := make(chan UploadPart, len(parts))
|
||||
failed := make(chan error)
|
||||
die := make(chan bool)
|
||||
|
||||
var completedBytes int64
|
||||
totalBytes := getSrcObjectBytes(parts)
|
||||
event := newProgressEvent(TransferStartedEvent, 0, totalBytes)
|
||||
publishProgress(listener, event)
|
||||
|
||||
// 启动工作协程
|
||||
arg := copyWorkerArg{descBucket, imur, srcBucketName, srcObjectKey, options, copyPartHooker}
|
||||
for w := 1; w <= routines; w++ {
|
||||
go copyWorker(w, arg, jobs, results, failed, die)
|
||||
}
|
||||
|
||||
// 并发上传分片
|
||||
go copyScheduler(jobs, parts)
|
||||
|
||||
// 等待分片下载完成
|
||||
completed := 0
|
||||
ups := make([]UploadPart, len(parts))
|
||||
for completed < len(parts) {
|
||||
select {
|
||||
case part := <-results:
|
||||
completed++
|
||||
ups[part.PartNumber-1] = part
|
||||
completedBytes += (parts[part.PartNumber-1].End - parts[part.PartNumber-1].Start + 1)
|
||||
event = newProgressEvent(TransferDataEvent, completedBytes, totalBytes)
|
||||
publishProgress(listener, event)
|
||||
case err := <-failed:
|
||||
close(die)
|
||||
descBucket.AbortMultipartUpload(imur)
|
||||
event = newProgressEvent(TransferFailedEvent, completedBytes, totalBytes)
|
||||
publishProgress(listener, event)
|
||||
return err
|
||||
}
|
||||
|
||||
if completed >= len(parts) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
event = newProgressEvent(TransferCompletedEvent, completedBytes, totalBytes)
|
||||
publishProgress(listener, event)
|
||||
|
||||
// 提交任务
|
||||
_, err = descBucket.CompleteMultipartUpload(imur, ups)
|
||||
if err != nil {
|
||||
bucket.AbortMultipartUpload(imur)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ----- 并发有断点的下载 -----
|
||||
|
||||
const copyCpMagic = "84F1F18C-FF1D-403B-A1D8-9DEB5F65910A"
|
||||
|
||||
type copyCheckpoint struct {
|
||||
Magic string // magic
|
||||
MD5 string // cp内容的MD5
|
||||
SrcBucketName string // 源Bucket
|
||||
SrcObjectKey string // 源Object
|
||||
DestBucketName string // 目标Bucket
|
||||
DestObjectKey string // 目标Bucket
|
||||
CopyID string // copy id
|
||||
ObjStat objectStat // 文件状态
|
||||
Parts []copyPart // 全部分片
|
||||
CopyParts []UploadPart // 分片上传成功后的返回值
|
||||
PartStat []bool // 分片下载是否完成
|
||||
}
|
||||
|
||||
// CP数据是否有效,CP有效且Object没有更新时有效
|
||||
func (cp copyCheckpoint) isValid(bucket *Bucket, objectKey string) (bool, error) {
|
||||
// 比较CP的Magic及MD5
|
||||
cpb := cp
|
||||
cpb.MD5 = ""
|
||||
js, _ := json.Marshal(cpb)
|
||||
sum := md5.Sum(js)
|
||||
b64 := base64.StdEncoding.EncodeToString(sum[:])
|
||||
|
||||
if cp.Magic != downloadCpMagic || b64 != cp.MD5 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// 确认object没有更新
|
||||
meta, err := bucket.GetObjectDetailedMeta(objectKey)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
objectSize, err := strconv.ParseInt(meta.Get(HTTPHeaderContentLength), 10, 0)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// 比较Object的大小/最后修改时间/etag
|
||||
if cp.ObjStat.Size != objectSize ||
|
||||
cp.ObjStat.LastModified != meta.Get(HTTPHeaderLastModified) ||
|
||||
cp.ObjStat.Etag != meta.Get(HTTPHeaderEtag) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// 从文件中load
|
||||
func (cp *copyCheckpoint) load(filePath string) error {
|
||||
contents, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(contents, cp)
|
||||
return err
|
||||
}
|
||||
|
||||
// 更新分片状态
|
||||
func (cp *copyCheckpoint) update(part UploadPart) {
|
||||
cp.CopyParts[part.PartNumber-1] = part
|
||||
cp.PartStat[part.PartNumber-1] = true
|
||||
}
|
||||
|
||||
// dump到文件
|
||||
func (cp *copyCheckpoint) dump(filePath string) error {
|
||||
bcp := *cp
|
||||
|
||||
// 计算MD5
|
||||
bcp.MD5 = ""
|
||||
js, err := json.Marshal(bcp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sum := md5.Sum(js)
|
||||
b64 := base64.StdEncoding.EncodeToString(sum[:])
|
||||
bcp.MD5 = b64
|
||||
|
||||
// 序列化
|
||||
js, err = json.Marshal(bcp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// dump
|
||||
return ioutil.WriteFile(filePath, js, FilePermMode)
|
||||
}
|
||||
|
||||
// 未完成的分片
|
||||
func (cp copyCheckpoint) todoParts() []copyPart {
|
||||
dps := []copyPart{}
|
||||
for i, ps := range cp.PartStat {
|
||||
if !ps {
|
||||
dps = append(dps, cp.Parts[i])
|
||||
}
|
||||
}
|
||||
return dps
|
||||
}
|
||||
|
||||
// 完成的字节数
|
||||
func (cp copyCheckpoint) getCompletedBytes() int64 {
|
||||
var completedBytes int64
|
||||
for i, part := range cp.Parts {
|
||||
if cp.PartStat[i] {
|
||||
completedBytes += (part.End - part.Start + 1)
|
||||
}
|
||||
}
|
||||
return completedBytes
|
||||
}
|
||||
|
||||
// 初始化下载任务
|
||||
func (cp *copyCheckpoint) prepare(srcBucket *Bucket, srcObjectKey string, destBucket *Bucket, destObjectKey string,
|
||||
partSize int64, options []Option) error {
|
||||
// cp
|
||||
cp.Magic = copyCpMagic
|
||||
cp.SrcBucketName = srcBucket.BucketName
|
||||
cp.SrcObjectKey = srcObjectKey
|
||||
cp.DestBucketName = destBucket.BucketName
|
||||
cp.DestObjectKey = destObjectKey
|
||||
|
||||
// object
|
||||
meta, err := srcBucket.GetObjectDetailedMeta(srcObjectKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
objectSize, err := strconv.ParseInt(meta.Get(HTTPHeaderContentLength), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cp.ObjStat.Size = objectSize
|
||||
cp.ObjStat.LastModified = meta.Get(HTTPHeaderLastModified)
|
||||
cp.ObjStat.Etag = meta.Get(HTTPHeaderEtag)
|
||||
|
||||
// parts
|
||||
cp.Parts, err = getCopyParts(srcBucket, srcObjectKey, partSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cp.PartStat = make([]bool, len(cp.Parts))
|
||||
for i := range cp.PartStat {
|
||||
cp.PartStat[i] = false
|
||||
}
|
||||
cp.CopyParts = make([]UploadPart, len(cp.Parts))
|
||||
|
||||
// init copy
|
||||
imur, err := destBucket.InitiateMultipartUpload(destObjectKey, options...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cp.CopyID = imur.UploadID
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cp *copyCheckpoint) complete(bucket *Bucket, parts []UploadPart, cpFilePath string) error {
|
||||
imur := InitiateMultipartUploadResult{Bucket: cp.DestBucketName,
|
||||
Key: cp.DestObjectKey, UploadID: cp.CopyID}
|
||||
_, err := bucket.CompleteMultipartUpload(imur, parts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
os.Remove(cpFilePath)
|
||||
return err
|
||||
}
|
||||
|
||||
// 并发带断点的下载
|
||||
func (bucket Bucket) copyFileWithCp(srcBucketName, srcObjectKey, destBucketName, destObjectKey string,
|
||||
partSize int64, options []Option, cpFilePath string, routines int) error {
|
||||
descBucket, err := bucket.Client.Bucket(destBucketName)
|
||||
srcBucket, err := bucket.Client.Bucket(srcBucketName)
|
||||
listener := getProgressListener(options)
|
||||
|
||||
// LOAD CP数据
|
||||
ccp := copyCheckpoint{}
|
||||
err = ccp.load(cpFilePath)
|
||||
if err != nil {
|
||||
os.Remove(cpFilePath)
|
||||
}
|
||||
|
||||
// LOAD出错或数据无效重新初始化下载
|
||||
valid, err := ccp.isValid(srcBucket, srcObjectKey)
|
||||
if err != nil || !valid {
|
||||
if err = ccp.prepare(srcBucket, srcObjectKey, descBucket, destObjectKey, partSize, options); err != nil {
|
||||
return err
|
||||
}
|
||||
os.Remove(cpFilePath)
|
||||
}
|
||||
|
||||
// 未完成的分片
|
||||
parts := ccp.todoParts()
|
||||
imur := InitiateMultipartUploadResult{
|
||||
Bucket: destBucketName,
|
||||
Key: destObjectKey,
|
||||
UploadID: ccp.CopyID}
|
||||
|
||||
jobs := make(chan copyPart, len(parts))
|
||||
results := make(chan UploadPart, len(parts))
|
||||
failed := make(chan error)
|
||||
die := make(chan bool)
|
||||
|
||||
completedBytes := ccp.getCompletedBytes()
|
||||
event := newProgressEvent(TransferStartedEvent, completedBytes, ccp.ObjStat.Size)
|
||||
publishProgress(listener, event)
|
||||
|
||||
// 启动工作协程
|
||||
arg := copyWorkerArg{descBucket, imur, srcBucketName, srcObjectKey, options, copyPartHooker}
|
||||
for w := 1; w <= routines; w++ {
|
||||
go copyWorker(w, arg, jobs, results, failed, die)
|
||||
}
|
||||
|
||||
// 并发下载分片
|
||||
go copyScheduler(jobs, parts)
|
||||
|
||||
// 等待分片下载完成
|
||||
completed := 0
|
||||
for completed < len(parts) {
|
||||
select {
|
||||
case part := <-results:
|
||||
completed++
|
||||
ccp.update(part)
|
||||
ccp.dump(cpFilePath)
|
||||
completedBytes += (parts[part.PartNumber-1].End - parts[part.PartNumber-1].Start + 1)
|
||||
event = newProgressEvent(TransferDataEvent, completedBytes, ccp.ObjStat.Size)
|
||||
publishProgress(listener, event)
|
||||
case err := <-failed:
|
||||
close(die)
|
||||
event = newProgressEvent(TransferFailedEvent, completedBytes, ccp.ObjStat.Size)
|
||||
publishProgress(listener, event)
|
||||
return err
|
||||
}
|
||||
|
||||
if completed >= len(parts) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
event = newProgressEvent(TransferCompletedEvent, completedBytes, ccp.ObjStat.Size)
|
||||
publishProgress(listener, event)
|
||||
|
||||
return ccp.complete(descBucket, ccp.CopyParts, cpFilePath)
|
||||
}
|
|
@ -0,0 +1,281 @@
|
|||
package oss
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
//
|
||||
// InitiateMultipartUpload 初始化分片上传任务。
|
||||
//
|
||||
// objectKey Object名称。
|
||||
// options 上传时可以指定Object的属性,可选属性有CacheControl、ContentDisposition、ContentEncoding、Expires、
|
||||
// ServerSideEncryption、Meta,具体含义请参考
|
||||
// https://help.aliyun.com/document_detail/oss/api-reference/multipart-upload/InitiateMultipartUpload.html
|
||||
//
|
||||
// InitiateMultipartUploadResult 初始化后操作成功的返回值,用于后面的UploadPartFromFile、UploadPartCopy等操作。error为nil时有效。
|
||||
// error 操作成功error为nil,非nil为错误信息。
|
||||
//
|
||||
func (bucket Bucket) InitiateMultipartUpload(objectKey string, options ...Option) (InitiateMultipartUploadResult, error) {
|
||||
var imur InitiateMultipartUploadResult
|
||||
opts := addContentType(options, objectKey)
|
||||
resp, err := bucket.do("POST", objectKey, "uploads", "uploads", opts, nil, nil)
|
||||
if err != nil {
|
||||
return imur, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = xmlUnmarshal(resp.Body, &imur)
|
||||
return imur, err
|
||||
}
|
||||
|
||||
//
|
||||
// UploadPart 上传分片。
|
||||
//
|
||||
// 初始化一个Multipart Upload之后,可以根据指定的Object名和Upload ID来分片(Part)上传数据。
|
||||
// 每一个上传的Part都有一个标识它的号码(part number,范围是1~10000)。对于同一个Upload ID,
|
||||
// 该号码不但唯一标识这一片数据,也标识了这片数据在整个文件内的相对位置。如果您用同一个part号码,上传了新的数据,
|
||||
// 那么OSS上已有的这个号码的Part数据将被覆盖。除了最后一片Part以外,其他的part最小为100KB;
|
||||
// 最后一片Part没有大小限制。
|
||||
//
|
||||
// imur InitiateMultipartUpload成功后的返回值。
|
||||
// reader io.Reader 需要分片上传的reader。
|
||||
// size 本次上传片Part的大小。
|
||||
// partNumber 本次上传片(Part)的编号,范围是1~10000。如果超出范围,OSS将返回InvalidArgument错误。
|
||||
//
|
||||
// UploadPart 上传成功的返回值,两个成员PartNumber、ETag。PartNumber片编号,即传入参数partNumber;
|
||||
// ETag及上传数据的MD5。error为nil时有效。
|
||||
// error 操作成功error为nil,非nil为错误信息。
|
||||
//
|
||||
func (bucket Bucket) UploadPart(imur InitiateMultipartUploadResult, reader io.Reader,
|
||||
partSize int64, partNumber int, options ...Option) (UploadPart, error) {
|
||||
request := &UploadPartRequest{
|
||||
InitResult: &imur,
|
||||
Reader: reader,
|
||||
PartSize: partSize,
|
||||
PartNumber: partNumber,
|
||||
}
|
||||
|
||||
result, err := bucket.DoUploadPart(request, options)
|
||||
|
||||
return result.Part, err
|
||||
}
|
||||
|
||||
//
|
||||
// UploadPartFromFile 上传分片。
|
||||
//
|
||||
// imur InitiateMultipartUpload成功后的返回值。
|
||||
// filePath 需要分片上传的本地文件。
|
||||
// startPosition 本次上传文件片的起始位置。
|
||||
// partSize 本次上传文件片的大小。
|
||||
// partNumber 本次上传文件片的编号,范围是1~10000。
|
||||
//
|
||||
// UploadPart 上传成功的返回值,两个成员PartNumber、ETag。PartNumber片编号,传入参数partNumber;
|
||||
// ETag上传数据的MD5。error为nil时有效。
|
||||
// error 操作成功error为nil,非nil为错误信息。
|
||||
//
|
||||
func (bucket Bucket) UploadPartFromFile(imur InitiateMultipartUploadResult, filePath string,
|
||||
startPosition, partSize int64, partNumber int, options ...Option) (UploadPart, error) {
|
||||
var part = UploadPart{}
|
||||
fd, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return part, err
|
||||
}
|
||||
defer fd.Close()
|
||||
fd.Seek(startPosition, os.SEEK_SET)
|
||||
|
||||
request := &UploadPartRequest{
|
||||
InitResult: &imur,
|
||||
Reader: fd,
|
||||
PartSize: partSize,
|
||||
PartNumber: partNumber,
|
||||
}
|
||||
|
||||
result, err := bucket.DoUploadPart(request, options)
|
||||
|
||||
return result.Part, err
|
||||
}
|
||||
|
||||
//
|
||||
// DoUploadPart 上传分片。
|
||||
//
|
||||
// request 上传分片请求。
|
||||
//
|
||||
// UploadPartResult 上传分片请求返回值。
|
||||
// error 操作无错误为nil,非nil为错误信息。
|
||||
//
|
||||
func (bucket Bucket) DoUploadPart(request *UploadPartRequest, options []Option) (*UploadPartResult, error) {
|
||||
listener := getProgressListener(options)
|
||||
params := "partNumber=" + strconv.Itoa(request.PartNumber) + "&uploadId=" + request.InitResult.UploadID
|
||||
opts := []Option{ContentLength(request.PartSize)}
|
||||
resp, err := bucket.do("PUT", request.InitResult.Key, params, params, opts,
|
||||
&io.LimitedReader{R: request.Reader, N: request.PartSize}, listener)
|
||||
if err != nil {
|
||||
return &UploadPartResult{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
part := UploadPart{
|
||||
ETag: resp.Headers.Get(HTTPHeaderEtag),
|
||||
PartNumber: request.PartNumber,
|
||||
}
|
||||
|
||||
if bucket.getConfig().IsEnableCRC {
|
||||
err = checkCRC(resp, "DoUploadPart")
|
||||
if err != nil {
|
||||
return &UploadPartResult{part}, err
|
||||
}
|
||||
}
|
||||
|
||||
return &UploadPartResult{part}, nil
|
||||
}
|
||||
|
||||
//
|
||||
// UploadPartCopy 拷贝分片。
|
||||
//
|
||||
// imur InitiateMultipartUpload成功后的返回值。
|
||||
// copySrc 源Object名称。
|
||||
// startPosition 本次拷贝片(Part)在源Object的起始位置。
|
||||
// partSize 本次拷贝片的大小。
|
||||
// partNumber 本次拷贝片的编号,范围是1~10000。如果超出范围,OSS将返回InvalidArgument错误。
|
||||
// options copy时源Object的限制条件,满足限制条件时copy,不满足时返回错误。可选条件有CopySourceIfMatch、
|
||||
// CopySourceIfNoneMatch、CopySourceIfModifiedSince CopySourceIfUnmodifiedSince,具体含义请参看
|
||||
// https://help.aliyun.com/document_detail/oss/api-reference/multipart-upload/UploadPartCopy.html
|
||||
//
|
||||
// UploadPart 上传成功的返回值,两个成员PartNumber、ETag。PartNumber片(Part)编号,即传入参数partNumber;
|
||||
// ETag及上传数据的MD5。error为nil时有效。
|
||||
// error 操作成功error为nil,非nil为错误信息。
|
||||
//
|
||||
func (bucket Bucket) UploadPartCopy(imur InitiateMultipartUploadResult, srcBucketName, srcObjectKey string,
|
||||
startPosition, partSize int64, partNumber int, options ...Option) (UploadPart, error) {
|
||||
var out UploadPartCopyResult
|
||||
var part UploadPart
|
||||
|
||||
opts := []Option{CopySource(srcBucketName, srcObjectKey),
|
||||
CopySourceRange(startPosition, partSize)}
|
||||
opts = append(opts, options...)
|
||||
params := "partNumber=" + strconv.Itoa(partNumber) + "&uploadId=" + imur.UploadID
|
||||
resp, err := bucket.do("PUT", imur.Key, params, params, opts, nil, nil)
|
||||
if err != nil {
|
||||
return part, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = xmlUnmarshal(resp.Body, &out)
|
||||
if err != nil {
|
||||
return part, err
|
||||
}
|
||||
part.ETag = out.ETag
|
||||
part.PartNumber = partNumber
|
||||
|
||||
return part, nil
|
||||
}
|
||||
|
||||
//
|
||||
// CompleteMultipartUpload 提交分片上传任务。
|
||||
//
|
||||
// imur InitiateMultipartUpload的返回值。
|
||||
// parts UploadPart/UploadPartFromFile/UploadPartCopy返回值组成的数组。
|
||||
//
|
||||
// CompleteMultipartUploadResponse 操作成功后的返回值。error为nil时有效。
|
||||
// error 操作成功error为nil,非nil为错误信息。
|
||||
//
|
||||
func (bucket Bucket) CompleteMultipartUpload(imur InitiateMultipartUploadResult,
|
||||
parts []UploadPart) (CompleteMultipartUploadResult, error) {
|
||||
var out CompleteMultipartUploadResult
|
||||
|
||||
sort.Sort(uploadParts(parts))
|
||||
cxml := completeMultipartUploadXML{}
|
||||
cxml.Part = parts
|
||||
bs, err := xml.Marshal(cxml)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
buffer := new(bytes.Buffer)
|
||||
buffer.Write(bs)
|
||||
|
||||
params := "uploadId=" + imur.UploadID
|
||||
resp, err := bucket.do("POST", imur.Key, params, params, nil, buffer, nil)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = xmlUnmarshal(resp.Body, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
//
|
||||
// AbortMultipartUpload 取消分片上传任务。
|
||||
//
|
||||
// imur InitiateMultipartUpload的返回值。
|
||||
//
|
||||
// error 操作成功error为nil,非nil为错误信息。
|
||||
//
|
||||
func (bucket Bucket) AbortMultipartUpload(imur InitiateMultipartUploadResult) error {
|
||||
params := "uploadId=" + imur.UploadID
|
||||
resp, err := bucket.do("DELETE", imur.Key, params, params, nil, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return checkRespCode(resp.StatusCode, []int{http.StatusNoContent})
|
||||
}
|
||||
|
||||
//
|
||||
// ListUploadedParts 列出指定上传任务已经上传的分片。
|
||||
//
|
||||
// imur InitiateMultipartUpload的返回值。
|
||||
//
|
||||
// ListUploadedPartsResponse 操作成功后的返回值,成员UploadedParts已经上传/拷贝的片。error为nil时该返回值有效。
|
||||
// error 操作成功error为nil,非nil为错误信息。
|
||||
//
|
||||
func (bucket Bucket) ListUploadedParts(imur InitiateMultipartUploadResult) (ListUploadedPartsResult, error) {
|
||||
var out ListUploadedPartsResult
|
||||
params := "uploadId=" + imur.UploadID
|
||||
resp, err := bucket.do("GET", imur.Key, params, params, nil, nil, nil)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = xmlUnmarshal(resp.Body, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
//
|
||||
// ListMultipartUploads 列出所有未上传完整的multipart任务列表。
|
||||
//
|
||||
// options ListObject的筛选行为。Prefix返回object的前缀,KeyMarker返回object的起始位置,MaxUploads最大数目默认1000,
|
||||
// Delimiter用于对Object名字进行分组的字符,所有名字包含指定的前缀且第一次出现delimiter字符之间的object。
|
||||
//
|
||||
// ListMultipartUploadResponse 操作成功后的返回值,error为nil时该返回值有效。
|
||||
// error 操作成功error为nil,非nil为错误信息。
|
||||
//
|
||||
func (bucket Bucket) ListMultipartUploads(options ...Option) (ListMultipartUploadResult, error) {
|
||||
var out ListMultipartUploadResult
|
||||
|
||||
options = append(options, EncodingType("url"))
|
||||
params, err := handleParams(options)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
|
||||
resp, err := bucket.do("GET", "", "uploads&"+params, "uploads", nil, nil, nil)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = xmlUnmarshal(resp.Body, &out)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
err = decodeListMultipartUploadResult(&out)
|
||||
return out, err
|
||||
}
|
|
@ -0,0 +1,351 @@
|
|||
package oss
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type optionType string
|
||||
|
||||
const (
|
||||
optionParam optionType = "HTTPParameter" // URL参数
|
||||
optionHTTP optionType = "HTTPHeader" // HTTP头
|
||||
optionArg optionType = "FuncArgument" // 函数参数
|
||||
)
|
||||
|
||||
const (
|
||||
deleteObjectsQuiet = "delete-objects-quiet"
|
||||
routineNum = "x-routine-num"
|
||||
checkpointConfig = "x-cp-config"
|
||||
initCRC64 = "init-crc64"
|
||||
progressListener = "x-progress-listener"
|
||||
)
|
||||
|
||||
type (
|
||||
optionValue struct {
|
||||
Value interface{}
|
||||
Type optionType
|
||||
}
|
||||
|
||||
// Option http option
|
||||
Option func(map[string]optionValue) error
|
||||
)
|
||||
|
||||
// ACL is an option to set X-Oss-Acl header
|
||||
func ACL(acl ACLType) Option {
|
||||
return setHeader(HTTPHeaderOssACL, string(acl))
|
||||
}
|
||||
|
||||
// ContentType is an option to set Content-Type header
|
||||
func ContentType(value string) Option {
|
||||
return setHeader(HTTPHeaderContentType, value)
|
||||
}
|
||||
|
||||
// ContentLength is an option to set Content-Length header
|
||||
func ContentLength(length int64) Option {
|
||||
return setHeader(HTTPHeaderContentLength, strconv.FormatInt(length, 10))
|
||||
}
|
||||
|
||||
// CacheControl is an option to set Cache-Control header
|
||||
func CacheControl(value string) Option {
|
||||
return setHeader(HTTPHeaderCacheControl, value)
|
||||
}
|
||||
|
||||
// ContentDisposition is an option to set Content-Disposition header
|
||||
func ContentDisposition(value string) Option {
|
||||
return setHeader(HTTPHeaderContentDisposition, value)
|
||||
}
|
||||
|
||||
// ContentEncoding is an option to set Content-Encoding header
|
||||
func ContentEncoding(value string) Option {
|
||||
return setHeader(HTTPHeaderContentEncoding, value)
|
||||
}
|
||||
|
||||
// ContentMD5 is an option to set Content-MD5 header
|
||||
func ContentMD5(value string) Option {
|
||||
return setHeader(HTTPHeaderContentMD5, value)
|
||||
}
|
||||
|
||||
// Expires is an option to set Expires header
|
||||
func Expires(t time.Time) Option {
|
||||
return setHeader(HTTPHeaderExpires, t.Format(http.TimeFormat))
|
||||
}
|
||||
|
||||
// Meta is an option to set Meta header
|
||||
func Meta(key, value string) Option {
|
||||
return setHeader(HTTPHeaderOssMetaPrefix+key, value)
|
||||
}
|
||||
|
||||
// Range is an option to set Range header, [start, end]
|
||||
func Range(start, end int64) Option {
|
||||
return setHeader(HTTPHeaderRange, fmt.Sprintf("bytes=%d-%d", start, end))
|
||||
}
|
||||
|
||||
// AcceptEncoding is an option to set Accept-Encoding header
|
||||
func AcceptEncoding(value string) Option {
|
||||
return setHeader(HTTPHeaderAcceptEncoding, value)
|
||||
}
|
||||
|
||||
// IfModifiedSince is an option to set If-Modified-Since header
|
||||
func IfModifiedSince(t time.Time) Option {
|
||||
return setHeader(HTTPHeaderIfModifiedSince, t.Format(http.TimeFormat))
|
||||
}
|
||||
|
||||
// IfUnmodifiedSince is an option to set If-Unmodified-Since header
|
||||
func IfUnmodifiedSince(t time.Time) Option {
|
||||
return setHeader(HTTPHeaderIfUnmodifiedSince, t.Format(http.TimeFormat))
|
||||
}
|
||||
|
||||
// IfMatch is an option to set If-Match header
|
||||
func IfMatch(value string) Option {
|
||||
return setHeader(HTTPHeaderIfMatch, value)
|
||||
}
|
||||
|
||||
// IfNoneMatch is an option to set IfNoneMatch header
|
||||
func IfNoneMatch(value string) Option {
|
||||
return setHeader(HTTPHeaderIfNoneMatch, value)
|
||||
}
|
||||
|
||||
// CopySource is an option to set X-Oss-Copy-Source header
|
||||
func CopySource(sourceBucket, sourceObject string) Option {
|
||||
return setHeader(HTTPHeaderOssCopySource, "/"+sourceBucket+"/"+sourceObject)
|
||||
}
|
||||
|
||||
// CopySourceRange is an option to set X-Oss-Copy-Source header
|
||||
func CopySourceRange(startPosition, partSize int64) Option {
|
||||
val := "bytes=" + strconv.FormatInt(startPosition, 10) + "-" +
|
||||
strconv.FormatInt((startPosition+partSize-1), 10)
|
||||
return setHeader(HTTPHeaderOssCopySourceRange, val)
|
||||
}
|
||||
|
||||
// CopySourceIfMatch is an option to set X-Oss-Copy-Source-If-Match header
|
||||
func CopySourceIfMatch(value string) Option {
|
||||
return setHeader(HTTPHeaderOssCopySourceIfMatch, value)
|
||||
}
|
||||
|
||||
// CopySourceIfNoneMatch is an option to set X-Oss-Copy-Source-If-None-Match header
|
||||
func CopySourceIfNoneMatch(value string) Option {
|
||||
return setHeader(HTTPHeaderOssCopySourceIfNoneMatch, value)
|
||||
}
|
||||
|
||||
// CopySourceIfModifiedSince is an option to set X-Oss-CopySource-If-Modified-Since header
|
||||
func CopySourceIfModifiedSince(t time.Time) Option {
|
||||
return setHeader(HTTPHeaderOssCopySourceIfModifiedSince, t.Format(http.TimeFormat))
|
||||
}
|
||||
|
||||
// CopySourceIfUnmodifiedSince is an option to set X-Oss-Copy-Source-If-Unmodified-Since header
|
||||
func CopySourceIfUnmodifiedSince(t time.Time) Option {
|
||||
return setHeader(HTTPHeaderOssCopySourceIfUnmodifiedSince, t.Format(http.TimeFormat))
|
||||
}
|
||||
|
||||
// MetadataDirective is an option to set X-Oss-Metadata-Directive header
|
||||
func MetadataDirective(directive MetadataDirectiveType) Option {
|
||||
return setHeader(HTTPHeaderOssMetadataDirective, string(directive))
|
||||
}
|
||||
|
||||
// ServerSideEncryption is an option to set X-Oss-Server-Side-Encryption header
|
||||
func ServerSideEncryption(value string) Option {
|
||||
return setHeader(HTTPHeaderOssServerSideEncryption, value)
|
||||
}
|
||||
|
||||
// ObjectACL is an option to set X-Oss-Object-Acl header
|
||||
func ObjectACL(acl ACLType) Option {
|
||||
return setHeader(HTTPHeaderOssObjectACL, string(acl))
|
||||
}
|
||||
|
||||
// Origin is an option to set Origin header
|
||||
func Origin(value string) Option {
|
||||
return setHeader(HTTPHeaderOrigin, value)
|
||||
}
|
||||
|
||||
// Delimiter is an option to set delimiler parameter
|
||||
func Delimiter(value string) Option {
|
||||
return addParam("delimiter", value)
|
||||
}
|
||||
|
||||
// Marker is an option to set marker parameter
|
||||
func Marker(value string) Option {
|
||||
return addParam("marker", value)
|
||||
}
|
||||
|
||||
// MaxKeys is an option to set maxkeys parameter
|
||||
func MaxKeys(value int) Option {
|
||||
return addParam("max-keys", strconv.Itoa(value))
|
||||
}
|
||||
|
||||
// Prefix is an option to set prefix parameter
|
||||
func Prefix(value string) Option {
|
||||
return addParam("prefix", value)
|
||||
}
|
||||
|
||||
// EncodingType is an option to set encoding-type parameter
|
||||
func EncodingType(value string) Option {
|
||||
return addParam("encoding-type", value)
|
||||
}
|
||||
|
||||
// MaxUploads is an option to set max-uploads parameter
|
||||
func MaxUploads(value int) Option {
|
||||
return addParam("max-uploads", strconv.Itoa(value))
|
||||
}
|
||||
|
||||
// KeyMarker is an option to set key-marker parameter
|
||||
func KeyMarker(value string) Option {
|
||||
return addParam("key-marker", value)
|
||||
}
|
||||
|
||||
// UploadIDMarker is an option to set upload-id-marker parameter
|
||||
func UploadIDMarker(value string) Option {
|
||||
return addParam("upload-id-marker", value)
|
||||
}
|
||||
|
||||
// DeleteObjectsQuiet DeleteObjects详细(verbose)模式或简单(quiet)模式,默认详细模式。
|
||||
func DeleteObjectsQuiet(isQuiet bool) Option {
|
||||
return addArg(deleteObjectsQuiet, isQuiet)
|
||||
}
|
||||
|
||||
// 断点续传配置,包括是否启用、cp文件
|
||||
type cpConfig struct {
|
||||
IsEnable bool
|
||||
FilePath string
|
||||
}
|
||||
|
||||
// Checkpoint DownloadFile/UploadFile是否开启checkpoint及checkpoint文件路径
|
||||
func Checkpoint(isEnable bool, filePath string) Option {
|
||||
return addArg(checkpointConfig, &cpConfig{isEnable, filePath})
|
||||
}
|
||||
|
||||
// Routines DownloadFile/UploadFile并发数
|
||||
func Routines(n int) Option {
|
||||
return addArg(routineNum, n)
|
||||
}
|
||||
|
||||
// InitCRC AppendObject CRC的校验的初始值
|
||||
func InitCRC(initCRC uint64) Option {
|
||||
return addArg(initCRC64, initCRC)
|
||||
}
|
||||
|
||||
// Progress set progress listener
|
||||
func Progress(listener ProgressListener) Option {
|
||||
return addArg(progressListener, listener)
|
||||
}
|
||||
|
||||
func setHeader(key string, value interface{}) Option {
|
||||
return func(params map[string]optionValue) error {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
params[key] = optionValue{value, optionHTTP}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func addParam(key string, value interface{}) Option {
|
||||
return func(params map[string]optionValue) error {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
params[key] = optionValue{value, optionParam}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func addArg(key string, value interface{}) Option {
|
||||
return func(params map[string]optionValue) error {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
params[key] = optionValue{value, optionArg}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func handleOptions(headers map[string]string, options []Option) error {
|
||||
params := map[string]optionValue{}
|
||||
for _, option := range options {
|
||||
if option != nil {
|
||||
if err := option(params); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range params {
|
||||
if v.Type == optionHTTP {
|
||||
headers[k] = v.Value.(string)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleParams(options []Option) (string, error) {
|
||||
// option
|
||||
params := map[string]optionValue{}
|
||||
for _, option := range options {
|
||||
if option != nil {
|
||||
if err := option(params); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sort
|
||||
var buf bytes.Buffer
|
||||
keys := make([]string, 0, len(params))
|
||||
for k, v := range params {
|
||||
if v.Type == optionParam {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
// serialize
|
||||
for _, k := range keys {
|
||||
vs := params[k]
|
||||
prefix := url.QueryEscape(k) + "="
|
||||
|
||||
if buf.Len() > 0 {
|
||||
buf.WriteByte('&')
|
||||
}
|
||||
buf.WriteString(prefix)
|
||||
buf.WriteString(url.QueryEscape(vs.Value.(string)))
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func findOption(options []Option, param string, defaultVal interface{}) (interface{}, error) {
|
||||
params := map[string]optionValue{}
|
||||
for _, option := range options {
|
||||
if option != nil {
|
||||
if err := option(params); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := params[param]; ok {
|
||||
return val.Value, nil
|
||||
}
|
||||
return defaultVal, nil
|
||||
}
|
||||
|
||||
func isOptionSet(options []Option, option string) (bool, interface{}, error) {
|
||||
params := map[string]optionValue{}
|
||||
for _, option := range options {
|
||||
if option != nil {
|
||||
if err := option(params); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := params[option]; ok {
|
||||
return true, val.Value, nil
|
||||
}
|
||||
return false, nil, nil
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
package oss
|
||||
|
||||
import "io"
|
||||
|
||||
// ProgressEventType transfer progress event type
|
||||
type ProgressEventType int
|
||||
|
||||
const (
|
||||
// TransferStartedEvent transfer started, set TotalBytes
|
||||
TransferStartedEvent ProgressEventType = 1 + iota
|
||||
// TransferDataEvent transfer data, set ConsumedBytes anmd TotalBytes
|
||||
TransferDataEvent
|
||||
// TransferCompletedEvent transfer completed
|
||||
TransferCompletedEvent
|
||||
// TransferFailedEvent transfer encounters an error
|
||||
TransferFailedEvent
|
||||
)
|
||||
|
||||
// ProgressEvent progress event
|
||||
type ProgressEvent struct {
|
||||
ConsumedBytes int64
|
||||
TotalBytes int64
|
||||
EventType ProgressEventType
|
||||
}
|
||||
|
||||
// ProgressListener listen progress change
|
||||
type ProgressListener interface {
|
||||
ProgressChanged(event *ProgressEvent)
|
||||
}
|
||||
|
||||
// -------------------- private --------------------
|
||||
|
||||
func newProgressEvent(eventType ProgressEventType, consumed, total int64) *ProgressEvent {
|
||||
return &ProgressEvent{
|
||||
ConsumedBytes: consumed,
|
||||
TotalBytes: total,
|
||||
EventType: eventType}
|
||||
}
|
||||
|
||||
// publishProgress
|
||||
func publishProgress(listener ProgressListener, event *ProgressEvent) {
|
||||
if listener != nil && event != nil {
|
||||
listener.ProgressChanged(event)
|
||||
}
|
||||
}
|
||||
|
||||
type readerTracker struct {
|
||||
completedBytes int64
|
||||
}
|
||||
|
||||
type teeReader struct {
|
||||
reader io.Reader
|
||||
writer io.Writer
|
||||
listener ProgressListener
|
||||
consumedBytes int64
|
||||
totalBytes int64
|
||||
tracker *readerTracker
|
||||
}
|
||||
|
||||
// TeeReader returns a Reader that writes to w what it reads from r.
|
||||
// All reads from r performed through it are matched with
|
||||
// corresponding writes to w. There is no internal buffering -
|
||||
// the write must complete before the read completes.
|
||||
// Any error encountered while writing is reported as a read error.
|
||||
func TeeReader(reader io.Reader, writer io.Writer, totalBytes int64, listener ProgressListener, tracker *readerTracker) io.Reader {
|
||||
return &teeReader{
|
||||
reader: reader,
|
||||
writer: writer,
|
||||
listener: listener,
|
||||
consumedBytes: 0,
|
||||
totalBytes: totalBytes,
|
||||
tracker: tracker,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *teeReader) Read(p []byte) (n int, err error) {
|
||||
n, err = t.reader.Read(p)
|
||||
|
||||
// read encountered error
|
||||
if err != nil && err != io.EOF {
|
||||
event := newProgressEvent(TransferFailedEvent, t.consumedBytes, t.totalBytes)
|
||||
publishProgress(t.listener, event)
|
||||
}
|
||||
|
||||
if n > 0 {
|
||||
t.consumedBytes += int64(n)
|
||||
// crc
|
||||
if t.writer != nil {
|
||||
if n, err := t.writer.Write(p[:n]); err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
// progress
|
||||
if t.listener != nil {
|
||||
event := newProgressEvent(TransferDataEvent, t.consumedBytes, t.totalBytes)
|
||||
publishProgress(t.listener, event)
|
||||
}
|
||||
// track
|
||||
if t.tracker != nil {
|
||||
t.tracker.completedBytes = t.consumedBytes
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
|
@ -0,0 +1,442 @@
|
|||
package oss
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ListBucketsResult ListBuckets请求返回的结果
|
||||
type ListBucketsResult struct {
|
||||
XMLName xml.Name `xml:"ListAllMyBucketsResult"`
|
||||
Prefix string `xml:"Prefix"` // 本次查询结果的前缀
|
||||
Marker string `xml:"Marker"` // 标明查询的起点,未全部返回时有此节点
|
||||
MaxKeys int `xml:"MaxKeys"` // 返回结果的最大数目,未全部返回时有此节点
|
||||
IsTruncated bool `xml:"IsTruncated"` // 所有的结果是否已经全部返回
|
||||
NextMarker string `xml:"NextMarker"` // 表示下一次查询的起点
|
||||
Owner Owner `xml:"Owner"` // 拥有者信息
|
||||
Buckets []BucketProperties `xml:"Buckets>Bucket"` // Bucket列表
|
||||
}
|
||||
|
||||
// BucketProperties Bucket信息
|
||||
type BucketProperties struct {
|
||||
XMLName xml.Name `xml:"Bucket"`
|
||||
Name string `xml:"Name"` // Bucket名称
|
||||
Location string `xml:"Location"` // Bucket所在的数据中心
|
||||
CreationDate time.Time `xml:"CreationDate"` // Bucket创建时间
|
||||
}
|
||||
|
||||
// GetBucketACLResult GetBucketACL请求返回的结果
|
||||
type GetBucketACLResult struct {
|
||||
XMLName xml.Name `xml:"AccessControlPolicy"`
|
||||
ACL string `xml:"AccessControlList>Grant"` // Bucket权限
|
||||
Owner Owner `xml:"Owner"` // Bucket拥有者信息
|
||||
}
|
||||
|
||||
// LifecycleConfiguration Bucket的Lifecycle配置
|
||||
type LifecycleConfiguration struct {
|
||||
XMLName xml.Name `xml:"LifecycleConfiguration"`
|
||||
Rules []LifecycleRule `xml:"Rule"`
|
||||
}
|
||||
|
||||
// LifecycleRule Lifecycle规则
|
||||
type LifecycleRule struct {
|
||||
XMLName xml.Name `xml:"Rule"`
|
||||
ID string `xml:"ID"` // 规则唯一的ID
|
||||
Prefix string `xml:"Prefix"` // 规则所适用Object的前缀
|
||||
Status string `xml:"Status"` // 规则是否生效
|
||||
Expiration LifecycleExpiration `xml:"Expiration"` // 规则的过期属性
|
||||
}
|
||||
|
||||
// LifecycleExpiration 规则的过期属性
|
||||
type LifecycleExpiration struct {
|
||||
XMLName xml.Name `xml:"Expiration"`
|
||||
Days int `xml:"Days,omitempty"` // 最后修改时间过后多少天生效
|
||||
Date time.Time `xml:"Date,omitempty"` // 指定规则何时生效
|
||||
}
|
||||
|
||||
type lifecycleXML struct {
|
||||
XMLName xml.Name `xml:"LifecycleConfiguration"`
|
||||
Rules []lifecycleRule `xml:"Rule"`
|
||||
}
|
||||
|
||||
type lifecycleRule struct {
|
||||
XMLName xml.Name `xml:"Rule"`
|
||||
ID string `xml:"ID"`
|
||||
Prefix string `xml:"Prefix"`
|
||||
Status string `xml:"Status"`
|
||||
Expiration lifecycleExpiration `xml:"Expiration"`
|
||||
}
|
||||
|
||||
type lifecycleExpiration struct {
|
||||
XMLName xml.Name `xml:"Expiration"`
|
||||
Days int `xml:"Days,omitempty"`
|
||||
Date string `xml:"Date,omitempty"`
|
||||
}
|
||||
|
||||
const expirationDateFormat = "2006-01-02T15:04:05.000Z"
|
||||
|
||||
func convLifecycleRule(rules []LifecycleRule) []lifecycleRule {
|
||||
rs := []lifecycleRule{}
|
||||
for _, rule := range rules {
|
||||
r := lifecycleRule{}
|
||||
r.ID = rule.ID
|
||||
r.Prefix = rule.Prefix
|
||||
r.Status = rule.Status
|
||||
if rule.Expiration.Date.IsZero() {
|
||||
r.Expiration.Days = rule.Expiration.Days
|
||||
} else {
|
||||
r.Expiration.Date = rule.Expiration.Date.Format(expirationDateFormat)
|
||||
}
|
||||
rs = append(rs, r)
|
||||
}
|
||||
return rs
|
||||
}
|
||||
|
||||
// BuildLifecycleRuleByDays 指定过期天数构建Lifecycle规则
|
||||
func BuildLifecycleRuleByDays(id, prefix string, status bool, days int) LifecycleRule {
|
||||
var statusStr = "Enabled"
|
||||
if !status {
|
||||
statusStr = "Disabled"
|
||||
}
|
||||
return LifecycleRule{ID: id, Prefix: prefix, Status: statusStr,
|
||||
Expiration: LifecycleExpiration{Days: days}}
|
||||
}
|
||||
|
||||
// BuildLifecycleRuleByDate 指定过期时间构建Lifecycle规则
|
||||
func BuildLifecycleRuleByDate(id, prefix string, status bool, year, month, day int) LifecycleRule {
|
||||
var statusStr = "Enabled"
|
||||
if !status {
|
||||
statusStr = "Disabled"
|
||||
}
|
||||
date := time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC)
|
||||
return LifecycleRule{ID: id, Prefix: prefix, Status: statusStr,
|
||||
Expiration: LifecycleExpiration{Date: date}}
|
||||
}
|
||||
|
||||
// GetBucketLifecycleResult GetBucketLifecycle请求请求结果
|
||||
type GetBucketLifecycleResult LifecycleConfiguration
|
||||
|
||||
// RefererXML Referer配置
|
||||
type RefererXML struct {
|
||||
XMLName xml.Name `xml:"RefererConfiguration"`
|
||||
AllowEmptyReferer bool `xml:"AllowEmptyReferer"` // 是否允许referer字段为空的请求访问
|
||||
RefererList []string `xml:"RefererList>Referer"` // referer访问白名单
|
||||
}
|
||||
|
||||
// GetBucketRefererResult GetBucketReferer请教返回结果
|
||||
type GetBucketRefererResult RefererXML
|
||||
|
||||
// LoggingXML Logging配置
|
||||
type LoggingXML struct {
|
||||
XMLName xml.Name `xml:"BucketLoggingStatus"`
|
||||
LoggingEnabled LoggingEnabled `xml:"LoggingEnabled"` // 访问日志信息容器
|
||||
}
|
||||
|
||||
type loggingXMLEmpty struct {
|
||||
XMLName xml.Name `xml:"BucketLoggingStatus"`
|
||||
}
|
||||
|
||||
// LoggingEnabled 访问日志信息容器
|
||||
type LoggingEnabled struct {
|
||||
XMLName xml.Name `xml:"LoggingEnabled"`
|
||||
TargetBucket string `xml:"TargetBucket"` //存放访问日志的Bucket
|
||||
TargetPrefix string `xml:"TargetPrefix"` //保存访问日志的文件前缀
|
||||
}
|
||||
|
||||
// GetBucketLoggingResult GetBucketLogging请求返回结果
|
||||
type GetBucketLoggingResult LoggingXML
|
||||
|
||||
// WebsiteXML Website配置
|
||||
type WebsiteXML struct {
|
||||
XMLName xml.Name `xml:"WebsiteConfiguration"`
|
||||
IndexDocument IndexDocument `xml:"IndexDocument"` // 目录URL时添加的索引文件
|
||||
ErrorDocument ErrorDocument `xml:"ErrorDocument"` // 404错误时使用的文件
|
||||
}
|
||||
|
||||
// IndexDocument 目录URL时添加的索引文件
|
||||
type IndexDocument struct {
|
||||
XMLName xml.Name `xml:"IndexDocument"`
|
||||
Suffix string `xml:"Suffix"` // 目录URL时添加的索引文件名
|
||||
}
|
||||
|
||||
// ErrorDocument 404错误时使用的文件
|
||||
type ErrorDocument struct {
|
||||
XMLName xml.Name `xml:"ErrorDocument"`
|
||||
Key string `xml:"Key"` // 404错误时使用的文件名
|
||||
}
|
||||
|
||||
// GetBucketWebsiteResult GetBucketWebsite请求返回结果
|
||||
type GetBucketWebsiteResult WebsiteXML
|
||||
|
||||
// CORSXML CORS配置
|
||||
type CORSXML struct {
|
||||
XMLName xml.Name `xml:"CORSConfiguration"`
|
||||
CORSRules []CORSRule `xml:"CORSRule"` // CORS规则列表
|
||||
}
|
||||
|
||||
// CORSRule CORS规则
|
||||
type CORSRule struct {
|
||||
XMLName xml.Name `xml:"CORSRule"`
|
||||
AllowedOrigin []string `xml:"AllowedOrigin"` // 允许的来源,默认通配符"*"
|
||||
AllowedMethod []string `xml:"AllowedMethod"` // 允许的方法
|
||||
AllowedHeader []string `xml:"AllowedHeader"` // 允许的请求头
|
||||
ExposeHeader []string `xml:"ExposeHeader"` // 允许的响应头
|
||||
MaxAgeSeconds int `xml:"MaxAgeSeconds"` // 最大的缓存时间
|
||||
}
|
||||
|
||||
// GetBucketCORSResult GetBucketCORS请求返回的结果
|
||||
type GetBucketCORSResult CORSXML
|
||||
|
||||
// GetBucketInfoResult GetBucketInfo请求返回结果
|
||||
type GetBucketInfoResult struct {
|
||||
XMLName xml.Name `xml:"BucketInfo"`
|
||||
BucketInfo BucketInfo `xml:"Bucket"`
|
||||
}
|
||||
|
||||
// BucketInfo Bucket信息
|
||||
type BucketInfo struct {
|
||||
XMLName xml.Name `xml:"Bucket"`
|
||||
Name string `xml:"Name"` // Bucket名称
|
||||
Location string `xml:"Location"` // Bucket所在的数据中心
|
||||
CreationDate time.Time `xml:"CreationDate"` // Bucket创建时间
|
||||
ExtranetEndpoint string `xml:"ExtranetEndpoint"` // Bucket访问的外网域名
|
||||
IntranetEndpoint string `xml:"IntranetEndpoint"` // Bucket访问的内网域名
|
||||
ACL string `xml:"AccessControlList>Grant"` // Bucket权限
|
||||
Owner Owner `xml:"Owner"` // Bucket拥有者信息
|
||||
}
|
||||
|
||||
// ListObjectsResult ListObjects请求返回结果
|
||||
type ListObjectsResult struct {
|
||||
XMLName xml.Name `xml:"ListBucketResult"`
|
||||
Prefix string `xml:"Prefix"` // 本次查询结果的开始前缀
|
||||
Marker string `xml:"Marker"` // 这次查询的起点
|
||||
MaxKeys int `xml:"MaxKeys"` // 请求返回结果的最大数目
|
||||
Delimiter string `xml:"Delimiter"` // 对Object名字进行分组的字符
|
||||
IsTruncated bool `xml:"IsTruncated"` // 是否所有的结果都已经返回
|
||||
NextMarker string `xml:"NextMarker"` // 下一次查询的起点
|
||||
Objects []ObjectProperties `xml:"Contents"` // Object类别
|
||||
CommonPrefixes []string `xml:"CommonPrefixes>Prefix"` // 以delimiter结尾并有共同前缀的Object的集合
|
||||
}
|
||||
|
||||
// ObjectProperties Objecct属性
|
||||
type ObjectProperties struct {
|
||||
XMLName xml.Name `xml:"Contents"`
|
||||
Key string `xml:"Key"` // Object的Key
|
||||
Type string `xml:"Type"` // Object Type
|
||||
Size int64 `xml:"Size"` // Object的长度字节数
|
||||
ETag string `xml:"ETag"` // 标示Object的内容
|
||||
Owner Owner `xml:"Owner"` // 保存Object拥有者信息的容器
|
||||
LastModified time.Time `xml:"LastModified"` // Object最后修改时间
|
||||
StorageClass string `xml:"StorageClass"` // Object的存储类型,目前只能是Standard
|
||||
}
|
||||
|
||||
// Owner Bucket/Object的owner
|
||||
type Owner struct {
|
||||
XMLName xml.Name `xml:"Owner"`
|
||||
ID string `xml:"ID"` // 用户ID
|
||||
DisplayName string `xml:"DisplayName"` // Owner名字
|
||||
}
|
||||
|
||||
// CopyObjectResult CopyObject请求返回的结果
|
||||
type CopyObjectResult struct {
|
||||
XMLName xml.Name `xml:"CopyObjectResult"`
|
||||
LastModified time.Time `xml:"LastModified"` // 新Object最后更新时间
|
||||
ETag string `xml:"ETag"` // 新Object的ETag值
|
||||
}
|
||||
|
||||
// GetObjectACLResult GetObjectACL请求返回的结果
|
||||
type GetObjectACLResult GetBucketACLResult
|
||||
|
||||
type deleteXML struct {
|
||||
XMLName xml.Name `xml:"Delete"`
|
||||
Objects []DeleteObject `xml:"Object"` // 删除的所有Object
|
||||
Quiet bool `xml:"Quiet"` // 安静响应模式
|
||||
}
|
||||
|
||||
// DeleteObject 删除的Object
|
||||
type DeleteObject struct {
|
||||
XMLName xml.Name `xml:"Object"`
|
||||
Key string `xml:"Key"` // Object名称
|
||||
}
|
||||
|
||||
// DeleteObjectsResult DeleteObjects请求返回结果
|
||||
type DeleteObjectsResult struct {
|
||||
XMLName xml.Name `xml:"DeleteResult"`
|
||||
DeletedObjects []string `xml:"Deleted>Key"` // 删除的Object列表
|
||||
}
|
||||
|
||||
// InitiateMultipartUploadResult InitiateMultipartUpload请求返回结果
|
||||
type InitiateMultipartUploadResult struct {
|
||||
XMLName xml.Name `xml:"InitiateMultipartUploadResult"`
|
||||
Bucket string `xml:"Bucket"` // Bucket名称
|
||||
Key string `xml:"Key"` // 上传Object名称
|
||||
UploadID string `xml:"UploadId"` // 生成的UploadId
|
||||
}
|
||||
|
||||
// UploadPart 上传/拷贝的分片
|
||||
type UploadPart struct {
|
||||
XMLName xml.Name `xml:"Part"`
|
||||
PartNumber int `xml:"PartNumber"` // Part编号
|
||||
ETag string `xml:"ETag"` // ETag缓存码
|
||||
}
|
||||
|
||||
type uploadParts []UploadPart
|
||||
|
||||
func (slice uploadParts) Len() int {
|
||||
return len(slice)
|
||||
}
|
||||
|
||||
func (slice uploadParts) Less(i, j int) bool {
|
||||
return slice[i].PartNumber < slice[j].PartNumber
|
||||
}
|
||||
|
||||
func (slice uploadParts) Swap(i, j int) {
|
||||
slice[i], slice[j] = slice[j], slice[i]
|
||||
}
|
||||
|
||||
// UploadPartCopyResult 拷贝分片请求返回的结果
|
||||
type UploadPartCopyResult struct {
|
||||
XMLName xml.Name `xml:"CopyPartResult"`
|
||||
LastModified time.Time `xml:"LastModified"` // 最后修改时间
|
||||
ETag string `xml:"ETag"` // ETag
|
||||
}
|
||||
|
||||
type completeMultipartUploadXML struct {
|
||||
XMLName xml.Name `xml:"CompleteMultipartUpload"`
|
||||
Part []UploadPart `xml:"Part"`
|
||||
}
|
||||
|
||||
// CompleteMultipartUploadResult 提交分片上传任务返回结果
|
||||
type CompleteMultipartUploadResult struct {
|
||||
XMLName xml.Name `xml:"CompleteMultipartUploadResult"`
|
||||
Location string `xml:"Location"` // Object的URL
|
||||
Bucket string `xml:"Bucket"` // Bucket名称
|
||||
ETag string `xml:"ETag"` // Object的ETag
|
||||
Key string `xml:"Key"` // Object的名字
|
||||
}
|
||||
|
||||
// ListUploadedPartsResult ListUploadedParts请求返回结果
|
||||
type ListUploadedPartsResult struct {
|
||||
XMLName xml.Name `xml:"ListPartsResult"`
|
||||
Bucket string `xml:"Bucket"` // Bucket名称
|
||||
Key string `xml:"Key"` // Object名称
|
||||
UploadID string `xml:"UploadId"` // 上传Id
|
||||
NextPartNumberMarker string `xml:"NextPartNumberMarker"` // 下一个Part的位置
|
||||
MaxParts int `xml:"MaxParts"` // 最大Part个数
|
||||
IsTruncated bool `xml:"IsTruncated"` // 是否完全上传完成
|
||||
UploadedParts []UploadedPart `xml:"Part"` // 已完成的Part
|
||||
}
|
||||
|
||||
// UploadedPart 该任务已经上传的分片
|
||||
type UploadedPart struct {
|
||||
XMLName xml.Name `xml:"Part"`
|
||||
PartNumber int `xml:"PartNumber"` // Part编号
|
||||
LastModified time.Time `xml:"LastModified"` // 最后一次修改时间
|
||||
ETag string `xml:"ETag"` // ETag缓存码
|
||||
Size int `xml:"Size"` // Part大小
|
||||
}
|
||||
|
||||
// ListMultipartUploadResult ListMultipartUpload请求返回结果
|
||||
type ListMultipartUploadResult struct {
|
||||
XMLName xml.Name `xml:"ListMultipartUploadsResult"`
|
||||
Bucket string `xml:"Bucket"` // Bucket名称
|
||||
Delimiter string `xml:"Delimiter"` // 分组分割符
|
||||
Prefix string `xml:"Prefix"` // 筛选前缀
|
||||
KeyMarker string `xml:"KeyMarker"` // 起始Object位置
|
||||
UploadIDMarker string `xml:"UploadIdMarker"` // 起始UploadId位置
|
||||
NextKeyMarker string `xml:"NextKeyMarker"` // 如果没有全部返回,标明接下去的KeyMarker位置
|
||||
NextUploadIDMarker string `xml:"NextUploadIdMarker"` // 如果没有全部返回,标明接下去的UploadId位置
|
||||
MaxUploads int `xml:"MaxUploads"` // 返回最大Upload数目
|
||||
IsTruncated bool `xml:"IsTruncated"` // 是否完全返回
|
||||
Uploads []UncompletedUpload `xml:"Upload"` // 未完成上传的MultipartUpload
|
||||
CommonPrefixes []string `xml:"CommonPrefixes>Prefix"` // 所有名字包含指定的前缀且第一次出现delimiter字符之间的object作为一组的分组结果
|
||||
}
|
||||
|
||||
// UncompletedUpload 未完成的Upload任务
|
||||
type UncompletedUpload struct {
|
||||
XMLName xml.Name `xml:"Upload"`
|
||||
Key string `xml:"Key"` // Object名称
|
||||
UploadID string `xml:"UploadId"` // 对应UploadId
|
||||
Initiated time.Time `xml:"Initiated"` // 初始化时间,格式2012-02-23T04:18:23.000Z
|
||||
}
|
||||
|
||||
// 解析URL编码
|
||||
func decodeDeleteObjectsResult(result *DeleteObjectsResult) error {
|
||||
var err error
|
||||
for i := 0; i < len(result.DeletedObjects); i++ {
|
||||
result.DeletedObjects[i], err = url.QueryUnescape(result.DeletedObjects[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 解析URL编码
|
||||
func decodeListObjectsResult(result *ListObjectsResult) error {
|
||||
var err error
|
||||
result.Prefix, err = url.QueryUnescape(result.Prefix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result.Marker, err = url.QueryUnescape(result.Marker)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result.Delimiter, err = url.QueryUnescape(result.Delimiter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result.NextMarker, err = url.QueryUnescape(result.NextMarker)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := 0; i < len(result.Objects); i++ {
|
||||
result.Objects[i].Key, err = url.QueryUnescape(result.Objects[i].Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for i := 0; i < len(result.CommonPrefixes); i++ {
|
||||
result.CommonPrefixes[i], err = url.QueryUnescape(result.CommonPrefixes[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 解析URL编码
|
||||
func decodeListMultipartUploadResult(result *ListMultipartUploadResult) error {
|
||||
var err error
|
||||
result.Prefix, err = url.QueryUnescape(result.Prefix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result.Delimiter, err = url.QueryUnescape(result.Delimiter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result.KeyMarker, err = url.QueryUnescape(result.KeyMarker)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result.NextKeyMarker, err = url.QueryUnescape(result.NextKeyMarker)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := 0; i < len(result.Uploads); i++ {
|
||||
result.Uploads[i].Key, err = url.QueryUnescape(result.Uploads[i].Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for i := 0; i < len(result.CommonPrefixes); i++ {
|
||||
result.CommonPrefixes[i], err = url.QueryUnescape(result.CommonPrefixes[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,485 @@
|
|||
package oss
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
//
|
||||
// UploadFile 分片上传文件
|
||||
//
|
||||
// objectKey object名称。
|
||||
// filePath 本地文件。需要上传的文件。
|
||||
// partSize 本次上传文件片的大小,字节数。比如100 * 1024为每片100KB。
|
||||
// options 上传Object时可以指定Object的属性。详见InitiateMultipartUpload。
|
||||
//
|
||||
// error 操作成功为nil,非nil为错误信息。
|
||||
//
|
||||
func (bucket Bucket) UploadFile(objectKey, filePath string, partSize int64, options ...Option) error {
|
||||
if partSize < MinPartSize || partSize > MaxPartSize {
|
||||
return errors.New("oss: part size invalid range (1024KB, 5GB]")
|
||||
}
|
||||
|
||||
cpConf, err := getCpConfig(options, filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
routines := getRoutines(options)
|
||||
|
||||
if cpConf.IsEnable {
|
||||
return bucket.uploadFileWithCp(objectKey, filePath, partSize, options, cpConf.FilePath, routines)
|
||||
}
|
||||
|
||||
return bucket.uploadFile(objectKey, filePath, partSize, options, routines)
|
||||
}
|
||||
|
||||
// ----- 并发无断点的上传 -----
|
||||
|
||||
// 获取Checkpoint配置
|
||||
func getCpConfig(options []Option, filePath string) (*cpConfig, error) {
|
||||
cpc := &cpConfig{}
|
||||
cpcOpt, err := findOption(options, checkpointConfig, nil)
|
||||
if err != nil || cpcOpt == nil {
|
||||
return cpc, err
|
||||
}
|
||||
|
||||
cpc = cpcOpt.(*cpConfig)
|
||||
if cpc.IsEnable && cpc.FilePath == "" {
|
||||
cpc.FilePath = filePath + CheckpointFileSuffix
|
||||
}
|
||||
|
||||
return cpc, nil
|
||||
}
|
||||
|
||||
// 获取并发数,默认并发数1
|
||||
func getRoutines(options []Option) int {
|
||||
rtnOpt, err := findOption(options, routineNum, nil)
|
||||
if err != nil || rtnOpt == nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
rs := rtnOpt.(int)
|
||||
if rs < 1 {
|
||||
rs = 1
|
||||
} else if rs > 100 {
|
||||
rs = 100
|
||||
}
|
||||
|
||||
return rs
|
||||
}
|
||||
|
||||
// 获取进度回调
|
||||
func getProgressListener(options []Option) ProgressListener {
|
||||
isSet, listener, _ := isOptionSet(options, progressListener)
|
||||
if !isSet {
|
||||
return nil
|
||||
}
|
||||
return listener.(ProgressListener)
|
||||
}
|
||||
|
||||
// 测试使用
|
||||
type uploadPartHook func(id int, chunk FileChunk) error
|
||||
|
||||
var uploadPartHooker uploadPartHook = defaultUploadPart
|
||||
|
||||
func defaultUploadPart(id int, chunk FileChunk) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 工作协程参数
|
||||
type workerArg struct {
|
||||
bucket *Bucket
|
||||
filePath string
|
||||
imur InitiateMultipartUploadResult
|
||||
hook uploadPartHook
|
||||
}
|
||||
|
||||
// 工作协程
|
||||
func worker(id int, arg workerArg, jobs <-chan FileChunk, results chan<- UploadPart, failed chan<- error, die <-chan bool) {
|
||||
for chunk := range jobs {
|
||||
if err := arg.hook(id, chunk); err != nil {
|
||||
failed <- err
|
||||
break
|
||||
}
|
||||
part, err := arg.bucket.UploadPartFromFile(arg.imur, arg.filePath, chunk.Offset, chunk.Size, chunk.Number)
|
||||
if err != nil {
|
||||
failed <- err
|
||||
break
|
||||
}
|
||||
select {
|
||||
case <-die:
|
||||
return
|
||||
default:
|
||||
}
|
||||
results <- part
|
||||
}
|
||||
}
|
||||
|
||||
// 调度协程
|
||||
func scheduler(jobs chan FileChunk, chunks []FileChunk) {
|
||||
for _, chunk := range chunks {
|
||||
jobs <- chunk
|
||||
}
|
||||
close(jobs)
|
||||
}
|
||||
|
||||
func getTotalBytes(chunks []FileChunk) int64 {
|
||||
var tb int64
|
||||
for _, chunk := range chunks {
|
||||
tb += chunk.Size
|
||||
}
|
||||
return tb
|
||||
}
|
||||
|
||||
// 并发上传,不带断点续传功能
|
||||
func (bucket Bucket) uploadFile(objectKey, filePath string, partSize int64, options []Option, routines int) error {
|
||||
listener := getProgressListener(options)
|
||||
|
||||
chunks, err := SplitFileByPartSize(filePath, partSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 初始化上传任务
|
||||
imur, err := bucket.InitiateMultipartUpload(objectKey, options...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
jobs := make(chan FileChunk, len(chunks))
|
||||
results := make(chan UploadPart, len(chunks))
|
||||
failed := make(chan error)
|
||||
die := make(chan bool)
|
||||
|
||||
var completedBytes int64
|
||||
totalBytes := getTotalBytes(chunks)
|
||||
event := newProgressEvent(TransferStartedEvent, 0, totalBytes)
|
||||
publishProgress(listener, event)
|
||||
|
||||
// 启动工作协程
|
||||
arg := workerArg{&bucket, filePath, imur, uploadPartHooker}
|
||||
for w := 1; w <= routines; w++ {
|
||||
go worker(w, arg, jobs, results, failed, die)
|
||||
}
|
||||
|
||||
// 并发上传分片
|
||||
go scheduler(jobs, chunks)
|
||||
|
||||
// 等待分配分片上传完成
|
||||
completed := 0
|
||||
parts := make([]UploadPart, len(chunks))
|
||||
for completed < len(chunks) {
|
||||
select {
|
||||
case part := <-results:
|
||||
completed++
|
||||
parts[part.PartNumber-1] = part
|
||||
completedBytes += chunks[part.PartNumber-1].Size
|
||||
event = newProgressEvent(TransferDataEvent, completedBytes, totalBytes)
|
||||
publishProgress(listener, event)
|
||||
case err := <-failed:
|
||||
close(die)
|
||||
event = newProgressEvent(TransferFailedEvent, completedBytes, totalBytes)
|
||||
publishProgress(listener, event)
|
||||
bucket.AbortMultipartUpload(imur)
|
||||
return err
|
||||
}
|
||||
|
||||
if completed >= len(chunks) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
event = newProgressEvent(TransferStartedEvent, completedBytes, totalBytes)
|
||||
publishProgress(listener, event)
|
||||
|
||||
// 提交任务
|
||||
_, err = bucket.CompleteMultipartUpload(imur, parts)
|
||||
if err != nil {
|
||||
bucket.AbortMultipartUpload(imur)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ----- 并发带断点的上传 -----
|
||||
const uploadCpMagic = "FE8BB4EA-B593-4FAC-AD7A-2459A36E2E62"
|
||||
|
||||
type uploadCheckpoint struct {
|
||||
Magic string // magic
|
||||
MD5 string // cp内容的MD5
|
||||
FilePath string // 本地文件
|
||||
FileStat cpStat // 文件状态
|
||||
ObjectKey string // key
|
||||
UploadID string // upload id
|
||||
Parts []cpPart // 本地文件的全部分片
|
||||
}
|
||||
|
||||
type cpStat struct {
|
||||
Size int64 // 文件大小
|
||||
LastModified time.Time // 本地文件最后修改时间
|
||||
MD5 string // 本地文件MD5
|
||||
}
|
||||
|
||||
type cpPart struct {
|
||||
Chunk FileChunk // 分片
|
||||
Part UploadPart // 上传完成的分片
|
||||
IsCompleted bool // upload是否完成
|
||||
}
|
||||
|
||||
// CP数据是否有效,CP有效且文件没有更新时有效
|
||||
func (cp uploadCheckpoint) isValid(filePath string) (bool, error) {
|
||||
// 比较CP的Magic及MD5
|
||||
cpb := cp
|
||||
cpb.MD5 = ""
|
||||
js, _ := json.Marshal(cpb)
|
||||
sum := md5.Sum(js)
|
||||
b64 := base64.StdEncoding.EncodeToString(sum[:])
|
||||
|
||||
if cp.Magic != uploadCpMagic || b64 != cp.MD5 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// 确认本地文件是否更新
|
||||
fd, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
st, err := fd.Stat()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
md, err := calcFileMD5(filePath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// 比较文件大小/文件最后更新时间/文件MD5
|
||||
if cp.FileStat.Size != st.Size() ||
|
||||
cp.FileStat.LastModified != st.ModTime() ||
|
||||
cp.FileStat.MD5 != md {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// 从文件中load
|
||||
func (cp *uploadCheckpoint) load(filePath string) error {
|
||||
contents, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(contents, cp)
|
||||
return err
|
||||
}
|
||||
|
||||
// dump到文件
|
||||
func (cp *uploadCheckpoint) dump(filePath string) error {
|
||||
bcp := *cp
|
||||
|
||||
// 计算MD5
|
||||
bcp.MD5 = ""
|
||||
js, err := json.Marshal(bcp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sum := md5.Sum(js)
|
||||
b64 := base64.StdEncoding.EncodeToString(sum[:])
|
||||
bcp.MD5 = b64
|
||||
|
||||
// 序列化
|
||||
js, err = json.Marshal(bcp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// dump
|
||||
return ioutil.WriteFile(filePath, js, FilePermMode)
|
||||
}
|
||||
|
||||
// 更新分片状态
|
||||
func (cp *uploadCheckpoint) updatePart(part UploadPart) {
|
||||
cp.Parts[part.PartNumber-1].Part = part
|
||||
cp.Parts[part.PartNumber-1].IsCompleted = true
|
||||
}
|
||||
|
||||
// 未完成的分片
|
||||
func (cp *uploadCheckpoint) todoParts() []FileChunk {
|
||||
fcs := []FileChunk{}
|
||||
for _, part := range cp.Parts {
|
||||
if !part.IsCompleted {
|
||||
fcs = append(fcs, part.Chunk)
|
||||
}
|
||||
}
|
||||
return fcs
|
||||
}
|
||||
|
||||
// 所有的分片
|
||||
func (cp *uploadCheckpoint) allParts() []UploadPart {
|
||||
ps := []UploadPart{}
|
||||
for _, part := range cp.Parts {
|
||||
ps = append(ps, part.Part)
|
||||
}
|
||||
return ps
|
||||
}
|
||||
|
||||
// 完成的字节数
|
||||
func (cp *uploadCheckpoint) getCompletedBytes() int64 {
|
||||
var completedBytes int64
|
||||
for _, part := range cp.Parts {
|
||||
if part.IsCompleted {
|
||||
completedBytes += part.Chunk.Size
|
||||
}
|
||||
}
|
||||
return completedBytes
|
||||
}
|
||||
|
||||
// 计算文件文件MD5
|
||||
func calcFileMD5(filePath string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// 初始化分片上传
|
||||
func prepare(cp *uploadCheckpoint, objectKey, filePath string, partSize int64, bucket *Bucket, options []Option) error {
|
||||
// cp
|
||||
cp.Magic = uploadCpMagic
|
||||
cp.FilePath = filePath
|
||||
cp.ObjectKey = objectKey
|
||||
|
||||
// localfile
|
||||
fd, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
st, err := fd.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cp.FileStat.Size = st.Size()
|
||||
cp.FileStat.LastModified = st.ModTime()
|
||||
md, err := calcFileMD5(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cp.FileStat.MD5 = md
|
||||
|
||||
// chunks
|
||||
parts, err := SplitFileByPartSize(filePath, partSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cp.Parts = make([]cpPart, len(parts))
|
||||
for i, part := range parts {
|
||||
cp.Parts[i].Chunk = part
|
||||
cp.Parts[i].IsCompleted = false
|
||||
}
|
||||
|
||||
// init load
|
||||
imur, err := bucket.InitiateMultipartUpload(objectKey, options...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cp.UploadID = imur.UploadID
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 提交分片上传,删除CP文件
|
||||
func complete(cp *uploadCheckpoint, bucket *Bucket, parts []UploadPart, cpFilePath string) error {
|
||||
imur := InitiateMultipartUploadResult{Bucket: bucket.BucketName,
|
||||
Key: cp.ObjectKey, UploadID: cp.UploadID}
|
||||
_, err := bucket.CompleteMultipartUpload(imur, parts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
os.Remove(cpFilePath)
|
||||
return err
|
||||
}
|
||||
|
||||
// 并发带断点的上传
|
||||
func (bucket Bucket) uploadFileWithCp(objectKey, filePath string, partSize int64, options []Option, cpFilePath string, routines int) error {
|
||||
listener := getProgressListener(options)
|
||||
|
||||
// LOAD CP数据
|
||||
ucp := uploadCheckpoint{}
|
||||
err := ucp.load(cpFilePath)
|
||||
if err != nil {
|
||||
os.Remove(cpFilePath)
|
||||
}
|
||||
|
||||
// LOAD出错或数据无效重新初始化上传
|
||||
valid, err := ucp.isValid(filePath)
|
||||
if err != nil || !valid {
|
||||
if err = prepare(&ucp, objectKey, filePath, partSize, &bucket, options); err != nil {
|
||||
return err
|
||||
}
|
||||
os.Remove(cpFilePath)
|
||||
}
|
||||
|
||||
chunks := ucp.todoParts()
|
||||
imur := InitiateMultipartUploadResult{
|
||||
Bucket: bucket.BucketName,
|
||||
Key: objectKey,
|
||||
UploadID: ucp.UploadID}
|
||||
|
||||
jobs := make(chan FileChunk, len(chunks))
|
||||
results := make(chan UploadPart, len(chunks))
|
||||
failed := make(chan error)
|
||||
die := make(chan bool)
|
||||
|
||||
completedBytes := ucp.getCompletedBytes()
|
||||
event := newProgressEvent(TransferStartedEvent, completedBytes, ucp.FileStat.Size)
|
||||
publishProgress(listener, event)
|
||||
|
||||
// 启动工作协程
|
||||
arg := workerArg{&bucket, filePath, imur, uploadPartHooker}
|
||||
for w := 1; w <= routines; w++ {
|
||||
go worker(w, arg, jobs, results, failed, die)
|
||||
}
|
||||
|
||||
// 并发上传分片
|
||||
go scheduler(jobs, chunks)
|
||||
|
||||
// 等待分配分片上传完成
|
||||
completed := 0
|
||||
for completed < len(chunks) {
|
||||
select {
|
||||
case part := <-results:
|
||||
completed++
|
||||
ucp.updatePart(part)
|
||||
ucp.dump(cpFilePath)
|
||||
completedBytes += ucp.Parts[part.PartNumber-1].Chunk.Size
|
||||
event = newProgressEvent(TransferDataEvent, completedBytes, ucp.FileStat.Size)
|
||||
publishProgress(listener, event)
|
||||
case err := <-failed:
|
||||
close(die)
|
||||
event = newProgressEvent(TransferFailedEvent, completedBytes, ucp.FileStat.Size)
|
||||
publishProgress(listener, event)
|
||||
return err
|
||||
}
|
||||
|
||||
if completed >= len(chunks) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
event = newProgressEvent(TransferCompletedEvent, completedBytes, ucp.FileStat.Size)
|
||||
publishProgress(listener, event)
|
||||
|
||||
// 提交分片上传
|
||||
err = complete(&ucp, &bucket, ucp.allParts(), cpFilePath)
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
package oss
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/crc64"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Get User Agent
|
||||
// Go sdk相关信息,包括sdk版本,操作系统类型,GO版本
|
||||
var userAgent = func() string {
|
||||
sys := getSysInfo()
|
||||
return fmt.Sprintf("aliyun-sdk-go/%s (%s/%s/%s;%s)", Version, sys.name,
|
||||
sys.release, sys.machine, runtime.Version())
|
||||
}()
|
||||
|
||||
type sysInfo struct {
|
||||
name string // 操作系统名称windows/Linux
|
||||
release string // 操作系统版本 2.6.32-220.23.2.ali1089.el5.x86_64等
|
||||
machine string // 机器类型amd64/x86_64
|
||||
}
|
||||
|
||||
// Get system info
|
||||
// 获取操作系统信息、机器类型
|
||||
func getSysInfo() sysInfo {
|
||||
name := runtime.GOOS
|
||||
release := "-"
|
||||
machine := runtime.GOARCH
|
||||
if out, err := exec.Command("uname", "-s").CombinedOutput(); err == nil {
|
||||
name = string(bytes.TrimSpace(out))
|
||||
}
|
||||
if out, err := exec.Command("uname", "-r").CombinedOutput(); err == nil {
|
||||
release = string(bytes.TrimSpace(out))
|
||||
}
|
||||
if out, err := exec.Command("uname", "-m").CombinedOutput(); err == nil {
|
||||
machine = string(bytes.TrimSpace(out))
|
||||
}
|
||||
return sysInfo{name: name, release: release, machine: machine}
|
||||
}
|
||||
|
||||
// GetNowSec returns Unix time, the number of seconds elapsed since January 1, 1970 UTC.
|
||||
// 获取当前时间,从UTC开始的秒数。
|
||||
func GetNowSec() int64 {
|
||||
return time.Now().Unix()
|
||||
}
|
||||
|
||||
// GetNowNanoSec returns t as a Unix time, the number of nanoseconds elapsed
|
||||
// since January 1, 1970 UTC. The result is undefined if the Unix time
|
||||
// in nanoseconds cannot be represented by an int64. Note that this
|
||||
// means the result of calling UnixNano on the zero Time is undefined.
|
||||
// 获取当前时间,从UTC开始的纳秒。
|
||||
func GetNowNanoSec() int64 {
|
||||
return time.Now().UnixNano()
|
||||
}
|
||||
|
||||
// GetNowGMT 获取当前时间,格式形如"Mon, 02 Jan 2006 15:04:05 GMT",HTTP中使用的时间格式
|
||||
func GetNowGMT() string {
|
||||
return time.Now().UTC().Format(http.TimeFormat)
|
||||
}
|
||||
|
||||
// FileChunk 文件片定义
|
||||
type FileChunk struct {
|
||||
Number int // 块序号
|
||||
Offset int64 // 块在文件中的偏移量
|
||||
Size int64 // 块大小
|
||||
}
|
||||
|
||||
// SplitFileByPartNum Split big file to part by the num of part
|
||||
// 按指定的块数分割文件。返回值FileChunk为分割结果,error为nil时有效。
|
||||
func SplitFileByPartNum(fileName string, chunkNum int) ([]FileChunk, error) {
|
||||
if chunkNum <= 0 || chunkNum > 10000 {
|
||||
return nil, errors.New("chunkNum invalid")
|
||||
}
|
||||
|
||||
file, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
stat, err := file.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if int64(chunkNum) > stat.Size() {
|
||||
return nil, errors.New("oss: chunkNum invalid")
|
||||
}
|
||||
|
||||
var chunks []FileChunk
|
||||
var chunk = FileChunk{}
|
||||
var chunkN = (int64)(chunkNum)
|
||||
for i := int64(0); i < chunkN; i++ {
|
||||
chunk.Number = int(i + 1)
|
||||
chunk.Offset = i * (stat.Size() / chunkN)
|
||||
if i == chunkN-1 {
|
||||
chunk.Size = stat.Size()/chunkN + stat.Size()%chunkN
|
||||
} else {
|
||||
chunk.Size = stat.Size() / chunkN
|
||||
}
|
||||
chunks = append(chunks, chunk)
|
||||
}
|
||||
|
||||
return chunks, nil
|
||||
}
|
||||
|
||||
// SplitFileByPartSize Split big file to part by the size of part
|
||||
// 按块大小分割文件。返回值FileChunk为分割结果,error为nil时有效。
|
||||
func SplitFileByPartSize(fileName string, chunkSize int64) ([]FileChunk, error) {
|
||||
if chunkSize <= 0 {
|
||||
return nil, errors.New("chunkSize invalid")
|
||||
}
|
||||
|
||||
file, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
stat, err := file.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var chunkN = stat.Size() / chunkSize
|
||||
if chunkN >= 10000 {
|
||||
return nil, errors.New("Too many parts, please increase part size.")
|
||||
}
|
||||
|
||||
var chunks []FileChunk
|
||||
var chunk = FileChunk{}
|
||||
for i := int64(0); i < chunkN; i++ {
|
||||
chunk.Number = int(i + 1)
|
||||
chunk.Offset = i * chunkSize
|
||||
chunk.Size = chunkSize
|
||||
chunks = append(chunks, chunk)
|
||||
}
|
||||
|
||||
if stat.Size()%chunkSize > 0 {
|
||||
chunk.Number = len(chunks) + 1
|
||||
chunk.Offset = int64(len(chunks)) * chunkSize
|
||||
chunk.Size = stat.Size() % chunkSize
|
||||
chunks = append(chunks, chunk)
|
||||
}
|
||||
|
||||
return chunks, nil
|
||||
}
|
||||
|
||||
// GetPartEnd 计算结束位置
|
||||
func GetPartEnd(begin int64, total int64, per int64) int64 {
|
||||
if begin+per > total {
|
||||
return total - 1
|
||||
}
|
||||
return begin + per - 1
|
||||
}
|
||||
|
||||
// crcTable returns the Table constructed from the specified polynomial
|
||||
var crcTable = func() *crc64.Table {
|
||||
return crc64.MakeTable(crc64.ECMA)
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
|
||||
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 2015-2015 Li Yi (denverdino@gmail.com).
|
||||
|
||||
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.
|
|
@ -0,0 +1,345 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/denverdino/aliyungo/util"
|
||||
)
|
||||
|
||||
// RemovalPolicy.N add index to array item
|
||||
// RemovalPolicy=["a", "b"] => RemovalPolicy.1="a" RemovalPolicy.2="b"
|
||||
type FlattenArray []string
|
||||
|
||||
// string contains underline which will be replaced with dot
|
||||
// SystemDisk_Category => SystemDisk.Category
|
||||
type UnderlineString string
|
||||
|
||||
// A Client represents a client of ECS services
|
||||
type Client struct {
|
||||
AccessKeyId string //Access Key Id
|
||||
AccessKeySecret string //Access Key Secret
|
||||
debug bool
|
||||
httpClient *http.Client
|
||||
endpoint string
|
||||
version string
|
||||
serviceCode string
|
||||
regionID Region
|
||||
businessInfo string
|
||||
}
|
||||
|
||||
// NewClient creates a new instance of ECS client
|
||||
func (client *Client) Init(endpoint, version, accessKeyId, accessKeySecret string) {
|
||||
client.AccessKeyId = accessKeyId
|
||||
client.AccessKeySecret = accessKeySecret + "&"
|
||||
client.debug = false
|
||||
client.httpClient = &http.Client{}
|
||||
client.endpoint = endpoint
|
||||
client.version = version
|
||||
}
|
||||
|
||||
func (client *Client) NewInit(endpoint, version, accessKeyId, accessKeySecret, serviceCode string, regionID Region) {
|
||||
client.Init(endpoint, version, accessKeyId, accessKeySecret)
|
||||
client.serviceCode = serviceCode
|
||||
client.regionID = regionID
|
||||
client.setEndpointByLocation(regionID, serviceCode, accessKeyId, accessKeySecret)
|
||||
}
|
||||
|
||||
//NewClient using location service
|
||||
func (client *Client) setEndpointByLocation(region Region, serviceCode, accessKeyId, accessKeySecret string) {
|
||||
locationClient := NewLocationClient(accessKeyId, accessKeySecret)
|
||||
ep := locationClient.DescribeOpenAPIEndpoint(region, serviceCode)
|
||||
if ep == "" {
|
||||
ep = loadEndpointFromFile(region, serviceCode)
|
||||
}
|
||||
|
||||
if ep != "" {
|
||||
client.endpoint = ep
|
||||
}
|
||||
}
|
||||
|
||||
// SetEndpoint sets custom endpoint
|
||||
func (client *Client) SetEndpoint(endpoint string) {
|
||||
client.endpoint = endpoint
|
||||
}
|
||||
|
||||
// SetEndpoint sets custom version
|
||||
func (client *Client) SetVersion(version string) {
|
||||
client.version = version
|
||||
}
|
||||
|
||||
func (client *Client) SetRegionID(regionID Region) {
|
||||
client.regionID = regionID
|
||||
}
|
||||
|
||||
//SetServiceCode sets serviceCode
|
||||
func (client *Client) SetServiceCode(serviceCode string) {
|
||||
client.serviceCode = serviceCode
|
||||
}
|
||||
|
||||
// SetAccessKeyId sets new AccessKeyId
|
||||
func (client *Client) SetAccessKeyId(id string) {
|
||||
client.AccessKeyId = id
|
||||
}
|
||||
|
||||
// SetAccessKeySecret sets new AccessKeySecret
|
||||
func (client *Client) SetAccessKeySecret(secret string) {
|
||||
client.AccessKeySecret = secret + "&"
|
||||
}
|
||||
|
||||
// SetDebug sets debug mode to log the request/response message
|
||||
func (client *Client) SetDebug(debug bool) {
|
||||
client.debug = debug
|
||||
}
|
||||
|
||||
// SetBusinessInfo sets business info to log the request/response message
|
||||
func (client *Client) SetBusinessInfo(businessInfo string) {
|
||||
if strings.HasPrefix(businessInfo, "/") {
|
||||
client.businessInfo = businessInfo
|
||||
} else if businessInfo != "" {
|
||||
client.businessInfo = "/" + businessInfo
|
||||
}
|
||||
}
|
||||
|
||||
// Invoke sends the raw HTTP request for ECS services
|
||||
func (client *Client) Invoke(action string, args interface{}, response interface{}) error {
|
||||
|
||||
request := Request{}
|
||||
request.init(client.version, action, client.AccessKeyId)
|
||||
|
||||
query := util.ConvertToQueryValues(request)
|
||||
util.SetQueryValues(args, &query)
|
||||
|
||||
// Sign request
|
||||
signature := util.CreateSignatureForRequest(ECSRequestMethod, &query, client.AccessKeySecret)
|
||||
|
||||
// Generate the request URL
|
||||
requestURL := client.endpoint + "?" + query.Encode() + "&Signature=" + url.QueryEscape(signature)
|
||||
|
||||
httpReq, err := http.NewRequest(ECSRequestMethod, requestURL, nil)
|
||||
|
||||
if err != nil {
|
||||
return GetClientError(err)
|
||||
}
|
||||
|
||||
// TODO move to util and add build val flag
|
||||
httpReq.Header.Set("X-SDK-Client", `AliyunGO/`+Version+client.businessInfo)
|
||||
|
||||
t0 := time.Now()
|
||||
httpResp, err := client.httpClient.Do(httpReq)
|
||||
t1 := time.Now()
|
||||
if err != nil {
|
||||
return GetClientError(err)
|
||||
}
|
||||
statusCode := httpResp.StatusCode
|
||||
|
||||
if client.debug {
|
||||
log.Printf("Invoke %s %s %d (%v)", ECSRequestMethod, requestURL, statusCode, t1.Sub(t0))
|
||||
}
|
||||
|
||||
defer httpResp.Body.Close()
|
||||
body, err := ioutil.ReadAll(httpResp.Body)
|
||||
|
||||
if err != nil {
|
||||
return GetClientError(err)
|
||||
}
|
||||
|
||||
if client.debug {
|
||||
var prettyJSON bytes.Buffer
|
||||
err = json.Indent(&prettyJSON, body, "", " ")
|
||||
log.Println(string(prettyJSON.Bytes()))
|
||||
}
|
||||
|
||||
if statusCode >= 400 && statusCode <= 599 {
|
||||
errorResponse := ErrorResponse{}
|
||||
err = json.Unmarshal(body, &errorResponse)
|
||||
ecsError := &Error{
|
||||
ErrorResponse: errorResponse,
|
||||
StatusCode: statusCode,
|
||||
}
|
||||
return ecsError
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, response)
|
||||
//log.Printf("%++v", response)
|
||||
if err != nil {
|
||||
return GetClientError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Invoke sends the raw HTTP request for ECS services
|
||||
func (client *Client) InvokeByFlattenMethod(action string, args interface{}, response interface{}) error {
|
||||
|
||||
request := Request{}
|
||||
request.init(client.version, action, client.AccessKeyId)
|
||||
|
||||
query := util.ConvertToQueryValues(request)
|
||||
|
||||
util.SetQueryValueByFlattenMethod(args, &query)
|
||||
|
||||
// Sign request
|
||||
signature := util.CreateSignatureForRequest(ECSRequestMethod, &query, client.AccessKeySecret)
|
||||
|
||||
// Generate the request URL
|
||||
requestURL := client.endpoint + "?" + query.Encode() + "&Signature=" + url.QueryEscape(signature)
|
||||
|
||||
httpReq, err := http.NewRequest(ECSRequestMethod, requestURL, nil)
|
||||
|
||||
if err != nil {
|
||||
return GetClientError(err)
|
||||
}
|
||||
|
||||
// TODO move to util and add build val flag
|
||||
httpReq.Header.Set("X-SDK-Client", `AliyunGO/`+Version+client.businessInfo)
|
||||
|
||||
t0 := time.Now()
|
||||
httpResp, err := client.httpClient.Do(httpReq)
|
||||
t1 := time.Now()
|
||||
if err != nil {
|
||||
return GetClientError(err)
|
||||
}
|
||||
statusCode := httpResp.StatusCode
|
||||
|
||||
if client.debug {
|
||||
log.Printf("Invoke %s %s %d (%v)", ECSRequestMethod, requestURL, statusCode, t1.Sub(t0))
|
||||
}
|
||||
|
||||
defer httpResp.Body.Close()
|
||||
body, err := ioutil.ReadAll(httpResp.Body)
|
||||
|
||||
if err != nil {
|
||||
return GetClientError(err)
|
||||
}
|
||||
|
||||
if client.debug {
|
||||
var prettyJSON bytes.Buffer
|
||||
err = json.Indent(&prettyJSON, body, "", " ")
|
||||
log.Println(string(prettyJSON.Bytes()))
|
||||
}
|
||||
|
||||
if statusCode >= 400 && statusCode <= 599 {
|
||||
errorResponse := ErrorResponse{}
|
||||
err = json.Unmarshal(body, &errorResponse)
|
||||
ecsError := &Error{
|
||||
ErrorResponse: errorResponse,
|
||||
StatusCode: statusCode,
|
||||
}
|
||||
return ecsError
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, response)
|
||||
//log.Printf("%++v", response)
|
||||
if err != nil {
|
||||
return GetClientError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Invoke sends the raw HTTP request for ECS services
|
||||
//改进了一下上面那个方法,可以使用各种Http方法
|
||||
//2017.1.30 增加了一个path参数,用来拓展访问的地址
|
||||
func (client *Client) InvokeByAnyMethod(method, action, path string, args interface{}, response interface{}) error {
|
||||
|
||||
request := Request{}
|
||||
request.init(client.version, action, client.AccessKeyId)
|
||||
|
||||
data := util.ConvertToQueryValues(request)
|
||||
util.SetQueryValues(args, &data)
|
||||
|
||||
// Sign request
|
||||
signature := util.CreateSignatureForRequest(method, &data, client.AccessKeySecret)
|
||||
|
||||
data.Add("Signature", signature)
|
||||
// Generate the request URL
|
||||
var (
|
||||
httpReq *http.Request
|
||||
err error
|
||||
)
|
||||
if method == http.MethodGet {
|
||||
requestURL := client.endpoint + path + "?" + data.Encode()
|
||||
//fmt.Println(requestURL)
|
||||
httpReq, err = http.NewRequest(method, requestURL, nil)
|
||||
} else {
|
||||
//fmt.Println(client.endpoint + path)
|
||||
httpReq, err = http.NewRequest(method, client.endpoint+path, strings.NewReader(data.Encode()))
|
||||
httpReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return GetClientError(err)
|
||||
}
|
||||
|
||||
// TODO move to util and add build val flag
|
||||
httpReq.Header.Set("X-SDK-Client", `AliyunGO/`+Version+client.businessInfo)
|
||||
|
||||
t0 := time.Now()
|
||||
httpResp, err := client.httpClient.Do(httpReq)
|
||||
t1 := time.Now()
|
||||
if err != nil {
|
||||
return GetClientError(err)
|
||||
}
|
||||
statusCode := httpResp.StatusCode
|
||||
|
||||
if client.debug {
|
||||
log.Printf("Invoke %s %s %d (%v) %v", ECSRequestMethod, client.endpoint, statusCode, t1.Sub(t0), data.Encode())
|
||||
}
|
||||
|
||||
defer httpResp.Body.Close()
|
||||
body, err := ioutil.ReadAll(httpResp.Body)
|
||||
|
||||
if err != nil {
|
||||
return GetClientError(err)
|
||||
}
|
||||
|
||||
if client.debug {
|
||||
var prettyJSON bytes.Buffer
|
||||
err = json.Indent(&prettyJSON, body, "", " ")
|
||||
log.Println(string(prettyJSON.Bytes()))
|
||||
}
|
||||
|
||||
if statusCode >= 400 && statusCode <= 599 {
|
||||
errorResponse := ErrorResponse{}
|
||||
err = json.Unmarshal(body, &errorResponse)
|
||||
ecsError := &Error{
|
||||
ErrorResponse: errorResponse,
|
||||
StatusCode: statusCode,
|
||||
}
|
||||
return ecsError
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, response)
|
||||
//log.Printf("%++v", response)
|
||||
if err != nil {
|
||||
return GetClientError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenerateClientToken generates the Client Token with random string
|
||||
func (client *Client) GenerateClientToken() string {
|
||||
return util.CreateRandomString()
|
||||
}
|
||||
|
||||
func GetClientErrorFromString(str string) error {
|
||||
return &Error{
|
||||
ErrorResponse: ErrorResponse{
|
||||
Code: "AliyunGoClientFailure",
|
||||
Message: str,
|
||||
},
|
||||
StatusCode: -1,
|
||||
}
|
||||
}
|
||||
|
||||
func GetClientError(err error) error {
|
||||
return GetClientErrorFromString(err.Error())
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// LocationDefaultEndpoint is the default API endpoint of Location services
|
||||
locationDefaultEndpoint = "https://location.aliyuncs.com"
|
||||
locationAPIVersion = "2015-06-12"
|
||||
HTTP_PROTOCOL = "http"
|
||||
HTTPS_PROTOCOL = "https"
|
||||
)
|
||||
|
||||
var (
|
||||
endpoints = make(map[Region]map[string]string)
|
||||
)
|
||||
|
||||
//init endpoints from file
|
||||
func init() {
|
||||
|
||||
}
|
||||
|
||||
func NewLocationClient(accessKeyId, accessKeySecret string) *Client {
|
||||
endpoint := os.Getenv("LOCATION_ENDPOINT")
|
||||
if endpoint == "" {
|
||||
endpoint = locationDefaultEndpoint
|
||||
}
|
||||
|
||||
client := &Client{}
|
||||
client.Init(endpoint, locationAPIVersion, accessKeyId, accessKeySecret)
|
||||
return client
|
||||
}
|
||||
|
||||
func (client *Client) DescribeEndpoint(args *DescribeEndpointArgs) (*DescribeEndpointResponse, error) {
|
||||
response := &DescribeEndpointResponse{}
|
||||
err := client.Invoke("DescribeEndpoint", args, response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response, err
|
||||
}
|
||||
|
||||
func getProductRegionEndpoint(region Region, serviceCode string) string {
|
||||
if sp, ok := endpoints[region]; ok {
|
||||
if endpoint, ok := sp[serviceCode]; ok {
|
||||
return endpoint
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func setProductRegionEndpoint(region Region, serviceCode string, endpoint string) {
|
||||
endpoints[region] = map[string]string{
|
||||
serviceCode: endpoint,
|
||||
}
|
||||
}
|
||||
|
||||
func (client *Client) DescribeOpenAPIEndpoint(region Region, serviceCode string) string {
|
||||
if endpoint := getProductRegionEndpoint(region, serviceCode); endpoint != "" {
|
||||
return endpoint
|
||||
}
|
||||
|
||||
defaultProtocols := HTTP_PROTOCOL
|
||||
|
||||
args := &DescribeEndpointArgs{
|
||||
Id: region,
|
||||
ServiceCode: serviceCode,
|
||||
Type: "openAPI",
|
||||
}
|
||||
|
||||
endpoint, err := client.DescribeEndpoint(args)
|
||||
if err != nil || endpoint.Endpoint == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
for _, protocol := range endpoint.Protocols.Protocols {
|
||||
if strings.ToLower(protocol) == HTTPS_PROTOCOL {
|
||||
defaultProtocols = HTTPS_PROTOCOL
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
ep := fmt.Sprintf("%s://%s", defaultProtocols, endpoint.Endpoint)
|
||||
|
||||
setProductRegionEndpoint(region, serviceCode, ep)
|
||||
return ep
|
||||
}
|
||||
|
||||
func loadEndpointFromFile(region Region, serviceCode string) string {
|
||||
data, err := ioutil.ReadFile("./endpoints.xml")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
var endpoints Endpoints
|
||||
err = xml.Unmarshal(data, &endpoints)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
for _, endpoint := range endpoints.Endpoint {
|
||||
if endpoint.RegionIds.RegionId == string(region) {
|
||||
for _, product := range endpoint.Products.Product {
|
||||
if strings.ToLower(product.ProductName) == serviceCode {
|
||||
return fmt.Sprintf("%s://%s", HTTPS_PROTOCOL, product.DomainName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,34 @@
|
|||
package common
|
||||
|
||||
// Region represents ECS region
|
||||
type Region string
|
||||
|
||||
// Constants of region definition
|
||||
const (
|
||||
Hangzhou = Region("cn-hangzhou")
|
||||
Qingdao = Region("cn-qingdao")
|
||||
Beijing = Region("cn-beijing")
|
||||
Hongkong = Region("cn-hongkong")
|
||||
Shenzhen = Region("cn-shenzhen")
|
||||
Shanghai = Region("cn-shanghai")
|
||||
Zhangjiakou = Region("cn-zhangjiakou")
|
||||
|
||||
APSouthEast1 = Region("ap-southeast-1")
|
||||
APNorthEast1 = Region("ap-northeast-1")
|
||||
APSouthEast2 = Region("ap-southeast-2")
|
||||
|
||||
USWest1 = Region("us-west-1")
|
||||
USEast1 = Region("us-east-1")
|
||||
|
||||
MEEast1 = Region("me-east-1")
|
||||
|
||||
EUCentral1 = Region("eu-central-1")
|
||||
)
|
||||
|
||||
var ValidRegions = []Region{
|
||||
Hangzhou, Qingdao, Beijing, Shenzhen, Hongkong, Shanghai, Zhangjiakou,
|
||||
USWest1, USEast1,
|
||||
APNorthEast1, APSouthEast1, APSouthEast2,
|
||||
MEEast1,
|
||||
EUCentral1,
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/denverdino/aliyungo/util"
|
||||
)
|
||||
|
||||
// Constants for Aliyun API requests
|
||||
const (
|
||||
SignatureVersion = "1.0"
|
||||
SignatureMethod = "HMAC-SHA1"
|
||||
JSONResponseFormat = "JSON"
|
||||
XMLResponseFormat = "XML"
|
||||
ECSRequestMethod = "GET"
|
||||
)
|
||||
|
||||
type Request struct {
|
||||
Format string
|
||||
Version string
|
||||
AccessKeyId string
|
||||
Signature string
|
||||
SignatureMethod string
|
||||
Timestamp util.ISO6801Time
|
||||
SignatureVersion string
|
||||
SignatureNonce string
|
||||
ResourceOwnerAccount string
|
||||
Action string
|
||||
}
|
||||
|
||||
func (request *Request) init(version string, action string, AccessKeyId string) {
|
||||
request.Format = JSONResponseFormat
|
||||
request.Timestamp = util.NewISO6801Time(time.Now().UTC())
|
||||
request.Version = version
|
||||
request.SignatureVersion = SignatureVersion
|
||||
request.SignatureMethod = SignatureMethod
|
||||
request.SignatureNonce = util.CreateRandomString()
|
||||
request.Action = action
|
||||
request.AccessKeyId = AccessKeyId
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
RequestId string
|
||||
}
|
||||
|
||||
type ErrorResponse struct {
|
||||
Response
|
||||
HostId string
|
||||
Code string
|
||||
Message string
|
||||
}
|
||||
|
||||
// An Error represents a custom error for Aliyun API failure response
|
||||
type Error struct {
|
||||
ErrorResponse
|
||||
StatusCode int //Status Code of HTTP Response
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return fmt.Sprintf("Aliyun API Error: RequestId: %s Status Code: %d Code: %s Message: %s", e.RequestId, e.StatusCode, e.Code, e.Message)
|
||||
}
|
||||
|
||||
type Pagination struct {
|
||||
PageNumber int
|
||||
PageSize int
|
||||
}
|
||||
|
||||
func (p *Pagination) SetPageSize(size int) {
|
||||
p.PageSize = size
|
||||
}
|
||||
|
||||
func (p *Pagination) Validate() {
|
||||
if p.PageNumber < 0 {
|
||||
log.Printf("Invalid PageNumber: %d", p.PageNumber)
|
||||
p.PageNumber = 1
|
||||
}
|
||||
if p.PageSize < 0 {
|
||||
log.Printf("Invalid PageSize: %d", p.PageSize)
|
||||
p.PageSize = 10
|
||||
} else if p.PageSize > 50 {
|
||||
log.Printf("Invalid PageSize: %d", p.PageSize)
|
||||
p.PageSize = 50
|
||||
}
|
||||
}
|
||||
|
||||
// A PaginationResponse represents a response with pagination information
|
||||
type PaginationResult struct {
|
||||
TotalCount int
|
||||
PageNumber int
|
||||
PageSize int
|
||||
}
|
||||
|
||||
// NextPage gets the next page of the result set
|
||||
func (r *PaginationResult) NextPage() *Pagination {
|
||||
if r.PageNumber*r.PageSize >= r.TotalCount {
|
||||
return nil
|
||||
}
|
||||
return &Pagination{PageNumber: r.PageNumber + 1, PageSize: r.PageSize}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
package common
|
||||
|
||||
type InternetChargeType string
|
||||
|
||||
const (
|
||||
PayByBandwidth = InternetChargeType("PayByBandwidth")
|
||||
PayByTraffic = InternetChargeType("PayByTraffic")
|
||||
)
|
||||
|
||||
type InstanceChargeType string
|
||||
|
||||
const (
|
||||
PrePaid = InstanceChargeType("PrePaid")
|
||||
PostPaid = InstanceChargeType("PostPaid")
|
||||
)
|
||||
|
||||
type DescribeEndpointArgs struct {
|
||||
Id Region
|
||||
ServiceCode string
|
||||
Type string
|
||||
}
|
||||
|
||||
type EndpointItem struct {
|
||||
Protocols struct {
|
||||
Protocols []string
|
||||
}
|
||||
Type string
|
||||
Namespace string
|
||||
Id Region
|
||||
SerivceCode string
|
||||
Endpoint string
|
||||
}
|
||||
|
||||
type DescribeEndpointResponse struct {
|
||||
Response
|
||||
EndpointItem
|
||||
}
|
||||
|
||||
type NetType string
|
||||
|
||||
const (
|
||||
Internet = NetType("Internet")
|
||||
Intranet = NetType("Intranet")
|
||||
)
|
||||
|
||||
type TimeType string
|
||||
|
||||
const (
|
||||
Hour = TimeType("Hour")
|
||||
Day = TimeType("Day")
|
||||
Month = TimeType("Month")
|
||||
Year = TimeType("Year")
|
||||
)
|
||||
|
||||
type NetworkType string
|
||||
|
||||
const (
|
||||
Classic = NetworkType("Classic")
|
||||
VPC = NetworkType("VPC")
|
||||
)
|
||||
|
||||
type BusinessInfo struct {
|
||||
Pack string `json:"pack,omitempty"`
|
||||
ActivityId string `json:"activityId,omitempty"`
|
||||
}
|
||||
|
||||
//xml
|
||||
type Endpoints struct {
|
||||
Endpoint []Endpoint `xml:"Endpoint"`
|
||||
}
|
||||
|
||||
type Endpoint struct {
|
||||
Name string `xml:"name,attr"`
|
||||
RegionIds RegionIds `xml:"RegionIds"`
|
||||
Products Products `xml:"Products"`
|
||||
}
|
||||
|
||||
type RegionIds struct {
|
||||
RegionId string `xml:"RegionId"`
|
||||
}
|
||||
|
||||
type Products struct {
|
||||
Product []Product `xml:"Product"`
|
||||
}
|
||||
|
||||
type Product struct {
|
||||
ProductName string `xml:"ProductName"`
|
||||
DomainName string `xml:"DomainName"`
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
package common
|
||||
|
||||
const Version = "0.1"
|
|
@ -0,0 +1,74 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
)
|
||||
|
||||
// Interval for checking status in WaitForXXX method
|
||||
const DefaultWaitForInterval = 5
|
||||
|
||||
// Default timeout value for WaitForXXX method
|
||||
const DefaultTimeout = 60
|
||||
|
||||
type Client struct {
|
||||
common.Client
|
||||
}
|
||||
|
||||
const (
|
||||
// ECSDefaultEndpoint is the default API endpoint of ECS services
|
||||
ECSDefaultEndpoint = "https://ecs-cn-hangzhou.aliyuncs.com"
|
||||
ECSAPIVersion = "2014-05-26"
|
||||
|
||||
ECSServiceCode = "ecs"
|
||||
|
||||
VPCDefaultEndpoint = "https://vpc.aliyuncs.com"
|
||||
VPCAPIVersion = "2016-04-28"
|
||||
VPCServiceCode = "vpc"
|
||||
)
|
||||
|
||||
// NewClient creates a new instance of ECS client
|
||||
func NewClient(accessKeyId, accessKeySecret string) *Client {
|
||||
endpoint := os.Getenv("ECS_ENDPOINT")
|
||||
if endpoint == "" {
|
||||
endpoint = ECSDefaultEndpoint
|
||||
}
|
||||
return NewClientWithEndpoint(endpoint, accessKeyId, accessKeySecret)
|
||||
}
|
||||
|
||||
func NewECSClient(accessKeyId, accessKeySecret string, regionID common.Region) *Client {
|
||||
endpoint := os.Getenv("ECS_ENDPOINT")
|
||||
if endpoint == "" {
|
||||
endpoint = ECSDefaultEndpoint
|
||||
}
|
||||
|
||||
return NewClientWithRegion(endpoint, accessKeyId, accessKeySecret, regionID)
|
||||
}
|
||||
|
||||
func NewClientWithRegion(endpoint string, accessKeyId, accessKeySecret string, regionID common.Region) *Client {
|
||||
client := &Client{}
|
||||
client.NewInit(endpoint, ECSAPIVersion, accessKeyId, accessKeySecret, ECSServiceCode, regionID)
|
||||
return client
|
||||
}
|
||||
|
||||
func NewClientWithEndpoint(endpoint string, accessKeyId, accessKeySecret string) *Client {
|
||||
client := &Client{}
|
||||
client.Init(endpoint, ECSAPIVersion, accessKeyId, accessKeySecret)
|
||||
return client
|
||||
}
|
||||
|
||||
func NewVPCClient(accessKeyId, accessKeySecret string, regionID common.Region) *Client {
|
||||
endpoint := os.Getenv("VPC_ENDPOINT")
|
||||
if endpoint == "" {
|
||||
endpoint = VPCDefaultEndpoint
|
||||
}
|
||||
|
||||
return NewVPCClientWithRegion(endpoint, accessKeyId, accessKeySecret, regionID)
|
||||
}
|
||||
|
||||
func NewVPCClientWithRegion(endpoint string, accessKeyId, accessKeySecret string, regionID common.Region) *Client {
|
||||
client := &Client{}
|
||||
client.NewInit(endpoint, VPCAPIVersion, accessKeyId, accessKeySecret, VPCServiceCode, regionID)
|
||||
return client
|
||||
}
|
|
@ -0,0 +1,330 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/util"
|
||||
)
|
||||
|
||||
// Types of disks
|
||||
type DiskType string
|
||||
|
||||
const (
|
||||
DiskTypeAll = DiskType("all") //Default
|
||||
DiskTypeAllSystem = DiskType("system")
|
||||
DiskTypeAllData = DiskType("data")
|
||||
)
|
||||
|
||||
// Categories of disks
|
||||
type DiskCategory string
|
||||
|
||||
const (
|
||||
DiskCategoryAll = DiskCategory("all") //Default
|
||||
DiskCategoryCloud = DiskCategory("cloud")
|
||||
DiskCategoryEphemeral = DiskCategory("ephemeral")
|
||||
DiskCategoryEphemeralSSD = DiskCategory("ephemeral_ssd")
|
||||
DiskCategoryCloudEfficiency = DiskCategory("cloud_efficiency")
|
||||
DiskCategoryCloudSSD = DiskCategory("cloud_ssd")
|
||||
)
|
||||
|
||||
// Status of disks
|
||||
type DiskStatus string
|
||||
|
||||
const (
|
||||
DiskStatusInUse = DiskStatus("In_use")
|
||||
DiskStatusAvailable = DiskStatus("Available")
|
||||
DiskStatusAttaching = DiskStatus("Attaching")
|
||||
DiskStatusDetaching = DiskStatus("Detaching")
|
||||
DiskStatusCreating = DiskStatus("Creating")
|
||||
DiskStatusReIniting = DiskStatus("ReIniting")
|
||||
DiskStatusAll = DiskStatus("All") //Default
|
||||
)
|
||||
|
||||
// Charge type of disks
|
||||
type DiskChargeType string
|
||||
|
||||
const (
|
||||
PrePaid = DiskChargeType("PrePaid")
|
||||
PostPaid = DiskChargeType("PostPaid")
|
||||
)
|
||||
|
||||
// A DescribeDisksArgs defines the arguments to describe disks
|
||||
type DescribeDisksArgs struct {
|
||||
RegionId common.Region
|
||||
ZoneId string
|
||||
DiskIds []string
|
||||
InstanceId string
|
||||
DiskType DiskType //enum for all(default) | system | data
|
||||
Category DiskCategory //enum for all(default) | cloud | ephemeral
|
||||
Status DiskStatus //enum for In_use | Available | Attaching | Detaching | Creating | ReIniting | All(default)
|
||||
SnapshotId string
|
||||
Name string
|
||||
Portable *bool //optional
|
||||
DeleteWithInstance *bool //optional
|
||||
DeleteAutoSnapshot *bool //optional
|
||||
EnableAutoSnapshot *bool //optional
|
||||
DiskChargeType DiskChargeType
|
||||
Tag map[string]string
|
||||
common.Pagination
|
||||
}
|
||||
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&diskitemtype
|
||||
type DiskItemType struct {
|
||||
DiskId string
|
||||
RegionId common.Region
|
||||
ZoneId string
|
||||
DiskName string
|
||||
Description string
|
||||
Type DiskType
|
||||
Category DiskCategory
|
||||
Size int
|
||||
ImageId string
|
||||
SourceSnapshotId string
|
||||
ProductCode string
|
||||
Portable bool
|
||||
Status DiskStatus
|
||||
OperationLocks OperationLocksType
|
||||
InstanceId string
|
||||
Device string
|
||||
DeleteWithInstance bool
|
||||
DeleteAutoSnapshot bool
|
||||
EnableAutoSnapshot bool
|
||||
CreationTime util.ISO6801Time
|
||||
AttachedTime util.ISO6801Time
|
||||
DetachedTime util.ISO6801Time
|
||||
DiskChargeType DiskChargeType
|
||||
}
|
||||
|
||||
type DescribeDisksResponse struct {
|
||||
common.Response
|
||||
common.PaginationResult
|
||||
RegionId common.Region
|
||||
Disks struct {
|
||||
Disk []DiskItemType
|
||||
}
|
||||
}
|
||||
|
||||
// DescribeDisks describes Disks
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/disk&describedisks
|
||||
func (client *Client) DescribeDisks(args *DescribeDisksArgs) (disks []DiskItemType, pagination *common.PaginationResult, err error) {
|
||||
response := DescribeDisksResponse{}
|
||||
|
||||
err = client.Invoke("DescribeDisks", args, &response)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return response.Disks.Disk, &response.PaginationResult, err
|
||||
}
|
||||
|
||||
type CreateDiskArgs struct {
|
||||
RegionId common.Region
|
||||
ZoneId string
|
||||
DiskName string
|
||||
Description string
|
||||
DiskCategory DiskCategory
|
||||
Size int
|
||||
SnapshotId string
|
||||
ClientToken string
|
||||
}
|
||||
|
||||
type CreateDisksResponse struct {
|
||||
common.Response
|
||||
DiskId string
|
||||
}
|
||||
|
||||
// CreateDisk creates a new disk
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/disk&createdisk
|
||||
func (client *Client) CreateDisk(args *CreateDiskArgs) (diskId string, err error) {
|
||||
response := CreateDisksResponse{}
|
||||
err = client.Invoke("CreateDisk", args, &response)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return response.DiskId, err
|
||||
}
|
||||
|
||||
type DeleteDiskArgs struct {
|
||||
DiskId string
|
||||
}
|
||||
|
||||
type DeleteDiskResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// DeleteDisk deletes disk
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/disk&deletedisk
|
||||
func (client *Client) DeleteDisk(diskId string) error {
|
||||
args := DeleteDiskArgs{
|
||||
DiskId: diskId,
|
||||
}
|
||||
response := DeleteDiskResponse{}
|
||||
err := client.Invoke("DeleteDisk", &args, &response)
|
||||
return err
|
||||
}
|
||||
|
||||
type ReInitDiskArgs struct {
|
||||
DiskId string
|
||||
}
|
||||
|
||||
type ReInitDiskResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// ReInitDisk reinitizes disk
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/disk&reinitdisk
|
||||
func (client *Client) ReInitDisk(diskId string) error {
|
||||
args := ReInitDiskArgs{
|
||||
DiskId: diskId,
|
||||
}
|
||||
response := ReInitDiskResponse{}
|
||||
err := client.Invoke("ReInitDisk", &args, &response)
|
||||
return err
|
||||
}
|
||||
|
||||
type AttachDiskArgs struct {
|
||||
InstanceId string
|
||||
DiskId string
|
||||
Device string
|
||||
DeleteWithInstance bool
|
||||
}
|
||||
|
||||
type AttachDiskResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// AttachDisk attaches disk to instance
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/disk&attachdisk
|
||||
func (client *Client) AttachDisk(args *AttachDiskArgs) error {
|
||||
response := AttachDiskResponse{}
|
||||
err := client.Invoke("AttachDisk", args, &response)
|
||||
return err
|
||||
}
|
||||
|
||||
type DetachDiskArgs struct {
|
||||
InstanceId string
|
||||
DiskId string
|
||||
}
|
||||
|
||||
type DetachDiskResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// DetachDisk detaches disk from instance
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/disk&detachdisk
|
||||
func (client *Client) DetachDisk(instanceId string, diskId string) error {
|
||||
args := DetachDiskArgs{
|
||||
InstanceId: instanceId,
|
||||
DiskId: diskId,
|
||||
}
|
||||
response := DetachDiskResponse{}
|
||||
err := client.Invoke("DetachDisk", &args, &response)
|
||||
return err
|
||||
}
|
||||
|
||||
type ResetDiskArgs struct {
|
||||
DiskId string
|
||||
SnapshotId string
|
||||
}
|
||||
|
||||
type ResetDiskResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// ResetDisk resets disk to original status
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/disk&resetdisk
|
||||
func (client *Client) ResetDisk(diskId string, snapshotId string) error {
|
||||
args := ResetDiskArgs{
|
||||
SnapshotId: snapshotId,
|
||||
DiskId: diskId,
|
||||
}
|
||||
response := ResetDiskResponse{}
|
||||
err := client.Invoke("ResetDisk", &args, &response)
|
||||
return err
|
||||
}
|
||||
|
||||
type ModifyDiskAttributeArgs struct {
|
||||
DiskId string
|
||||
DiskName string
|
||||
Description string
|
||||
DeleteWithInstance *bool
|
||||
DeleteAutoSnapshot *bool
|
||||
EnableAutoSnapshot *bool
|
||||
}
|
||||
|
||||
type ModifyDiskAttributeResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// ModifyDiskAttribute modifies disk attribute
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/disk&modifydiskattribute
|
||||
func (client *Client) ModifyDiskAttribute(args *ModifyDiskAttributeArgs) error {
|
||||
response := ModifyDiskAttributeResponse{}
|
||||
err := client.Invoke("ModifyDiskAttribute", args, &response)
|
||||
return err
|
||||
}
|
||||
|
||||
type ReplaceSystemDiskArgs struct {
|
||||
InstanceId string
|
||||
ImageId string
|
||||
SystemDisk SystemDiskType
|
||||
ClientToken string
|
||||
}
|
||||
|
||||
type ReplaceSystemDiskResponse struct {
|
||||
common.Response
|
||||
DiskId string
|
||||
}
|
||||
|
||||
// ReplaceSystemDisk replace system disk
|
||||
//
|
||||
// You can read doc at https://help.aliyun.com/document_detail/ecs/open-api/disk/replacesystemdisk.html
|
||||
func (client *Client) ReplaceSystemDisk(args *ReplaceSystemDiskArgs) (diskId string, err error) {
|
||||
response := ReplaceSystemDiskResponse{}
|
||||
err = client.Invoke("ReplaceSystemDisk", args, &response)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return response.DiskId, nil
|
||||
}
|
||||
|
||||
// WaitForDisk waits for disk to given status
|
||||
func (client *Client) WaitForDisk(regionId common.Region, diskId string, status DiskStatus, timeout int) error {
|
||||
if timeout <= 0 {
|
||||
timeout = DefaultTimeout
|
||||
}
|
||||
args := DescribeDisksArgs{
|
||||
RegionId: regionId,
|
||||
DiskIds: []string{diskId},
|
||||
}
|
||||
|
||||
for {
|
||||
disks, _, err := client.DescribeDisks(&args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if disks == nil || len(disks) == 0 {
|
||||
return common.GetClientErrorFromString("Not found")
|
||||
}
|
||||
if disks[0].Status == status {
|
||||
break
|
||||
}
|
||||
timeout = timeout - DefaultWaitForInterval
|
||||
if timeout <= 0 {
|
||||
return common.GetClientErrorFromString("Timeout")
|
||||
}
|
||||
time.Sleep(DefaultWaitForInterval * time.Second)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
package ecs
|
||||
|
||||
import "github.com/denverdino/aliyungo/common"
|
||||
|
||||
type CreateForwardEntryArgs struct {
|
||||
RegionId common.Region
|
||||
ForwardTableId string
|
||||
ExternalIp string
|
||||
ExternalPort string
|
||||
IpProtocol string
|
||||
InternalIp string
|
||||
InternalPort string
|
||||
}
|
||||
|
||||
type CreateForwardEntryResponse struct {
|
||||
common.Response
|
||||
ForwardEntryId string
|
||||
}
|
||||
|
||||
type DescribeForwardTableEntriesArgs struct {
|
||||
RegionId common.Region
|
||||
ForwardTableId string
|
||||
common.Pagination
|
||||
}
|
||||
|
||||
type ForwardTableEntrySetType struct {
|
||||
RegionId common.Region
|
||||
ExternalIp string
|
||||
ExternalPort string
|
||||
ForwardEntryId string
|
||||
ForwardTableId string
|
||||
InternalIp string
|
||||
InternalPort string
|
||||
IpProtocol string
|
||||
Status string
|
||||
}
|
||||
|
||||
type DescribeForwardTableEntriesResponse struct {
|
||||
common.Response
|
||||
common.PaginationResult
|
||||
ForwardTableEntries struct {
|
||||
ForwardTableEntry []ForwardTableEntrySetType
|
||||
}
|
||||
}
|
||||
|
||||
type ModifyForwardEntryArgs struct {
|
||||
RegionId common.Region
|
||||
ForwardTableId string
|
||||
ForwardEntryId string
|
||||
ExternalIp string
|
||||
IpProtocol string
|
||||
ExternalPort string
|
||||
InternalIp string
|
||||
InternalPort string
|
||||
}
|
||||
|
||||
type ModifyForwardEntryResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
type DeleteForwardEntryArgs struct {
|
||||
RegionId common.Region
|
||||
ForwardTableId string
|
||||
ForwardEntryId string
|
||||
}
|
||||
|
||||
type DeleteForwardEntryResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
func (client *Client) CreateForwardEntry(args *CreateForwardEntryArgs) (resp *CreateForwardEntryResponse, err error) {
|
||||
response := CreateForwardEntryResponse{}
|
||||
err = client.Invoke("CreateForwardEntry", args, &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &response, err
|
||||
}
|
||||
|
||||
func (client *Client) DescribeForwardTableEntries(args *DescribeForwardTableEntriesArgs) (forwardTableEntries []ForwardTableEntrySetType,
|
||||
pagination *common.PaginationResult, err error) {
|
||||
|
||||
args.Validate()
|
||||
response := DescribeForwardTableEntriesResponse{}
|
||||
|
||||
err = client.Invoke("DescribeForwardTableEntries", args, &response)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return response.ForwardTableEntries.ForwardTableEntry, &response.PaginationResult, nil
|
||||
}
|
||||
|
||||
func (client *Client) ModifyForwardEntry(args *ModifyForwardEntryArgs) error {
|
||||
response := ModifyForwardEntryResponse{}
|
||||
return client.Invoke("ModifyForwardEntry", args, &response)
|
||||
}
|
||||
|
||||
func (client *Client) DeleteForwardEntry(args *DeleteForwardEntryArgs) error {
|
||||
response := DeleteForwardEntryResponse{}
|
||||
err := client.Invoke("DeleteForwardEntry", args, &response)
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,317 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/util"
|
||||
)
|
||||
|
||||
// ImageOwnerAlias represents image owner
|
||||
type ImageOwnerAlias string
|
||||
|
||||
// Constants of image owner
|
||||
const (
|
||||
ImageOwnerSystem = ImageOwnerAlias("system")
|
||||
ImageOwnerSelf = ImageOwnerAlias("self")
|
||||
ImageOwnerOthers = ImageOwnerAlias("others")
|
||||
ImageOwnerMarketplace = ImageOwnerAlias("marketplace")
|
||||
ImageOwnerDefault = ImageOwnerAlias("") //Return the values for system, self, and others
|
||||
)
|
||||
|
||||
type ImageStatus string
|
||||
|
||||
const (
|
||||
ImageStatusAvailable = ImageStatus("Available")
|
||||
ImageStatusUnAvailable = ImageStatus("UnAvailable")
|
||||
ImageStatusCreating = ImageStatus("Creating")
|
||||
ImageStatusCreateFailed = ImageStatus("CreateFailed")
|
||||
)
|
||||
|
||||
type ImageUsage string
|
||||
|
||||
const (
|
||||
ImageUsageInstance = ImageUsage("instance")
|
||||
ImageUsageNone = ImageUsage("none")
|
||||
)
|
||||
|
||||
// DescribeImagesArgs repsents arguements to describe images
|
||||
type DescribeImagesArgs struct {
|
||||
RegionId common.Region
|
||||
ImageId string
|
||||
SnapshotId string
|
||||
ImageName string
|
||||
Status ImageStatus
|
||||
ImageOwnerAlias ImageOwnerAlias
|
||||
common.Pagination
|
||||
}
|
||||
|
||||
type DescribeImagesResponse struct {
|
||||
common.Response
|
||||
common.PaginationResult
|
||||
|
||||
RegionId common.Region
|
||||
Images struct {
|
||||
Image []ImageType
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&diskdevicemapping
|
||||
type DiskDeviceMapping struct {
|
||||
SnapshotId string
|
||||
//Why Size Field is string-type.
|
||||
Size string
|
||||
Device string
|
||||
//For import images
|
||||
Format string
|
||||
OSSBucket string
|
||||
OSSObject string
|
||||
}
|
||||
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&imagetype
|
||||
type ImageType struct {
|
||||
ImageId string
|
||||
ImageVersion string
|
||||
Architecture string
|
||||
ImageName string
|
||||
Description string
|
||||
Size int
|
||||
ImageOwnerAlias string
|
||||
OSName string
|
||||
OSType string
|
||||
Platform string
|
||||
DiskDeviceMappings struct {
|
||||
DiskDeviceMapping []DiskDeviceMapping
|
||||
}
|
||||
ProductCode string
|
||||
IsSubscribed bool
|
||||
IsSelfShared string
|
||||
IsCopied bool
|
||||
IsSupportIoOptimized bool
|
||||
Progress string
|
||||
Usage ImageUsage
|
||||
Status ImageStatus
|
||||
CreationTime util.ISO6801Time
|
||||
}
|
||||
|
||||
// DescribeImages describes images
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/image&describeimages
|
||||
func (client *Client) DescribeImages(args *DescribeImagesArgs) (images []ImageType, pagination *common.PaginationResult, err error) {
|
||||
|
||||
args.Validate()
|
||||
response := DescribeImagesResponse{}
|
||||
err = client.Invoke("DescribeImages", args, &response)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return response.Images.Image, &response.PaginationResult, nil
|
||||
}
|
||||
|
||||
// CreateImageArgs repsents arguements to create image
|
||||
type CreateImageArgs struct {
|
||||
RegionId common.Region
|
||||
SnapshotId string
|
||||
InstanceId string
|
||||
ImageName string
|
||||
ImageVersion string
|
||||
Description string
|
||||
ClientToken string
|
||||
}
|
||||
|
||||
type CreateImageResponse struct {
|
||||
common.Response
|
||||
|
||||
ImageId string
|
||||
}
|
||||
|
||||
// CreateImage creates a new image
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/image&createimage
|
||||
func (client *Client) CreateImage(args *CreateImageArgs) (imageId string, err error) {
|
||||
response := &CreateImageResponse{}
|
||||
err = client.Invoke("CreateImage", args, &response)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return response.ImageId, nil
|
||||
}
|
||||
|
||||
type DeleteImageArgs struct {
|
||||
RegionId common.Region
|
||||
ImageId string
|
||||
}
|
||||
|
||||
type DeleteImageResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// DeleteImage deletes Image
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/image&deleteimage
|
||||
func (client *Client) DeleteImage(regionId common.Region, imageId string) error {
|
||||
args := DeleteImageArgs{
|
||||
RegionId: regionId,
|
||||
ImageId: imageId,
|
||||
}
|
||||
|
||||
response := &DeleteImageResponse{}
|
||||
return client.Invoke("DeleteImage", &args, &response)
|
||||
}
|
||||
|
||||
// ModifyImageSharePermission repsents arguements to share image
|
||||
type ModifyImageSharePermissionArgs struct {
|
||||
RegionId common.Region
|
||||
ImageId string
|
||||
AddAccount []string
|
||||
RemoveAccount []string
|
||||
}
|
||||
|
||||
// You can read doc at http://help.aliyun.com/document_detail/ecs/open-api/image/modifyimagesharepermission.html
|
||||
func (client *Client) ModifyImageSharePermission(args *ModifyImageSharePermissionArgs) error {
|
||||
req := url.Values{}
|
||||
req.Add("RegionId", string(args.RegionId))
|
||||
req.Add("ImageId", args.ImageId)
|
||||
|
||||
for i, item := range args.AddAccount {
|
||||
req.Add("AddAccount."+strconv.Itoa(i+1), item)
|
||||
}
|
||||
for i, item := range args.RemoveAccount {
|
||||
req.Add("RemoveAccount."+strconv.Itoa(i+1), item)
|
||||
}
|
||||
|
||||
return client.Invoke("ModifyImageSharePermission", req, &common.Response{})
|
||||
}
|
||||
|
||||
type AccountType struct {
|
||||
AliyunId string
|
||||
}
|
||||
type ImageSharePermissionResponse struct {
|
||||
common.Response
|
||||
ImageId string
|
||||
RegionId string
|
||||
Accounts struct {
|
||||
Account []AccountType
|
||||
}
|
||||
TotalCount int
|
||||
PageNumber int
|
||||
PageSize int
|
||||
}
|
||||
|
||||
func (client *Client) DescribeImageSharePermission(args *ModifyImageSharePermissionArgs) (*ImageSharePermissionResponse, error) {
|
||||
response := ImageSharePermissionResponse{}
|
||||
err := client.Invoke("DescribeImageSharePermission", args, &response)
|
||||
return &response, err
|
||||
}
|
||||
|
||||
type CopyImageArgs struct {
|
||||
RegionId common.Region
|
||||
ImageId string
|
||||
DestinationRegionId common.Region
|
||||
DestinationImageName string
|
||||
DestinationDescription string
|
||||
ClientToken string
|
||||
}
|
||||
|
||||
type CopyImageResponse struct {
|
||||
common.Response
|
||||
ImageId string
|
||||
}
|
||||
|
||||
// You can read doc at https://help.aliyun.com/document_detail/25538.html
|
||||
func (client *Client) CopyImage(args *CopyImageArgs) (string, error) {
|
||||
response := &CopyImageResponse{}
|
||||
err := client.Invoke("CopyImage", args, &response)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return response.ImageId, nil
|
||||
}
|
||||
|
||||
|
||||
// ImportImageArgs repsents arguements to import image from oss
|
||||
type ImportImageArgs struct {
|
||||
RegionId common.Region
|
||||
ImageName string
|
||||
ImageVersion string
|
||||
Description string
|
||||
ClientToken string
|
||||
Architecture string
|
||||
OSType string
|
||||
Platform string
|
||||
DiskDeviceMappings struct {
|
||||
DiskDeviceMapping []DiskDeviceMapping
|
||||
}
|
||||
}
|
||||
|
||||
func (client *Client) ImportImage(args *ImportImageArgs) (string, error) {
|
||||
response := &CopyImageResponse{}
|
||||
err := client.Invoke("ImportImage", args, &response)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return response.ImageId, nil
|
||||
}
|
||||
|
||||
type ImportImageResponse struct {
|
||||
common.Response
|
||||
RegionId common.Region
|
||||
ImageId string
|
||||
ImportTaskId string
|
||||
}
|
||||
|
||||
// Default timeout value for WaitForImageReady method
|
||||
const ImageDefaultTimeout = 120
|
||||
|
||||
//Wait Image ready
|
||||
func (client *Client) WaitForImageReady(regionId common.Region, imageId string, timeout int) error {
|
||||
if timeout <= 0 {
|
||||
timeout = ImageDefaultTimeout
|
||||
}
|
||||
for {
|
||||
args := DescribeImagesArgs{
|
||||
RegionId: regionId,
|
||||
ImageId: imageId,
|
||||
Status: ImageStatusCreating,
|
||||
}
|
||||
|
||||
images, _, err := client.DescribeImages(&args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if images == nil || len(images) == 0 {
|
||||
args.Status = ImageStatusAvailable
|
||||
images, _, er := client.DescribeImages(&args)
|
||||
if er == nil && len(images) == 1 {
|
||||
break
|
||||
} else {
|
||||
return common.GetClientErrorFromString("Not found")
|
||||
}
|
||||
}
|
||||
if images[0].Progress == "100%" {
|
||||
break
|
||||
}
|
||||
timeout = timeout - DefaultWaitForInterval
|
||||
if timeout <= 0 {
|
||||
return common.GetClientErrorFromString("Timeout")
|
||||
}
|
||||
time.Sleep(DefaultWaitForInterval * time.Second)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type CancelCopyImageRequest struct {
|
||||
regionId common.Region
|
||||
ImageId string
|
||||
}
|
||||
|
||||
// You can read doc at https://help.aliyun.com/document_detail/25539.html
|
||||
func (client *Client) CancelCopyImage(regionId common.Region, imageId string) error {
|
||||
response := &common.Response{}
|
||||
err := client.Invoke("CancelCopyImage", &CancelCopyImageRequest{regionId, imageId}, &response)
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
package ecs
|
||||
|
||||
import "github.com/denverdino/aliyungo/common"
|
||||
|
||||
type DescribeInstanceTypesArgs struct {
|
||||
InstanceTypeFamily string
|
||||
}
|
||||
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&instancetypeitemtype
|
||||
type InstanceTypeItemType struct {
|
||||
InstanceTypeId string
|
||||
CpuCoreCount int
|
||||
MemorySize float64
|
||||
InstanceTypeFamily string
|
||||
}
|
||||
|
||||
type DescribeInstanceTypesResponse struct {
|
||||
common.Response
|
||||
InstanceTypes struct {
|
||||
InstanceType []InstanceTypeItemType
|
||||
}
|
||||
}
|
||||
|
||||
// DescribeInstanceTypes describes all instance types
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/other&describeinstancetypes
|
||||
func (client *Client) DescribeInstanceTypes() (instanceTypes []InstanceTypeItemType, err error) {
|
||||
response := DescribeInstanceTypesResponse{}
|
||||
|
||||
err = client.Invoke("DescribeInstanceTypes", &DescribeInstanceTypesArgs{}, &response)
|
||||
|
||||
if err != nil {
|
||||
return []InstanceTypeItemType{}, err
|
||||
}
|
||||
return response.InstanceTypes.InstanceType, nil
|
||||
|
||||
}
|
||||
|
||||
// support user args
|
||||
func (client *Client) DescribeInstanceTypesNew(args *DescribeInstanceTypesArgs) (instanceTypes []InstanceTypeItemType, err error) {
|
||||
response := DescribeInstanceTypesResponse{}
|
||||
|
||||
err = client.Invoke("DescribeInstanceTypes", args, &response)
|
||||
|
||||
if err != nil {
|
||||
return []InstanceTypeItemType{}, err
|
||||
}
|
||||
return response.InstanceTypes.InstanceType, nil
|
||||
|
||||
}
|
||||
|
||||
type DescribeInstanceTypeFamiliesArgs struct {
|
||||
RegionId common.Region
|
||||
Generation string
|
||||
}
|
||||
|
||||
type InstanceTypeFamilies struct {
|
||||
InstanceTypeFamily []InstanceTypeFamily
|
||||
}
|
||||
|
||||
type InstanceTypeFamily struct {
|
||||
InstanceTypeFamilyId string
|
||||
Generation string
|
||||
}
|
||||
|
||||
type DescribeInstanceTypeFamiliesResponse struct {
|
||||
common.Response
|
||||
|
||||
InstanceTypeFamilies InstanceTypeFamilies
|
||||
}
|
||||
|
||||
func (client *Client) DescribeInstanceTypeFamilies(args *DescribeInstanceTypeFamiliesArgs) (*DescribeInstanceTypeFamiliesResponse, error) {
|
||||
response := &DescribeInstanceTypeFamiliesResponse{}
|
||||
|
||||
err := client.Invoke("DescribeInstanceTypeFamilies", args, response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
|
@ -0,0 +1,607 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/util"
|
||||
)
|
||||
|
||||
// InstanceStatus represents instance status
|
||||
type InstanceStatus string
|
||||
|
||||
// Constants of InstanceStatus
|
||||
const (
|
||||
Creating = InstanceStatus("Creating") // For backward compatability
|
||||
Pending = InstanceStatus("Pending")
|
||||
Running = InstanceStatus("Running")
|
||||
Starting = InstanceStatus("Starting")
|
||||
|
||||
Stopped = InstanceStatus("Stopped")
|
||||
Stopping = InstanceStatus("Stopping")
|
||||
Deleted = InstanceStatus("Deleted")
|
||||
)
|
||||
|
||||
type LockReason string
|
||||
|
||||
const (
|
||||
LockReasonFinancial = LockReason("financial")
|
||||
LockReasonSecurity = LockReason("security")
|
||||
)
|
||||
|
||||
type LockReasonType struct {
|
||||
LockReason LockReason
|
||||
}
|
||||
|
||||
type DescribeUserdataArgs struct {
|
||||
RegionId common.Region
|
||||
InstanceId string
|
||||
}
|
||||
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&instancestatusitemtype
|
||||
type DescribeUserdataItemType struct {
|
||||
UserData string
|
||||
InstanceId string
|
||||
RegionId string
|
||||
}
|
||||
|
||||
type DescribeUserdataResponse struct {
|
||||
common.Response
|
||||
DescribeUserdataItemType
|
||||
}
|
||||
|
||||
// DescribeInstanceStatus describes instance status
|
||||
//
|
||||
// You can read doc at https://intl.aliyun.com/help/doc-detail/49227.htm
|
||||
func (client *Client) DescribeUserdata(args *DescribeUserdataArgs) (userData *DescribeUserdataItemType, err error) {
|
||||
response := DescribeUserdataResponse{}
|
||||
|
||||
err = client.Invoke("DescribeUserdata", args, &response)
|
||||
|
||||
if err == nil {
|
||||
return &response.DescribeUserdataItemType, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
type DescribeInstanceStatusArgs struct {
|
||||
RegionId common.Region
|
||||
ZoneId string
|
||||
common.Pagination
|
||||
}
|
||||
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&instancestatusitemtype
|
||||
type InstanceStatusItemType struct {
|
||||
InstanceId string
|
||||
Status InstanceStatus
|
||||
}
|
||||
|
||||
type DescribeInstanceStatusResponse struct {
|
||||
common.Response
|
||||
common.PaginationResult
|
||||
InstanceStatuses struct {
|
||||
InstanceStatus []InstanceStatusItemType
|
||||
}
|
||||
}
|
||||
|
||||
// DescribeInstanceStatus describes instance status
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/instance&describeinstancestatus
|
||||
func (client *Client) DescribeInstanceStatus(args *DescribeInstanceStatusArgs) (instanceStatuses []InstanceStatusItemType, pagination *common.PaginationResult, err error) {
|
||||
args.Validate()
|
||||
response := DescribeInstanceStatusResponse{}
|
||||
|
||||
err = client.Invoke("DescribeInstanceStatus", args, &response)
|
||||
|
||||
if err == nil {
|
||||
return response.InstanceStatuses.InstanceStatus, &response.PaginationResult, nil
|
||||
}
|
||||
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
type StopInstanceArgs struct {
|
||||
InstanceId string
|
||||
ForceStop bool
|
||||
}
|
||||
|
||||
type StopInstanceResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// StopInstance stops instance
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/instance&stopinstance
|
||||
func (client *Client) StopInstance(instanceId string, forceStop bool) error {
|
||||
args := StopInstanceArgs{
|
||||
InstanceId: instanceId,
|
||||
ForceStop: forceStop,
|
||||
}
|
||||
response := StopInstanceResponse{}
|
||||
err := client.Invoke("StopInstance", &args, &response)
|
||||
return err
|
||||
}
|
||||
|
||||
type StartInstanceArgs struct {
|
||||
InstanceId string
|
||||
}
|
||||
|
||||
type StartInstanceResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// StartInstance starts instance
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/instance&startinstance
|
||||
func (client *Client) StartInstance(instanceId string) error {
|
||||
args := StartInstanceArgs{InstanceId: instanceId}
|
||||
response := StartInstanceResponse{}
|
||||
err := client.Invoke("StartInstance", &args, &response)
|
||||
return err
|
||||
}
|
||||
|
||||
type RebootInstanceArgs struct {
|
||||
InstanceId string
|
||||
ForceStop bool
|
||||
}
|
||||
|
||||
type RebootInstanceResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// RebootInstance reboot instance
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/instance&rebootinstance
|
||||
func (client *Client) RebootInstance(instanceId string, forceStop bool) error {
|
||||
request := RebootInstanceArgs{
|
||||
InstanceId: instanceId,
|
||||
ForceStop: forceStop,
|
||||
}
|
||||
response := RebootInstanceResponse{}
|
||||
err := client.Invoke("RebootInstance", &request, &response)
|
||||
return err
|
||||
}
|
||||
|
||||
type DescribeInstanceAttributeArgs struct {
|
||||
InstanceId string
|
||||
}
|
||||
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&operationlockstype
|
||||
type OperationLocksType struct {
|
||||
LockReason []LockReasonType //enum for financial, security
|
||||
}
|
||||
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&securitygroupidsettype
|
||||
type SecurityGroupIdSetType struct {
|
||||
SecurityGroupId string
|
||||
}
|
||||
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&ipaddresssettype
|
||||
type IpAddressSetType struct {
|
||||
IpAddress []string
|
||||
}
|
||||
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&vpcattributestype
|
||||
type VpcAttributesType struct {
|
||||
VpcId string
|
||||
VSwitchId string
|
||||
PrivateIpAddress IpAddressSetType
|
||||
NatIpAddress string
|
||||
}
|
||||
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&eipaddressassociatetype
|
||||
type EipAddressAssociateType struct {
|
||||
AllocationId string
|
||||
IpAddress string
|
||||
Bandwidth int
|
||||
InternetChargeType common.InternetChargeType
|
||||
}
|
||||
|
||||
// Experimental feature
|
||||
type SpotStrategyType string
|
||||
|
||||
// Constants of SpotStrategyType
|
||||
const (
|
||||
NoSpot = SpotStrategyType("NoSpot")
|
||||
SpotWithPriceLimit = SpotStrategyType("SpotWithPriceLimit")
|
||||
)
|
||||
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&instanceattributestype
|
||||
type InstanceAttributesType struct {
|
||||
InstanceId string
|
||||
InstanceName string
|
||||
Description string
|
||||
ImageId string
|
||||
RegionId common.Region
|
||||
ZoneId string
|
||||
CPU int
|
||||
Memory int
|
||||
ClusterId string
|
||||
InstanceType string
|
||||
InstanceTypeFamily string
|
||||
HostName string
|
||||
SerialNumber string
|
||||
Status InstanceStatus
|
||||
OperationLocks OperationLocksType
|
||||
SecurityGroupIds struct {
|
||||
SecurityGroupId []string
|
||||
}
|
||||
PublicIpAddress IpAddressSetType
|
||||
InnerIpAddress IpAddressSetType
|
||||
InstanceNetworkType string //enum Classic | Vpc
|
||||
InternetMaxBandwidthIn int
|
||||
InternetMaxBandwidthOut int
|
||||
InternetChargeType common.InternetChargeType
|
||||
CreationTime util.ISO6801Time //time.Time
|
||||
VpcAttributes VpcAttributesType
|
||||
EipAddress EipAddressAssociateType
|
||||
IoOptimized StringOrBool
|
||||
InstanceChargeType common.InstanceChargeType
|
||||
ExpiredTime util.ISO6801Time
|
||||
Tags struct {
|
||||
Tag []TagItemType
|
||||
}
|
||||
SpotStrategy SpotStrategyType
|
||||
}
|
||||
|
||||
type DescribeInstanceAttributeResponse struct {
|
||||
common.Response
|
||||
InstanceAttributesType
|
||||
}
|
||||
|
||||
// DescribeInstanceAttribute describes instance attribute
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/instance&describeinstanceattribute
|
||||
func (client *Client) DescribeInstanceAttribute(instanceId string) (instance *InstanceAttributesType, err error) {
|
||||
args := DescribeInstanceAttributeArgs{InstanceId: instanceId}
|
||||
|
||||
response := DescribeInstanceAttributeResponse{}
|
||||
err = client.Invoke("DescribeInstanceAttribute", &args, &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &response.InstanceAttributesType, err
|
||||
}
|
||||
|
||||
type ModifyInstanceAttributeArgs struct {
|
||||
InstanceId string
|
||||
InstanceName string
|
||||
Description string
|
||||
Password string
|
||||
HostName string
|
||||
UserData string
|
||||
}
|
||||
|
||||
type ModifyInstanceAttributeResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
//ModifyInstanceAttribute modify instance attrbute
|
||||
//
|
||||
// You can read doc at https://help.aliyun.com/document_detail/ecs/open-api/instance/modifyinstanceattribute.html
|
||||
func (client *Client) ModifyInstanceAttribute(args *ModifyInstanceAttributeArgs) error {
|
||||
response := ModifyInstanceAttributeResponse{}
|
||||
err := client.Invoke("ModifyInstanceAttribute", args, &response)
|
||||
return err
|
||||
}
|
||||
|
||||
// Default timeout value for WaitForInstance method
|
||||
const InstanceDefaultTimeout = 120
|
||||
|
||||
// WaitForInstance waits for instance to given status
|
||||
func (client *Client) WaitForInstance(instanceId string, status InstanceStatus, timeout int) error {
|
||||
if timeout <= 0 {
|
||||
timeout = InstanceDefaultTimeout
|
||||
}
|
||||
for {
|
||||
instance, err := client.DescribeInstanceAttribute(instanceId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if instance.Status == status {
|
||||
//TODO
|
||||
//Sleep one more time for timing issues
|
||||
time.Sleep(DefaultWaitForInterval * time.Second)
|
||||
break
|
||||
}
|
||||
timeout = timeout - DefaultWaitForInterval
|
||||
if timeout <= 0 {
|
||||
return common.GetClientErrorFromString("Timeout")
|
||||
}
|
||||
time.Sleep(DefaultWaitForInterval * time.Second)
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WaitForInstance waits for instance to given status
|
||||
// when instance.NotFound wait until timeout
|
||||
func (client *Client) WaitForInstanceAsyn(instanceId string, status InstanceStatus, timeout int) error {
|
||||
if timeout <= 0 {
|
||||
timeout = InstanceDefaultTimeout
|
||||
}
|
||||
for {
|
||||
instance, err := client.DescribeInstanceAttribute(instanceId)
|
||||
if err != nil {
|
||||
e, _ := err.(*common.Error)
|
||||
if e.Code != "InvalidInstanceId.NotFound" && e.Code != "Forbidden.InstanceNotFound" {
|
||||
return err
|
||||
}
|
||||
} else if instance != nil && instance.Status == status {
|
||||
//TODO
|
||||
break
|
||||
}
|
||||
timeout = timeout - DefaultWaitForInterval
|
||||
if timeout <= 0 {
|
||||
return common.GetClientErrorFromString("Timeout")
|
||||
}
|
||||
time.Sleep(DefaultWaitForInterval * time.Second)
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type DescribeInstanceVncUrlArgs struct {
|
||||
RegionId common.Region
|
||||
InstanceId string
|
||||
}
|
||||
|
||||
type DescribeInstanceVncUrlResponse struct {
|
||||
common.Response
|
||||
VncUrl string
|
||||
}
|
||||
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/instance&describeinstancevncurl
|
||||
func (client *Client) DescribeInstanceVncUrl(args *DescribeInstanceVncUrlArgs) (string, error) {
|
||||
response := DescribeInstanceVncUrlResponse{}
|
||||
|
||||
err := client.Invoke("DescribeInstanceVncUrl", args, &response)
|
||||
|
||||
if err == nil {
|
||||
return response.VncUrl, nil
|
||||
}
|
||||
|
||||
return "", err
|
||||
}
|
||||
|
||||
type DescribeInstancesArgs struct {
|
||||
RegionId common.Region
|
||||
VpcId string
|
||||
VSwitchId string
|
||||
ZoneId string
|
||||
InstanceIds string
|
||||
InstanceNetworkType string
|
||||
InstanceName string
|
||||
Status InstanceStatus
|
||||
PrivateIpAddresses string
|
||||
InnerIpAddresses string
|
||||
PublicIpAddresses string
|
||||
SecurityGroupId string
|
||||
Tag map[string]string
|
||||
InstanceType string
|
||||
SpotStrategy SpotStrategyType
|
||||
common.Pagination
|
||||
}
|
||||
|
||||
type DescribeInstancesResponse struct {
|
||||
common.Response
|
||||
common.PaginationResult
|
||||
Instances struct {
|
||||
Instance []InstanceAttributesType
|
||||
}
|
||||
}
|
||||
|
||||
// DescribeInstances describes instances
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/instance&describeinstances
|
||||
func (client *Client) DescribeInstances(args *DescribeInstancesArgs) (instances []InstanceAttributesType, pagination *common.PaginationResult, err error) {
|
||||
args.Validate()
|
||||
response := DescribeInstancesResponse{}
|
||||
|
||||
err = client.Invoke("DescribeInstances", args, &response)
|
||||
|
||||
if err == nil {
|
||||
return response.Instances.Instance, &response.PaginationResult, nil
|
||||
}
|
||||
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
type DeleteInstanceArgs struct {
|
||||
InstanceId string
|
||||
}
|
||||
|
||||
type DeleteInstanceResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// DeleteInstance deletes instance
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/instance&deleteinstance
|
||||
func (client *Client) DeleteInstance(instanceId string) error {
|
||||
args := DeleteInstanceArgs{InstanceId: instanceId}
|
||||
response := DeleteInstanceResponse{}
|
||||
err := client.Invoke("DeleteInstance", &args, &response)
|
||||
return err
|
||||
}
|
||||
|
||||
type DataDiskType struct {
|
||||
Size int
|
||||
Category DiskCategory //Enum cloud, ephemeral, ephemeral_ssd
|
||||
SnapshotId string
|
||||
DiskName string
|
||||
Description string
|
||||
Device string
|
||||
DeleteWithInstance bool
|
||||
}
|
||||
|
||||
type SystemDiskType struct {
|
||||
Size int
|
||||
Category DiskCategory //Enum cloud, ephemeral, ephemeral_ssd
|
||||
DiskName string
|
||||
Description string
|
||||
}
|
||||
|
||||
type IoOptimized string
|
||||
|
||||
type StringOrBool struct {
|
||||
Value bool
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaller interface.
|
||||
func (io *StringOrBool) UnmarshalJSON(value []byte) error {
|
||||
if value[0] == '"' {
|
||||
var str string
|
||||
err := json.Unmarshal(value, &str)
|
||||
if err == nil {
|
||||
io.Value = (str == "true" || str == "optimized")
|
||||
}
|
||||
return err
|
||||
}
|
||||
var boolVal bool
|
||||
err := json.Unmarshal(value, &boolVal)
|
||||
if err == nil {
|
||||
io.Value = boolVal
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (io StringOrBool) Bool() bool {
|
||||
return io.Value
|
||||
}
|
||||
|
||||
func (io StringOrBool) String() string {
|
||||
return strconv.FormatBool(io.Value)
|
||||
}
|
||||
|
||||
var (
|
||||
IoOptimizedNone = IoOptimized("none")
|
||||
IoOptimizedOptimized = IoOptimized("optimized")
|
||||
)
|
||||
|
||||
type CreateInstanceArgs struct {
|
||||
RegionId common.Region
|
||||
ZoneId string
|
||||
ImageId string
|
||||
InstanceType string
|
||||
SecurityGroupId string
|
||||
InstanceName string
|
||||
Description string
|
||||
InternetChargeType common.InternetChargeType
|
||||
InternetMaxBandwidthIn int
|
||||
InternetMaxBandwidthOut int
|
||||
HostName string
|
||||
Password string
|
||||
IoOptimized IoOptimized
|
||||
SystemDisk SystemDiskType
|
||||
DataDisk []DataDiskType
|
||||
VSwitchId string
|
||||
PrivateIpAddress string
|
||||
ClientToken string
|
||||
InstanceChargeType common.InstanceChargeType
|
||||
Period int
|
||||
UserData string
|
||||
AutoRenew bool
|
||||
AutoRenewPeriod int
|
||||
SpotStrategy SpotStrategyType
|
||||
}
|
||||
|
||||
type CreateInstanceResponse struct {
|
||||
common.Response
|
||||
InstanceId string
|
||||
}
|
||||
|
||||
// CreateInstance creates instance
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/instance&createinstance
|
||||
func (client *Client) CreateInstance(args *CreateInstanceArgs) (instanceId string, err error) {
|
||||
if args.UserData != "" {
|
||||
// Encode to base64 string
|
||||
args.UserData = base64.StdEncoding.EncodeToString([]byte(args.UserData))
|
||||
}
|
||||
response := CreateInstanceResponse{}
|
||||
err = client.Invoke("CreateInstance", args, &response)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return response.InstanceId, err
|
||||
}
|
||||
|
||||
type RunInstanceArgs struct {
|
||||
CreateInstanceArgs
|
||||
MinAmount int
|
||||
MaxAmount int
|
||||
AutoReleaseTime string
|
||||
NetworkType string
|
||||
InnerIpAddress string
|
||||
BusinessInfo string
|
||||
}
|
||||
|
||||
type RunInstanceResponse struct {
|
||||
common.Response
|
||||
InstanceIdSets InstanceIdSets
|
||||
}
|
||||
|
||||
type InstanceIdSets struct {
|
||||
InstanceIdSet []string
|
||||
}
|
||||
|
||||
type BusinessInfo struct {
|
||||
Pack string `json:"pack,omitempty"`
|
||||
ActivityId string `json:"activityId,omitempty"`
|
||||
}
|
||||
|
||||
func (client *Client) RunInstances(args *RunInstanceArgs) (instanceIdSet []string, err error) {
|
||||
if args.UserData != "" {
|
||||
// Encode to base64 string
|
||||
args.UserData = base64.StdEncoding.EncodeToString([]byte(args.UserData))
|
||||
}
|
||||
response := RunInstanceResponse{}
|
||||
err = client.Invoke("RunInstances", args, &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response.InstanceIdSets.InstanceIdSet, err
|
||||
}
|
||||
|
||||
type SecurityGroupArgs struct {
|
||||
InstanceId string
|
||||
SecurityGroupId string
|
||||
}
|
||||
|
||||
type SecurityGroupResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
//JoinSecurityGroup
|
||||
//
|
||||
//You can read doc at https://help.aliyun.com/document_detail/ecs/open-api/instance/joinsecuritygroup.html
|
||||
func (client *Client) JoinSecurityGroup(instanceId string, securityGroupId string) error {
|
||||
args := SecurityGroupArgs{InstanceId: instanceId, SecurityGroupId: securityGroupId}
|
||||
response := SecurityGroupResponse{}
|
||||
err := client.Invoke("JoinSecurityGroup", &args, &response)
|
||||
return err
|
||||
}
|
||||
|
||||
//LeaveSecurityGroup
|
||||
//
|
||||
//You can read doc at https://help.aliyun.com/document_detail/ecs/open-api/instance/leavesecuritygroup.html
|
||||
func (client *Client) LeaveSecurityGroup(instanceId string, securityGroupId string) error {
|
||||
args := SecurityGroupArgs{InstanceId: instanceId, SecurityGroupId: securityGroupId}
|
||||
response := SecurityGroupResponse{}
|
||||
err := client.Invoke("LeaveSecurityGroup", &args, &response)
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/util"
|
||||
)
|
||||
|
||||
type DescribeInstanceMonitorDataArgs struct {
|
||||
InstanceId string
|
||||
StartTime util.ISO6801Time
|
||||
EndTime util.ISO6801Time
|
||||
Period int //Default 60s
|
||||
}
|
||||
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&instancemonitordatatype
|
||||
type InstanceMonitorDataType struct {
|
||||
InstanceId string
|
||||
CPU int
|
||||
IntranetRX int
|
||||
IntranetTX int
|
||||
IntranetBandwidth int
|
||||
InternetRX int
|
||||
InternetTX int
|
||||
InternetBandwidth int
|
||||
IOPSRead int
|
||||
IOPSWrite int
|
||||
BPSRead int
|
||||
BPSWrite int
|
||||
TimeStamp util.ISO6801Time
|
||||
}
|
||||
|
||||
type DescribeInstanceMonitorDataResponse struct {
|
||||
common.Response
|
||||
MonitorData struct {
|
||||
InstanceMonitorData []InstanceMonitorDataType
|
||||
}
|
||||
}
|
||||
|
||||
// DescribeInstanceMonitorData describes instance monitoring data
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/monitor&describeinstancemonitordata
|
||||
func (client *Client) DescribeInstanceMonitorData(args *DescribeInstanceMonitorDataArgs) (monitorData []InstanceMonitorDataType, err error) {
|
||||
if args.Period == 0 {
|
||||
args.Period = 60
|
||||
}
|
||||
response := DescribeInstanceMonitorDataResponse{}
|
||||
err = client.Invoke("DescribeInstanceMonitorData", args, &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response.MonitorData.InstanceMonitorData, err
|
||||
}
|
||||
|
||||
type DescribeEipMonitorDataArgs struct {
|
||||
AllocationId string
|
||||
StartTime util.ISO6801Time
|
||||
EndTime util.ISO6801Time
|
||||
Period int //Default 60s
|
||||
}
|
||||
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&eipmonitordatatype
|
||||
type EipMonitorDataType struct {
|
||||
EipRX int
|
||||
EipTX int
|
||||
EipFlow int
|
||||
EipBandwidth int
|
||||
EipPackets int
|
||||
TimeStamp util.ISO6801Time
|
||||
}
|
||||
|
||||
type DescribeEipMonitorDataResponse struct {
|
||||
common.Response
|
||||
EipMonitorDatas struct {
|
||||
EipMonitorData []EipMonitorDataType
|
||||
}
|
||||
}
|
||||
|
||||
// DescribeEipMonitorData describes EIP monitoring data
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/monitor&describeeipmonitordata
|
||||
func (client *Client) DescribeEipMonitorData(args *DescribeEipMonitorDataArgs) (monitorData []EipMonitorDataType, err error) {
|
||||
if args.Period == 0 {
|
||||
args.Period = 60
|
||||
}
|
||||
response := DescribeEipMonitorDataResponse{}
|
||||
err = client.Invoke("DescribeEipMonitorData", args, &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response.EipMonitorDatas.EipMonitorData, err
|
||||
}
|
||||
|
||||
type DescribeDiskMonitorDataArgs struct {
|
||||
DiskId string
|
||||
StartTime util.ISO6801Time
|
||||
EndTime util.ISO6801Time
|
||||
Period int //Default 60s
|
||||
}
|
||||
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&diskmonitordatatype
|
||||
type DiskMonitorDataType struct {
|
||||
DiskId string
|
||||
IOPSRead int
|
||||
IOPSWrite int
|
||||
IOPSTotal int
|
||||
BPSRead int
|
||||
BPSWrite int
|
||||
BPSTotal int
|
||||
TimeStamp util.ISO6801Time
|
||||
}
|
||||
|
||||
type DescribeDiskMonitorDataResponse struct {
|
||||
common.Response
|
||||
TotalCount int
|
||||
MonitorData struct {
|
||||
DiskMonitorData []DiskMonitorDataType
|
||||
}
|
||||
}
|
||||
|
||||
// DescribeDiskMonitorData describes disk monitoring data
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/monitor&describediskmonitordata
|
||||
func (client *Client) DescribeDiskMonitorData(args *DescribeDiskMonitorDataArgs) (monitorData []DiskMonitorDataType, totalCount int, err error) {
|
||||
if args.Period == 0 {
|
||||
args.Period = 60
|
||||
}
|
||||
response := DescribeDiskMonitorDataResponse{}
|
||||
err = client.Invoke("DescribeDiskMonitorData", args, &response)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return response.MonitorData.DiskMonitorData, response.TotalCount, err
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
)
|
||||
|
||||
type BandwidthPackageType struct {
|
||||
IpCount int
|
||||
Bandwidth int
|
||||
Zone string
|
||||
}
|
||||
|
||||
type CreateNatGatewayArgs struct {
|
||||
RegionId common.Region
|
||||
VpcId string
|
||||
Spec string
|
||||
BandwidthPackage []BandwidthPackageType
|
||||
Name string
|
||||
Description string
|
||||
ClientToken string
|
||||
}
|
||||
|
||||
type ForwardTableIdType struct {
|
||||
ForwardTableId []string
|
||||
}
|
||||
|
||||
type SnatTableIdType struct {
|
||||
SnatTableId []string
|
||||
}
|
||||
|
||||
type BandwidthPackageIdType struct {
|
||||
BandwidthPackageId []string
|
||||
}
|
||||
|
||||
type CreateNatGatewayResponse struct {
|
||||
common.Response
|
||||
NatGatewayId string
|
||||
ForwardTableIds ForwardTableIdType
|
||||
BandwidthPackageIds BandwidthPackageIdType
|
||||
}
|
||||
|
||||
// CreateNatGateway creates Virtual Private Cloud
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/vpc&createvpc
|
||||
func (client *Client) CreateNatGateway(args *CreateNatGatewayArgs) (resp *CreateNatGatewayResponse, err error) {
|
||||
response := CreateNatGatewayResponse{}
|
||||
err = client.Invoke("CreateNatGateway", args, &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &response, err
|
||||
}
|
||||
|
||||
type NatGatewaySetType struct {
|
||||
BusinessStatus string
|
||||
Description string
|
||||
BandwidthPackageIds BandwidthPackageIdType
|
||||
ForwardTableIds ForwardTableIdType
|
||||
SnatTableIds SnatTableIdType
|
||||
InstanceChargeType string
|
||||
Name string
|
||||
NatGatewayId string
|
||||
RegionId common.Region
|
||||
Spec string
|
||||
Status string
|
||||
VpcId string
|
||||
}
|
||||
|
||||
type DescribeNatGatewayResponse struct {
|
||||
common.Response
|
||||
common.PaginationResult
|
||||
NatGateways struct {
|
||||
NatGateway []NatGatewaySetType
|
||||
}
|
||||
}
|
||||
|
||||
type DescribeNatGatewaysArgs struct {
|
||||
RegionId common.Region
|
||||
NatGatewayId string
|
||||
VpcId string
|
||||
common.Pagination
|
||||
}
|
||||
|
||||
func (client *Client) DescribeNatGateways(args *DescribeNatGatewaysArgs) (natGateways []NatGatewaySetType,
|
||||
pagination *common.PaginationResult, err error) {
|
||||
|
||||
args.Validate()
|
||||
response := DescribeNatGatewayResponse{}
|
||||
|
||||
err = client.Invoke("DescribeNatGateways", args, &response)
|
||||
|
||||
if err == nil {
|
||||
return response.NatGateways.NatGateway, &response.PaginationResult, nil
|
||||
}
|
||||
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
type ModifyNatGatewayAttributeArgs struct {
|
||||
RegionId common.Region
|
||||
NatGatewayId string
|
||||
Name string
|
||||
Description string
|
||||
}
|
||||
|
||||
type ModifyNatGatewayAttributeResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
func (client *Client) ModifyNatGatewayAttribute(args *ModifyNatGatewayAttributeArgs) error {
|
||||
response := ModifyNatGatewayAttributeResponse{}
|
||||
return client.Invoke("ModifyNatGatewayAttribute", args, &response)
|
||||
}
|
||||
|
||||
type ModifyNatGatewaySpecArgs struct {
|
||||
RegionId common.Region
|
||||
NatGatewayId string
|
||||
Spec NatGatewaySpec
|
||||
}
|
||||
|
||||
func (client *Client) ModifyNatGatewaySpec(args *ModifyNatGatewaySpecArgs) error {
|
||||
response := ModifyNatGatewayAttributeResponse{}
|
||||
return client.Invoke("ModifyNatGatewaySpec", args, &response)
|
||||
}
|
||||
|
||||
type DeleteNatGatewayArgs struct {
|
||||
RegionId common.Region
|
||||
NatGatewayId string
|
||||
}
|
||||
|
||||
type DeleteNatGatewayResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
func (client *Client) DeleteNatGateway(args *DeleteNatGatewayArgs) error {
|
||||
response := DeleteNatGatewayResponse{}
|
||||
err := client.Invoke("DeleteNatGateway", args, &response)
|
||||
return err
|
||||
}
|
||||
|
||||
type DescribeBandwidthPackagesArgs struct {
|
||||
RegionId common.Region
|
||||
BandwidthPackageId string
|
||||
NatGatewayId string
|
||||
}
|
||||
|
||||
type PublicIpAddresseType struct {
|
||||
AllocationId string
|
||||
IpAddress string
|
||||
}
|
||||
|
||||
type DescribeBandwidthPackageType struct {
|
||||
Bandwidth string
|
||||
BandwidthPackageId string
|
||||
IpCount string
|
||||
PublicIpAddresses struct {
|
||||
PublicIpAddresse []PublicIpAddresseType
|
||||
}
|
||||
|
||||
ZoneId string
|
||||
}
|
||||
|
||||
type DescribeBandwidthPackagesResponse struct {
|
||||
common.Response
|
||||
BandwidthPackages struct {
|
||||
BandwidthPackage []DescribeBandwidthPackageType
|
||||
}
|
||||
}
|
||||
|
||||
func (client *Client) DescribeBandwidthPackages(args *DescribeBandwidthPackagesArgs) ([]DescribeBandwidthPackageType, error) {
|
||||
response := &DescribeBandwidthPackagesResponse{}
|
||||
|
||||
err := client.Invoke("DescribeBandwidthPackages", args, response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return response.BandwidthPackages.BandwidthPackage, err
|
||||
}
|
||||
|
||||
type DeleteBandwidthPackageArgs struct {
|
||||
RegionId common.Region
|
||||
BandwidthPackageId string
|
||||
}
|
||||
|
||||
type DeleteBandwidthPackageResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
func (client *Client) DeleteBandwidthPackage(args *DeleteBandwidthPackageArgs) error {
|
||||
response := DeleteBandwidthPackageResponse{}
|
||||
err := client.Invoke("DeleteBandwidthPackage", args, &response)
|
||||
return err
|
||||
}
|
||||
|
||||
type NatGatewaySpec string
|
||||
|
||||
const (
|
||||
NatGatewaySmallSpec = NatGatewaySpec("Small")
|
||||
NatGatewayMiddleSpec = NatGatewaySpec("Middle")
|
||||
NatGatewayLargeSpec = NatGatewaySpec("Large")
|
||||
)
|
|
@ -0,0 +1,249 @@
|
|||
// API on Network
|
||||
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/util"
|
||||
)
|
||||
|
||||
type AllocatePublicIpAddressArgs struct {
|
||||
InstanceId string
|
||||
}
|
||||
|
||||
type AllocatePublicIpAddressResponse struct {
|
||||
common.Response
|
||||
|
||||
IpAddress string
|
||||
}
|
||||
|
||||
// AllocatePublicIpAddress allocates Public Ip Address
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/network&allocatepublicipaddress
|
||||
func (client *Client) AllocatePublicIpAddress(instanceId string) (ipAddress string, err error) {
|
||||
args := AllocatePublicIpAddressArgs{
|
||||
InstanceId: instanceId,
|
||||
}
|
||||
response := AllocatePublicIpAddressResponse{}
|
||||
err = client.Invoke("AllocatePublicIpAddress", &args, &response)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return response.IpAddress, nil
|
||||
}
|
||||
|
||||
type ModifyInstanceNetworkSpec struct {
|
||||
InstanceId string
|
||||
InternetMaxBandwidthOut *int
|
||||
InternetMaxBandwidthIn *int
|
||||
}
|
||||
|
||||
type ModifyInstanceNetworkSpecResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// ModifyInstanceNetworkSpec modifies instance network spec
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/network&modifyinstancenetworkspec
|
||||
func (client *Client) ModifyInstanceNetworkSpec(args *ModifyInstanceNetworkSpec) error {
|
||||
|
||||
response := ModifyInstanceNetworkSpecResponse{}
|
||||
return client.Invoke("ModifyInstanceNetworkSpec", args, &response)
|
||||
}
|
||||
|
||||
type AllocateEipAddressArgs struct {
|
||||
RegionId common.Region
|
||||
Bandwidth int
|
||||
InternetChargeType common.InternetChargeType
|
||||
ClientToken string
|
||||
}
|
||||
|
||||
type AllocateEipAddressResponse struct {
|
||||
common.Response
|
||||
EipAddress string
|
||||
AllocationId string
|
||||
}
|
||||
|
||||
// AllocateEipAddress allocates Eip Address
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/network&allocateeipaddress
|
||||
func (client *Client) AllocateEipAddress(args *AllocateEipAddressArgs) (EipAddress string, AllocationId string, err error) {
|
||||
if args.Bandwidth == 0 {
|
||||
args.Bandwidth = 5
|
||||
}
|
||||
response := AllocateEipAddressResponse{}
|
||||
err = client.Invoke("AllocateEipAddress", args, &response)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return response.EipAddress, response.AllocationId, nil
|
||||
}
|
||||
|
||||
type AssociateEipAddressArgs struct {
|
||||
AllocationId string
|
||||
InstanceId string
|
||||
}
|
||||
|
||||
type AssociateEipAddressResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// AssociateEipAddress associates EIP address to VM instance
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/network&associateeipaddress
|
||||
func (client *Client) AssociateEipAddress(allocationId string, instanceId string) error {
|
||||
args := AssociateEipAddressArgs{
|
||||
AllocationId: allocationId,
|
||||
InstanceId: instanceId,
|
||||
}
|
||||
response := ModifyInstanceNetworkSpecResponse{}
|
||||
return client.Invoke("AssociateEipAddress", &args, &response)
|
||||
}
|
||||
|
||||
// Status of disks
|
||||
type EipStatus string
|
||||
|
||||
const (
|
||||
EipStatusAssociating = EipStatus("Associating")
|
||||
EipStatusUnassociating = EipStatus("Unassociating")
|
||||
EipStatusInUse = EipStatus("InUse")
|
||||
EipStatusAvailable = EipStatus("Available")
|
||||
)
|
||||
|
||||
type DescribeEipAddressesArgs struct {
|
||||
RegionId common.Region
|
||||
Status EipStatus //enum Associating | Unassociating | InUse | Available
|
||||
EipAddress string
|
||||
AllocationId string
|
||||
common.Pagination
|
||||
}
|
||||
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&eipaddresssettype
|
||||
type EipAddressSetType struct {
|
||||
RegionId common.Region
|
||||
IpAddress string
|
||||
AllocationId string
|
||||
Status EipStatus
|
||||
InstanceId string
|
||||
Bandwidth string // Why string
|
||||
InternetChargeType common.InternetChargeType
|
||||
OperationLocks OperationLocksType
|
||||
AllocationTime util.ISO6801Time
|
||||
}
|
||||
|
||||
type DescribeEipAddressesResponse struct {
|
||||
common.Response
|
||||
common.PaginationResult
|
||||
EipAddresses struct {
|
||||
EipAddress []EipAddressSetType
|
||||
}
|
||||
}
|
||||
|
||||
// DescribeInstanceStatus describes instance status
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/network&describeeipaddresses
|
||||
func (client *Client) DescribeEipAddresses(args *DescribeEipAddressesArgs) (eipAddresses []EipAddressSetType, pagination *common.PaginationResult, err error) {
|
||||
args.Validate()
|
||||
response := DescribeEipAddressesResponse{}
|
||||
|
||||
err = client.Invoke("DescribeEipAddresses", args, &response)
|
||||
|
||||
if err == nil {
|
||||
return response.EipAddresses.EipAddress, &response.PaginationResult, nil
|
||||
}
|
||||
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
type ModifyEipAddressAttributeArgs struct {
|
||||
AllocationId string
|
||||
Bandwidth int
|
||||
}
|
||||
|
||||
type ModifyEipAddressAttributeResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// ModifyEipAddressAttribute Modifies EIP attribute
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/network&modifyeipaddressattribute
|
||||
func (client *Client) ModifyEipAddressAttribute(allocationId string, bandwidth int) error {
|
||||
args := ModifyEipAddressAttributeArgs{
|
||||
AllocationId: allocationId,
|
||||
Bandwidth: bandwidth,
|
||||
}
|
||||
response := ModifyEipAddressAttributeResponse{}
|
||||
return client.Invoke("ModifyEipAddressAttribute", &args, &response)
|
||||
}
|
||||
|
||||
type UnallocateEipAddressArgs struct {
|
||||
AllocationId string
|
||||
InstanceId string
|
||||
}
|
||||
|
||||
type UnallocateEipAddressResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// UnassociateEipAddress unallocates Eip Address from instance
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/network&unassociateeipaddress
|
||||
func (client *Client) UnassociateEipAddress(allocationId string, instanceId string) error {
|
||||
args := UnallocateEipAddressArgs{
|
||||
AllocationId: allocationId,
|
||||
InstanceId: instanceId,
|
||||
}
|
||||
response := UnallocateEipAddressResponse{}
|
||||
return client.Invoke("UnassociateEipAddress", &args, &response)
|
||||
}
|
||||
|
||||
type ReleaseEipAddressArgs struct {
|
||||
AllocationId string
|
||||
}
|
||||
|
||||
type ReleaseEipAddressResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// ReleaseEipAddress releases Eip address
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/network&releaseeipaddress
|
||||
func (client *Client) ReleaseEipAddress(allocationId string) error {
|
||||
args := ReleaseEipAddressArgs{
|
||||
AllocationId: allocationId,
|
||||
}
|
||||
response := ReleaseEipAddressResponse{}
|
||||
return client.Invoke("ReleaseEipAddress", &args, &response)
|
||||
}
|
||||
|
||||
// WaitForVSwitchAvailable waits for VSwitch to given status
|
||||
func (client *Client) WaitForEip(regionId common.Region, allocationId string, status EipStatus, timeout int) error {
|
||||
if timeout <= 0 {
|
||||
timeout = DefaultTimeout
|
||||
}
|
||||
args := DescribeEipAddressesArgs{
|
||||
RegionId: regionId,
|
||||
AllocationId: allocationId,
|
||||
}
|
||||
for {
|
||||
eips, _, err := client.DescribeEipAddresses(&args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(eips) == 0 {
|
||||
return common.GetClientErrorFromString("Not found")
|
||||
}
|
||||
if eips[0].Status == status {
|
||||
break
|
||||
}
|
||||
timeout = timeout - DefaultWaitForInterval
|
||||
if timeout <= 0 {
|
||||
return common.GetClientErrorFromString("Timeout")
|
||||
}
|
||||
time.Sleep(DefaultWaitForInterval * time.Second)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package ecs
|
||||
|
||||
import "github.com/denverdino/aliyungo/common"
|
||||
|
||||
type DescribeRegionsArgs struct {
|
||||
}
|
||||
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype®iontype
|
||||
type RegionType struct {
|
||||
RegionId common.Region
|
||||
LocalName string
|
||||
}
|
||||
|
||||
type DescribeRegionsResponse struct {
|
||||
common.Response
|
||||
Regions struct {
|
||||
Region []RegionType
|
||||
}
|
||||
}
|
||||
|
||||
// DescribeRegions describes regions
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/region&describeregions
|
||||
func (client *Client) DescribeRegions() (regions []RegionType, err error) {
|
||||
response := DescribeRegionsResponse{}
|
||||
|
||||
err = client.Invoke("DescribeRegions", &DescribeRegionsArgs{}, &response)
|
||||
|
||||
if err != nil {
|
||||
return []RegionType{}, err
|
||||
}
|
||||
return response.Regions.Region, nil
|
||||
}
|
|
@ -0,0 +1,176 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/util"
|
||||
)
|
||||
|
||||
type DescribeRouteTablesArgs struct {
|
||||
VRouterId string
|
||||
RouteTableId string
|
||||
common.Pagination
|
||||
}
|
||||
|
||||
type RouteTableType string
|
||||
|
||||
const (
|
||||
RouteTableSystem = RouteTableType("System")
|
||||
RouteTableCustom = RouteTableType("Custom")
|
||||
)
|
||||
|
||||
type RouteEntryStatus string
|
||||
|
||||
const (
|
||||
RouteEntryStatusPending = RouteEntryStatus("Pending")
|
||||
RouteEntryStatusAvailable = RouteEntryStatus("Available")
|
||||
RouteEntryStatusModifying = RouteEntryStatus("Modifying")
|
||||
)
|
||||
|
||||
type NextHopListType struct {
|
||||
NextHopList struct {
|
||||
NextHopItem []NextHopItemType
|
||||
}
|
||||
}
|
||||
|
||||
type NextHopItemType struct {
|
||||
NextHopType string
|
||||
NextHopId string
|
||||
}
|
||||
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&routeentrysettype
|
||||
type RouteEntrySetType struct {
|
||||
RouteTableId string
|
||||
DestinationCidrBlock string
|
||||
Type RouteTableType
|
||||
NextHopType string
|
||||
NextHopId string
|
||||
NextHopList NextHopListType
|
||||
InstanceId string
|
||||
Status RouteEntryStatus // enum Pending | Available | Modifying
|
||||
}
|
||||
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&routetablesettype
|
||||
type RouteTableSetType struct {
|
||||
VRouterId string
|
||||
RouteTableId string
|
||||
RouteEntrys struct {
|
||||
RouteEntry []RouteEntrySetType
|
||||
}
|
||||
RouteTableType RouteTableType
|
||||
CreationTime util.ISO6801Time
|
||||
}
|
||||
|
||||
type DescribeRouteTablesResponse struct {
|
||||
common.Response
|
||||
common.PaginationResult
|
||||
RouteTables struct {
|
||||
RouteTable []RouteTableSetType
|
||||
}
|
||||
}
|
||||
|
||||
// DescribeRouteTables describes Virtual Routers
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/routertable&describeroutetables
|
||||
func (client *Client) DescribeRouteTables(args *DescribeRouteTablesArgs) (routeTables []RouteTableSetType, pagination *common.PaginationResult, err error) {
|
||||
args.Validate()
|
||||
response := DescribeRouteTablesResponse{}
|
||||
|
||||
err = client.Invoke("DescribeRouteTables", args, &response)
|
||||
|
||||
if err == nil {
|
||||
return response.RouteTables.RouteTable, &response.PaginationResult, nil
|
||||
}
|
||||
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
type NextHopType string
|
||||
|
||||
const (
|
||||
NextHopIntance = NextHopType("Instance") //Default
|
||||
NextHopTunnel = NextHopType("Tunnel")
|
||||
)
|
||||
|
||||
type CreateRouteEntryArgs struct {
|
||||
RouteTableId string
|
||||
DestinationCidrBlock string
|
||||
NextHopType NextHopType
|
||||
NextHopId string
|
||||
ClientToken string
|
||||
}
|
||||
|
||||
type CreateRouteEntryResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// CreateRouteEntry creates route entry
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/routertable&createrouteentry
|
||||
func (client *Client) CreateRouteEntry(args *CreateRouteEntryArgs) error {
|
||||
response := CreateRouteEntryResponse{}
|
||||
return client.Invoke("CreateRouteEntry", args, &response)
|
||||
}
|
||||
|
||||
type DeleteRouteEntryArgs struct {
|
||||
RouteTableId string
|
||||
DestinationCidrBlock string
|
||||
NextHopId string
|
||||
}
|
||||
|
||||
type DeleteRouteEntryResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// DeleteRouteEntry deletes route entry
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/routertable&deleterouteentry
|
||||
func (client *Client) DeleteRouteEntry(args *DeleteRouteEntryArgs) error {
|
||||
response := DeleteRouteEntryResponse{}
|
||||
return client.Invoke("DeleteRouteEntry", args, &response)
|
||||
}
|
||||
|
||||
// WaitForAllRouteEntriesAvailable waits for all route entries to Available status
|
||||
func (client *Client) WaitForAllRouteEntriesAvailable(vrouterId string, routeTableId string, timeout int) error {
|
||||
if timeout <= 0 {
|
||||
timeout = DefaultTimeout
|
||||
}
|
||||
args := DescribeRouteTablesArgs{
|
||||
VRouterId: vrouterId,
|
||||
RouteTableId: routeTableId,
|
||||
}
|
||||
for {
|
||||
|
||||
routeTables, _, err := client.DescribeRouteTables(&args)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(routeTables) == 0 {
|
||||
return common.GetClientErrorFromString("Not found")
|
||||
}
|
||||
success := true
|
||||
|
||||
loop:
|
||||
for _, routeTable := range routeTables {
|
||||
for _, routeEntry := range routeTable.RouteEntrys.RouteEntry {
|
||||
if routeEntry.Status != RouteEntryStatusAvailable {
|
||||
success = false
|
||||
break loop
|
||||
}
|
||||
}
|
||||
}
|
||||
if success {
|
||||
break
|
||||
}
|
||||
timeout = timeout - DefaultWaitForInterval
|
||||
if timeout <= 0 {
|
||||
return common.GetClientErrorFromString("Timeout")
|
||||
}
|
||||
time.Sleep(DefaultWaitForInterval * time.Second)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,273 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/util"
|
||||
)
|
||||
|
||||
type NicType string
|
||||
|
||||
const (
|
||||
NicTypeInternet = NicType("internet")
|
||||
NicTypeIntranet = NicType("intranet")
|
||||
)
|
||||
|
||||
type IpProtocol string
|
||||
|
||||
const (
|
||||
IpProtocolAll = IpProtocol("all")
|
||||
IpProtocolTCP = IpProtocol("tcp")
|
||||
IpProtocolUDP = IpProtocol("udp")
|
||||
IpProtocolICMP = IpProtocol("icmp")
|
||||
IpProtocolGRE = IpProtocol("gre")
|
||||
)
|
||||
|
||||
type PermissionPolicy string
|
||||
|
||||
const (
|
||||
PermissionPolicyAccept = PermissionPolicy("accept")
|
||||
PermissionPolicyDrop = PermissionPolicy("drop")
|
||||
)
|
||||
|
||||
type DescribeSecurityGroupAttributeArgs struct {
|
||||
SecurityGroupId string
|
||||
RegionId common.Region
|
||||
NicType NicType //enum for internet (default) |intranet
|
||||
Direction string // enum ingress egress
|
||||
}
|
||||
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&permissiontype
|
||||
type PermissionType struct {
|
||||
IpProtocol IpProtocol
|
||||
PortRange string
|
||||
SourceCidrIp string
|
||||
SourceGroupId string
|
||||
SourceGroupOwnerAccount string
|
||||
DestCidrIp string
|
||||
DestGroupId string
|
||||
DestGroupOwnerAccount string
|
||||
Policy PermissionPolicy
|
||||
NicType NicType
|
||||
Priority int
|
||||
Direction string
|
||||
Description string
|
||||
}
|
||||
|
||||
type DescribeSecurityGroupAttributeResponse struct {
|
||||
common.Response
|
||||
|
||||
SecurityGroupId string
|
||||
SecurityGroupName string
|
||||
RegionId common.Region
|
||||
Description string
|
||||
Permissions struct {
|
||||
Permission []PermissionType
|
||||
}
|
||||
VpcId string
|
||||
}
|
||||
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/securitygroup&describesecuritygroupattribute
|
||||
func (client *Client) DescribeSecurityGroupAttribute(args *DescribeSecurityGroupAttributeArgs) (response *DescribeSecurityGroupAttributeResponse, err error) {
|
||||
response = &DescribeSecurityGroupAttributeResponse{}
|
||||
err = client.Invoke("DescribeSecurityGroupAttribute", args, response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
type DescribeSecurityGroupsArgs struct {
|
||||
RegionId common.Region
|
||||
VpcId string
|
||||
common.Pagination
|
||||
}
|
||||
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&securitygroupitemtype
|
||||
type SecurityGroupItemType struct {
|
||||
SecurityGroupId string
|
||||
SecurityGroupName string
|
||||
Description string
|
||||
VpcId string
|
||||
CreationTime util.ISO6801Time
|
||||
}
|
||||
|
||||
type DescribeSecurityGroupsResponse struct {
|
||||
common.Response
|
||||
common.PaginationResult
|
||||
|
||||
RegionId common.Region
|
||||
SecurityGroups struct {
|
||||
SecurityGroup []SecurityGroupItemType
|
||||
}
|
||||
}
|
||||
|
||||
// DescribeSecurityGroups describes security groups
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/securitygroup&describesecuritygroups
|
||||
func (client *Client) DescribeSecurityGroups(args *DescribeSecurityGroupsArgs) (securityGroupItems []SecurityGroupItemType, pagination *common.PaginationResult, err error) {
|
||||
args.Validate()
|
||||
response := DescribeSecurityGroupsResponse{}
|
||||
|
||||
err = client.Invoke("DescribeSecurityGroups", args, &response)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return response.SecurityGroups.SecurityGroup, &response.PaginationResult, nil
|
||||
}
|
||||
|
||||
type CreateSecurityGroupArgs struct {
|
||||
RegionId common.Region
|
||||
SecurityGroupName string
|
||||
Description string
|
||||
VpcId string
|
||||
ClientToken string
|
||||
}
|
||||
|
||||
type CreateSecurityGroupResponse struct {
|
||||
common.Response
|
||||
|
||||
SecurityGroupId string
|
||||
}
|
||||
|
||||
// CreateSecurityGroup creates security group
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/securitygroup&createsecuritygroup
|
||||
func (client *Client) CreateSecurityGroup(args *CreateSecurityGroupArgs) (securityGroupId string, err error) {
|
||||
response := CreateSecurityGroupResponse{}
|
||||
err = client.Invoke("CreateSecurityGroup", args, &response)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return response.SecurityGroupId, err
|
||||
}
|
||||
|
||||
type DeleteSecurityGroupArgs struct {
|
||||
RegionId common.Region
|
||||
SecurityGroupId string
|
||||
}
|
||||
|
||||
type DeleteSecurityGroupResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// DeleteSecurityGroup deletes security group
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/securitygroup&deletesecuritygroup
|
||||
func (client *Client) DeleteSecurityGroup(regionId common.Region, securityGroupId string) error {
|
||||
args := DeleteSecurityGroupArgs{
|
||||
RegionId: regionId,
|
||||
SecurityGroupId: securityGroupId,
|
||||
}
|
||||
response := DeleteSecurityGroupResponse{}
|
||||
err := client.Invoke("DeleteSecurityGroup", &args, &response)
|
||||
return err
|
||||
}
|
||||
|
||||
type ModifySecurityGroupAttributeArgs struct {
|
||||
RegionId common.Region
|
||||
SecurityGroupId string
|
||||
SecurityGroupName string
|
||||
Description string
|
||||
}
|
||||
|
||||
type ModifySecurityGroupAttributeResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// ModifySecurityGroupAttribute modifies attribute of security group
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/securitygroup&modifysecuritygroupattribute
|
||||
func (client *Client) ModifySecurityGroupAttribute(args *ModifySecurityGroupAttributeArgs) error {
|
||||
response := ModifySecurityGroupAttributeResponse{}
|
||||
err := client.Invoke("ModifySecurityGroupAttribute", args, &response)
|
||||
return err
|
||||
}
|
||||
|
||||
type AuthorizeSecurityGroupArgs struct {
|
||||
SecurityGroupId string
|
||||
RegionId common.Region
|
||||
IpProtocol IpProtocol
|
||||
PortRange string
|
||||
SourceGroupId string
|
||||
SourceGroupOwnerAccount string
|
||||
SourceGroupOwnerID string
|
||||
SourceCidrIp string // IPv4 only, default 0.0.0.0/0
|
||||
Policy PermissionPolicy // enum of accept (default) | drop
|
||||
Priority int // 1 - 100, default 1
|
||||
NicType NicType // enum of internet | intranet (default)
|
||||
}
|
||||
|
||||
type AuthorizeSecurityGroupResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// AuthorizeSecurityGroup authorize permissions to security group
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/securitygroup&authorizesecuritygroup
|
||||
func (client *Client) AuthorizeSecurityGroup(args *AuthorizeSecurityGroupArgs) error {
|
||||
response := AuthorizeSecurityGroupResponse{}
|
||||
err := client.Invoke("AuthorizeSecurityGroup", args, &response)
|
||||
return err
|
||||
}
|
||||
|
||||
type RevokeSecurityGroupArgs struct {
|
||||
AuthorizeSecurityGroupArgs
|
||||
}
|
||||
|
||||
type RevokeSecurityGroupResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// You can read doc at https://help.aliyun.com/document_detail/25557.html?spm=5176.doc25554.6.755.O6Tjz0
|
||||
func (client *Client) RevokeSecurityGroup(args *RevokeSecurityGroupArgs) error {
|
||||
response := RevokeSecurityGroupResponse{}
|
||||
err := client.Invoke("RevokeSecurityGroup", args, &response)
|
||||
return err
|
||||
}
|
||||
|
||||
type AuthorizeSecurityGroupEgressArgs struct {
|
||||
SecurityGroupId string
|
||||
RegionId common.Region
|
||||
IpProtocol IpProtocol
|
||||
PortRange string
|
||||
DestGroupId string
|
||||
DestGroupOwnerAccount string
|
||||
DestGroupOwnerId string
|
||||
DestCidrIp string // IPv4 only, default 0.0.0.0/0
|
||||
Policy PermissionPolicy // enum of accept (default) | drop
|
||||
Priority int // 1 - 100, default 1
|
||||
NicType NicType // enum of internet | intranet (default)
|
||||
}
|
||||
|
||||
type AuthorizeSecurityGroupEgressResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// AuthorizeSecurityGroup authorize permissions to security group
|
||||
//
|
||||
// You can read doc at https://help.aliyun.com/document_detail/25560.html
|
||||
func (client *Client) AuthorizeSecurityGroupEgress(args *AuthorizeSecurityGroupEgressArgs) error {
|
||||
response := AuthorizeSecurityGroupEgressResponse{}
|
||||
err := client.Invoke("AuthorizeSecurityGroupEgress", args, &response)
|
||||
return err
|
||||
}
|
||||
|
||||
type RevokeSecurityGroupEgressArgs struct {
|
||||
AuthorizeSecurityGroupEgressArgs
|
||||
}
|
||||
|
||||
type RevokeSecurityGroupEgressResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// You can read doc at https://help.aliyun.com/document_detail/25561.html?spm=5176.doc25557.6.759.qcR4Az
|
||||
func (client *Client) RevokeSecurityGroupEgress(args *RevokeSecurityGroupEgressArgs) error {
|
||||
response := RevokeSecurityGroupEgressResponse{}
|
||||
err := client.Invoke("RevokeSecurityGroupEgress", args, &response)
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/util"
|
||||
)
|
||||
|
||||
type DescribeSnapshotsArgs struct {
|
||||
RegionId common.Region
|
||||
InstanceId string
|
||||
DiskId string
|
||||
SnapshotIds []string //["s-xxxxxxxxx", "s-yyyyyyyyy", ..."s-zzzzzzzzz"]
|
||||
common.Pagination
|
||||
}
|
||||
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&snapshottype
|
||||
type SnapshotType struct {
|
||||
SnapshotId string
|
||||
SnapshotName string
|
||||
Description string
|
||||
Progress string
|
||||
SourceDiskId string
|
||||
SourceDiskSize int
|
||||
SourceDiskType string //enum for System | Data
|
||||
ProductCode string
|
||||
CreationTime util.ISO6801Time
|
||||
}
|
||||
|
||||
type DescribeSnapshotsResponse struct {
|
||||
common.Response
|
||||
common.PaginationResult
|
||||
Snapshots struct {
|
||||
Snapshot []SnapshotType
|
||||
}
|
||||
}
|
||||
|
||||
// DescribeSnapshots describe snapshots
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/snapshot&describesnapshots
|
||||
func (client *Client) DescribeSnapshots(args *DescribeSnapshotsArgs) (snapshots []SnapshotType, pagination *common.PaginationResult, err error) {
|
||||
args.Validate()
|
||||
response := DescribeSnapshotsResponse{}
|
||||
|
||||
err = client.Invoke("DescribeSnapshots", args, &response)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return response.Snapshots.Snapshot, &response.PaginationResult, nil
|
||||
|
||||
}
|
||||
|
||||
type DeleteSnapshotArgs struct {
|
||||
SnapshotId string
|
||||
}
|
||||
|
||||
type DeleteSnapshotResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// DeleteSnapshot deletes snapshot
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/snapshot&deletesnapshot
|
||||
func (client *Client) DeleteSnapshot(snapshotId string) error {
|
||||
args := DeleteSnapshotArgs{SnapshotId: snapshotId}
|
||||
response := DeleteSnapshotResponse{}
|
||||
|
||||
return client.Invoke("DeleteSnapshot", &args, &response)
|
||||
}
|
||||
|
||||
type CreateSnapshotArgs struct {
|
||||
DiskId string
|
||||
SnapshotName string
|
||||
Description string
|
||||
ClientToken string
|
||||
}
|
||||
|
||||
type CreateSnapshotResponse struct {
|
||||
common.Response
|
||||
SnapshotId string
|
||||
}
|
||||
|
||||
// CreateSnapshot creates a new snapshot
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/snapshot&createsnapshot
|
||||
func (client *Client) CreateSnapshot(args *CreateSnapshotArgs) (snapshotId string, err error) {
|
||||
|
||||
response := CreateSnapshotResponse{}
|
||||
|
||||
err = client.Invoke("CreateSnapshot", args, &response)
|
||||
if err == nil {
|
||||
snapshotId = response.SnapshotId
|
||||
}
|
||||
return snapshotId, err
|
||||
}
|
||||
|
||||
// Default timeout value for WaitForSnapShotReady method
|
||||
const SnapshotDefaultTimeout = 120
|
||||
|
||||
// WaitForSnapShotReady waits for snapshot ready
|
||||
func (client *Client) WaitForSnapShotReady(regionId common.Region, snapshotId string, timeout int) error {
|
||||
if timeout <= 0 {
|
||||
timeout = SnapshotDefaultTimeout
|
||||
}
|
||||
for {
|
||||
args := DescribeSnapshotsArgs{
|
||||
RegionId: regionId,
|
||||
SnapshotIds: []string{snapshotId},
|
||||
}
|
||||
|
||||
snapshots, _, err := client.DescribeSnapshots(&args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if snapshots == nil || len(snapshots) == 0 {
|
||||
return common.GetClientErrorFromString("Not found")
|
||||
}
|
||||
if snapshots[0].Progress == "100%" {
|
||||
break
|
||||
}
|
||||
timeout = timeout - DefaultWaitForInterval
|
||||
if timeout <= 0 {
|
||||
return common.GetClientErrorFromString("Timeout")
|
||||
}
|
||||
time.Sleep(DefaultWaitForInterval * time.Second)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
package ecs
|
||||
|
||||
import "github.com/denverdino/aliyungo/common"
|
||||
|
||||
type CreateSnatEntryArgs struct {
|
||||
RegionId common.Region
|
||||
SnatTableId string
|
||||
SourceVSwitchId string
|
||||
SnatIp string
|
||||
}
|
||||
|
||||
type CreateSnatEntryResponse struct {
|
||||
common.Response
|
||||
SnatEntryId string
|
||||
}
|
||||
|
||||
type SnatEntrySetType struct {
|
||||
RegionId common.Region
|
||||
SnatEntryId string
|
||||
SnatIp string
|
||||
SnatTableId string
|
||||
SourceCIDR string
|
||||
SourceVSwitchId string
|
||||
Status string
|
||||
}
|
||||
|
||||
type DescribeSnatTableEntriesArgs struct {
|
||||
RegionId common.Region
|
||||
SnatTableId string
|
||||
common.Pagination
|
||||
}
|
||||
|
||||
type DescribeSnatTableEntriesResponse struct {
|
||||
common.Response
|
||||
common.PaginationResult
|
||||
SnatTableEntries struct {
|
||||
SnatTableEntry []SnatEntrySetType
|
||||
}
|
||||
}
|
||||
|
||||
type ModifySnatEntryArgs struct {
|
||||
RegionId common.Region
|
||||
SnatTableId string
|
||||
SnatEntryId string
|
||||
SnatIp string
|
||||
}
|
||||
|
||||
type ModifySnatEntryResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
type DeleteSnatEntryArgs struct {
|
||||
RegionId common.Region
|
||||
SnatTableId string
|
||||
SnatEntryId string
|
||||
}
|
||||
|
||||
type DeleteSnatEntryResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
func (client *Client) CreateSnatEntry(args *CreateSnatEntryArgs) (resp *CreateSnatEntryResponse, err error) {
|
||||
response := CreateSnatEntryResponse{}
|
||||
err = client.Invoke("CreateSnatEntry", args, &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &response, err
|
||||
}
|
||||
|
||||
func (client *Client) DescribeSnatTableEntries(args *DescribeSnatTableEntriesArgs) (snatTableEntries []SnatEntrySetType,
|
||||
pagination *common.PaginationResult, err error) {
|
||||
|
||||
args.Validate()
|
||||
response := DescribeSnatTableEntriesResponse{}
|
||||
|
||||
err = client.Invoke("DescribeSnatTableEntries", args, &response)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return response.SnatTableEntries.SnatTableEntry, &response.PaginationResult, nil
|
||||
}
|
||||
|
||||
func (client *Client) ModifySnatEntry(args *ModifySnatEntryArgs) error {
|
||||
response := ModifySnatEntryResponse{}
|
||||
return client.Invoke("ModifySnatEntry", args, &response)
|
||||
}
|
||||
|
||||
func (client *Client) DeleteSnatEntry(args *DeleteSnatEntryArgs) error {
|
||||
response := DeleteSnatEntryResponse{}
|
||||
err := client.Invoke("DeleteSnatEntry", args, &response)
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
)
|
||||
|
||||
type CreateKeyPairArgs struct {
|
||||
RegionId common.Region
|
||||
KeyPairName string
|
||||
}
|
||||
|
||||
type CreateKeyPairResponse struct {
|
||||
common.Response
|
||||
KeyPairName string
|
||||
KeyPairFingerPrint string
|
||||
PrivateKeyBody string
|
||||
}
|
||||
|
||||
// CreateKeyPair creates keypair
|
||||
//
|
||||
// You can read doc at https://help.aliyun.com/document_detail/51771.html?spm=5176.doc51775.6.910.cedjfr
|
||||
func (client *Client) CreateKeyPair(args *CreateKeyPairArgs) (resp *CreateKeyPairResponse,err error) {
|
||||
response := CreateKeyPairResponse{}
|
||||
err = client.Invoke("CreateKeyPair", args, &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &response, err
|
||||
}
|
||||
|
||||
type ImportKeyPairArgs struct {
|
||||
RegionId common.Region
|
||||
PublicKeyBody string
|
||||
KeyPairName string
|
||||
}
|
||||
|
||||
type ImportKeyPairResponse struct {
|
||||
common.Response
|
||||
KeyPairName string
|
||||
KeyPairFingerPrint string
|
||||
}
|
||||
|
||||
// ImportKeyPair import keypair
|
||||
//
|
||||
// You can read doc at https://help.aliyun.com/document_detail/51774.html?spm=5176.doc51771.6.911.BicQq2
|
||||
func (client *Client) ImportKeyPair(args *ImportKeyPairArgs) (resp *ImportKeyPairResponse,err error) {
|
||||
response := ImportKeyPairResponse{}
|
||||
err = client.Invoke("ImportKeyPair", args, &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &response, err
|
||||
}
|
||||
|
||||
type DescribeKeyPairsArgs struct {
|
||||
RegionId common.Region
|
||||
KeyPairFingerPrint string
|
||||
KeyPairName string
|
||||
common.Pagination
|
||||
}
|
||||
|
||||
type KeyPairItemType struct {
|
||||
KeyPairName string
|
||||
KeyPairFingerPrint string
|
||||
}
|
||||
|
||||
type DescribeKeyPairsResponse struct {
|
||||
common.Response
|
||||
common.PaginationResult
|
||||
RegionId common.Region
|
||||
KeyPairs struct {
|
||||
KeyPair []KeyPairItemType
|
||||
}
|
||||
}
|
||||
|
||||
// DescribeKeyPairs describe keypairs
|
||||
//
|
||||
// You can read doc at https://help.aliyun.com/document_detail/51773.html?spm=5176.doc51774.6.912.lyE0iX
|
||||
func (client *Client) DescribeKeyPairs(args *DescribeKeyPairsArgs) (KeyPairs []KeyPairItemType, pagination *common.PaginationResult, err error) {
|
||||
response := DescribeKeyPairsResponse{}
|
||||
|
||||
err = client.Invoke("DescribeKeyPairs", args, &response)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return response.KeyPairs.KeyPair, &response.PaginationResult, err
|
||||
}
|
||||
|
||||
type AttachKeyPairArgs struct {
|
||||
RegionId common.Region
|
||||
KeyPairName string
|
||||
InstanceIds string
|
||||
}
|
||||
|
||||
// AttachKeyPair keypars to instances
|
||||
//
|
||||
// You can read doc at https://help.aliyun.com/document_detail/51775.html?spm=5176.doc51773.6.913.igEem4
|
||||
func (client *Client) AttachKeyPair(args *AttachKeyPairArgs) (err error) {
|
||||
response := common.Response{}
|
||||
err = client.Invoke("AttachKeyPair", args, &response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type DetachKeyPairArgs struct {
|
||||
RegionId common.Region
|
||||
KeyPairName string
|
||||
InstanceIds string
|
||||
}
|
||||
|
||||
// DetachKeyPair keyparis from instances
|
||||
//
|
||||
// You can read doc at https://help.aliyun.com/document_detail/51776.html?spm=5176.doc51775.6.914.DJ7Gmq
|
||||
func (client *Client) DetachKeyPair(args *DetachKeyPairArgs) (err error) {
|
||||
response := common.Response{}
|
||||
err = client.Invoke("DetachKeyPair", args, &response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type DeleteKeyPairsArgs struct {
|
||||
RegionId common.Region
|
||||
KeyPairNames string
|
||||
}
|
||||
|
||||
// DeleteKeyPairs delete keypairs
|
||||
//
|
||||
// You can read doc at https://help.aliyun.com/document_detail/51772.html?spm=5176.doc51776.6.915.Qqcv2Q
|
||||
func (client *Client) DeleteKeyPairs(args *DeleteKeyPairsArgs) (err error) {
|
||||
response := common.Response{}
|
||||
err = client.Invoke("DeleteKeyPairs", args, &response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
package ecs
|
||||
|
||||
import "github.com/denverdino/aliyungo/common"
|
||||
|
||||
type TagResourceType string
|
||||
|
||||
const (
|
||||
TagResourceImage = TagResourceType("image")
|
||||
TagResourceInstance = TagResourceType("instance")
|
||||
TagResourceSnapshot = TagResourceType("snapshot")
|
||||
TagResourceDisk = TagResourceType("disk")
|
||||
)
|
||||
|
||||
type AddTagsArgs struct {
|
||||
ResourceId string
|
||||
ResourceType TagResourceType //image, instance, snapshot or disk
|
||||
RegionId common.Region
|
||||
Tag map[string]string
|
||||
}
|
||||
|
||||
type AddTagsResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// AddTags Add tags to resource
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/tags&addtags
|
||||
func (client *Client) AddTags(args *AddTagsArgs) error {
|
||||
response := AddTagsResponse{}
|
||||
err := client.Invoke("AddTags", args, &response)
|
||||
return err
|
||||
}
|
||||
|
||||
type RemoveTagsArgs struct {
|
||||
ResourceId string
|
||||
ResourceType TagResourceType //image, instance, snapshot or disk
|
||||
RegionId common.Region
|
||||
Tag map[string]string
|
||||
}
|
||||
|
||||
type RemoveTagsResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// RemoveTags remove tags to resource
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/tags&removetags
|
||||
func (client *Client) RemoveTags(args *RemoveTagsArgs) error {
|
||||
response := RemoveTagsResponse{}
|
||||
err := client.Invoke("RemoveTags", args, &response)
|
||||
return err
|
||||
}
|
||||
|
||||
type ResourceItemType struct {
|
||||
ResourceId string
|
||||
ResourceType TagResourceType
|
||||
RegionId common.Region
|
||||
}
|
||||
|
||||
type DescribeResourceByTagsArgs struct {
|
||||
ResourceType TagResourceType //image, instance, snapshot or disk
|
||||
RegionId common.Region
|
||||
Tag map[string]string
|
||||
common.Pagination
|
||||
}
|
||||
|
||||
type DescribeResourceByTagsResponse struct {
|
||||
common.Response
|
||||
common.PaginationResult
|
||||
Resources struct {
|
||||
Resource []ResourceItemType
|
||||
}
|
||||
}
|
||||
|
||||
// DescribeResourceByTags describe resource by tags
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/tags&describeresourcebytags
|
||||
func (client *Client) DescribeResourceByTags(args *DescribeResourceByTagsArgs) (resources []ResourceItemType, pagination *common.PaginationResult, err error) {
|
||||
args.Validate()
|
||||
response := DescribeResourceByTagsResponse{}
|
||||
err = client.Invoke("DescribeResourceByTags", args, &response)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return response.Resources.Resource, &response.PaginationResult, nil
|
||||
}
|
||||
|
||||
type TagItemType struct {
|
||||
TagKey string
|
||||
TagValue string
|
||||
}
|
||||
|
||||
type DescribeTagsArgs struct {
|
||||
RegionId common.Region
|
||||
ResourceType TagResourceType //image, instance, snapshot or disk
|
||||
ResourceId string
|
||||
Tag map[string]string
|
||||
common.Pagination
|
||||
}
|
||||
|
||||
type DescribeTagsResponse struct {
|
||||
common.Response
|
||||
common.PaginationResult
|
||||
Tags struct {
|
||||
Tag []TagItemType
|
||||
}
|
||||
}
|
||||
|
||||
// DescribeResourceByTags describe resource by tags
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/tags&describeresourcebytags
|
||||
func (client *Client) DescribeTags(args *DescribeTagsArgs) (tags []TagItemType, pagination *common.PaginationResult, err error) {
|
||||
args.Validate()
|
||||
response := DescribeTagsResponse{}
|
||||
err = client.Invoke("DescribeTags", args, &response)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return response.Tags.Tag, &response.PaginationResult, nil
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/util"
|
||||
)
|
||||
|
||||
type CreateVpcArgs struct {
|
||||
RegionId common.Region
|
||||
CidrBlock string //192.168.0.0/16 or 172.16.0.0/16 (default)
|
||||
VpcName string
|
||||
Description string
|
||||
ClientToken string
|
||||
}
|
||||
|
||||
type CreateVpcResponse struct {
|
||||
common.Response
|
||||
VpcId string
|
||||
VRouterId string
|
||||
RouteTableId string
|
||||
}
|
||||
|
||||
// CreateVpc creates Virtual Private Cloud
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/vpc&createvpc
|
||||
func (client *Client) CreateVpc(args *CreateVpcArgs) (resp *CreateVpcResponse, err error) {
|
||||
response := CreateVpcResponse{}
|
||||
err = client.Invoke("CreateVpc", args, &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &response, err
|
||||
}
|
||||
|
||||
type DeleteVpcArgs struct {
|
||||
VpcId string
|
||||
}
|
||||
|
||||
type DeleteVpcResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// DeleteVpc deletes Virtual Private Cloud
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/vpc&deletevpc
|
||||
func (client *Client) DeleteVpc(vpcId string) error {
|
||||
args := DeleteVpcArgs{
|
||||
VpcId: vpcId,
|
||||
}
|
||||
response := DeleteVpcResponse{}
|
||||
return client.Invoke("DeleteVpc", &args, &response)
|
||||
}
|
||||
|
||||
type VpcStatus string
|
||||
|
||||
const (
|
||||
VpcStatusPending = VpcStatus("Pending")
|
||||
VpcStatusAvailable = VpcStatus("Available")
|
||||
)
|
||||
|
||||
type DescribeVpcsArgs struct {
|
||||
VpcId string
|
||||
RegionId common.Region
|
||||
common.Pagination
|
||||
}
|
||||
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&vpcsettype
|
||||
type VpcSetType struct {
|
||||
VpcId string
|
||||
RegionId common.Region
|
||||
Status VpcStatus // enum Pending | Available
|
||||
VpcName string
|
||||
VSwitchIds struct {
|
||||
VSwitchId []string
|
||||
}
|
||||
CidrBlock string
|
||||
VRouterId string
|
||||
Description string
|
||||
IsDefault bool
|
||||
CreationTime util.ISO6801Time
|
||||
}
|
||||
|
||||
type DescribeVpcsResponse struct {
|
||||
common.Response
|
||||
common.PaginationResult
|
||||
Vpcs struct {
|
||||
Vpc []VpcSetType
|
||||
}
|
||||
}
|
||||
|
||||
// DescribeInstanceStatus describes instance status
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/vpc&describevpcs
|
||||
func (client *Client) DescribeVpcs(args *DescribeVpcsArgs) (vpcs []VpcSetType, pagination *common.PaginationResult, err error) {
|
||||
args.Validate()
|
||||
response := DescribeVpcsResponse{}
|
||||
|
||||
err = client.Invoke("DescribeVpcs", args, &response)
|
||||
|
||||
if err == nil {
|
||||
return response.Vpcs.Vpc, &response.PaginationResult, nil
|
||||
}
|
||||
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
type ModifyVpcAttributeArgs struct {
|
||||
VpcId string
|
||||
VpcName string
|
||||
Description string
|
||||
}
|
||||
|
||||
type ModifyVpcAttributeResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// ModifyVpcAttribute modifies attribute of Virtual Private Cloud
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/vpc&modifyvpcattribute
|
||||
func (client *Client) ModifyVpcAttribute(args *ModifyVpcAttributeArgs) error {
|
||||
response := ModifyVpcAttributeResponse{}
|
||||
return client.Invoke("ModifyVpcAttribute", args, &response)
|
||||
}
|
||||
|
||||
// WaitForInstance waits for instance to given status
|
||||
func (client *Client) WaitForVpcAvailable(regionId common.Region, vpcId string, timeout int) error {
|
||||
if timeout <= 0 {
|
||||
timeout = DefaultTimeout
|
||||
}
|
||||
args := DescribeVpcsArgs{
|
||||
RegionId: regionId,
|
||||
VpcId: vpcId,
|
||||
}
|
||||
for {
|
||||
vpcs, _, err := client.DescribeVpcs(&args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(vpcs) > 0 && vpcs[0].Status == VpcStatusAvailable {
|
||||
break
|
||||
}
|
||||
timeout = timeout - DefaultWaitForInterval
|
||||
if timeout <= 0 {
|
||||
return common.GetClientErrorFromString("Timeout")
|
||||
}
|
||||
time.Sleep(DefaultWaitForInterval * time.Second)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/util"
|
||||
)
|
||||
|
||||
type DescribeVRoutersArgs struct {
|
||||
VRouterId string
|
||||
RegionId common.Region
|
||||
common.Pagination
|
||||
}
|
||||
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&vroutersettype
|
||||
type VRouterSetType struct {
|
||||
VRouterId string
|
||||
RegionId common.Region
|
||||
VpcId string
|
||||
RouteTableIds struct {
|
||||
RouteTableId []string
|
||||
}
|
||||
VRouterName string
|
||||
Description string
|
||||
CreationTime util.ISO6801Time
|
||||
}
|
||||
|
||||
type DescribeVRoutersResponse struct {
|
||||
common.Response
|
||||
common.PaginationResult
|
||||
VRouters struct {
|
||||
VRouter []VRouterSetType
|
||||
}
|
||||
}
|
||||
|
||||
// DescribeVRouters describes Virtual Routers
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/vrouter&describevrouters
|
||||
func (client *Client) DescribeVRouters(args *DescribeVRoutersArgs) (vrouters []VRouterSetType, pagination *common.PaginationResult, err error) {
|
||||
args.Validate()
|
||||
response := DescribeVRoutersResponse{}
|
||||
|
||||
err = client.Invoke("DescribeVRouters", args, &response)
|
||||
|
||||
if err == nil {
|
||||
return response.VRouters.VRouter, &response.PaginationResult, nil
|
||||
}
|
||||
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
type ModifyVRouterAttributeArgs struct {
|
||||
VRouterId string
|
||||
VRouterName string
|
||||
Description string
|
||||
}
|
||||
|
||||
type ModifyVRouterAttributeResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// ModifyVRouterAttribute modifies attribute of Virtual Router
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/vrouter&modifyvrouterattribute
|
||||
func (client *Client) ModifyVRouterAttribute(args *ModifyVRouterAttributeArgs) error {
|
||||
response := ModifyVRouterAttributeResponse{}
|
||||
return client.Invoke("ModifyVRouterAttribute", args, &response)
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/util"
|
||||
)
|
||||
|
||||
type CreateVSwitchArgs struct {
|
||||
ZoneId string
|
||||
CidrBlock string
|
||||
VpcId string
|
||||
VSwitchName string
|
||||
Description string
|
||||
ClientToken string
|
||||
}
|
||||
|
||||
type CreateVSwitchResponse struct {
|
||||
common.Response
|
||||
VSwitchId string
|
||||
}
|
||||
|
||||
// CreateVSwitch creates Virtual Switch
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/vswitch&createvswitch
|
||||
func (client *Client) CreateVSwitch(args *CreateVSwitchArgs) (vswitchId string, err error) {
|
||||
response := CreateVSwitchResponse{}
|
||||
err = client.Invoke("CreateVSwitch", args, &response)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return response.VSwitchId, err
|
||||
}
|
||||
|
||||
type DeleteVSwitchArgs struct {
|
||||
VSwitchId string
|
||||
}
|
||||
|
||||
type DeleteVSwitchResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// DeleteVSwitch deletes Virtual Switch
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/vswitch&deletevswitch
|
||||
func (client *Client) DeleteVSwitch(VSwitchId string) error {
|
||||
args := DeleteVSwitchArgs{
|
||||
VSwitchId: VSwitchId,
|
||||
}
|
||||
response := DeleteVSwitchResponse{}
|
||||
return client.Invoke("DeleteVSwitch", &args, &response)
|
||||
}
|
||||
|
||||
type DescribeVSwitchesArgs struct {
|
||||
VpcId string
|
||||
VSwitchId string
|
||||
ZoneId string
|
||||
common.Pagination
|
||||
}
|
||||
|
||||
type VSwitchStatus string
|
||||
|
||||
const (
|
||||
VSwitchStatusPending = VSwitchStatus("Pending")
|
||||
VSwitchStatusAvailable = VSwitchStatus("Available")
|
||||
)
|
||||
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&vswitchsettype
|
||||
type VSwitchSetType struct {
|
||||
VSwitchId string
|
||||
VpcId string
|
||||
Status VSwitchStatus // enum Pending | Available
|
||||
CidrBlock string
|
||||
ZoneId string
|
||||
AvailableIpAddressCount int
|
||||
Description string
|
||||
VSwitchName string
|
||||
IsDefault bool
|
||||
CreationTime util.ISO6801Time
|
||||
}
|
||||
|
||||
type DescribeVSwitchesResponse struct {
|
||||
common.Response
|
||||
common.PaginationResult
|
||||
VSwitches struct {
|
||||
VSwitch []VSwitchSetType
|
||||
}
|
||||
}
|
||||
|
||||
// DescribeVSwitches describes Virtual Switches
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/vswitch&describevswitches
|
||||
func (client *Client) DescribeVSwitches(args *DescribeVSwitchesArgs) (vswitches []VSwitchSetType, pagination *common.PaginationResult, err error) {
|
||||
args.Validate()
|
||||
response := DescribeVSwitchesResponse{}
|
||||
|
||||
err = client.Invoke("DescribeVSwitches", args, &response)
|
||||
|
||||
if err == nil {
|
||||
return response.VSwitches.VSwitch, &response.PaginationResult, nil
|
||||
}
|
||||
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
type ModifyVSwitchAttributeArgs struct {
|
||||
VSwitchId string
|
||||
VSwitchName string
|
||||
Description string
|
||||
}
|
||||
|
||||
type ModifyVSwitchAttributeResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// ModifyVSwitchAttribute modifies attribute of Virtual Private Cloud
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/vswitch&modifyvswitchattribute
|
||||
func (client *Client) ModifyVSwitchAttribute(args *ModifyVSwitchAttributeArgs) error {
|
||||
response := ModifyVSwitchAttributeResponse{}
|
||||
return client.Invoke("ModifyVSwitchAttribute", args, &response)
|
||||
}
|
||||
|
||||
// WaitForVSwitchAvailable waits for VSwitch to given status
|
||||
func (client *Client) WaitForVSwitchAvailable(vpcId string, vswitchId string, timeout int) error {
|
||||
if timeout <= 0 {
|
||||
timeout = DefaultTimeout
|
||||
}
|
||||
args := DescribeVSwitchesArgs{
|
||||
VpcId: vpcId,
|
||||
VSwitchId: vswitchId,
|
||||
}
|
||||
for {
|
||||
vswitches, _, err := client.DescribeVSwitches(&args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(vswitches) == 0 {
|
||||
return common.GetClientErrorFromString("Not found")
|
||||
}
|
||||
if vswitches[0].Status == VSwitchStatusAvailable {
|
||||
break
|
||||
}
|
||||
timeout = timeout - DefaultWaitForInterval
|
||||
if timeout <= 0 {
|
||||
return common.GetClientErrorFromString("Timeout")
|
||||
}
|
||||
time.Sleep(DefaultWaitForInterval * time.Second)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package ecs
|
||||
|
||||
import "github.com/denverdino/aliyungo/common"
|
||||
|
||||
type ResourceType string
|
||||
|
||||
const (
|
||||
ResourceTypeInstance = ResourceType("Instance")
|
||||
ResourceTypeDisk = ResourceType("Disk")
|
||||
ResourceTypeVSwitch = ResourceType("VSwitch")
|
||||
ResourceTypeIOOptimizedInstance = ResourceType("IoOptimized")
|
||||
)
|
||||
|
||||
type DescribeZonesArgs struct {
|
||||
RegionId common.Region
|
||||
}
|
||||
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&availableresourcecreationtype
|
||||
type AvailableResourceCreationType struct {
|
||||
ResourceTypes []ResourceType //enum for Instance, Disk, VSwitch
|
||||
}
|
||||
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&availablediskcategoriestype
|
||||
type AvailableDiskCategoriesType struct {
|
||||
DiskCategories []DiskCategory //enum for cloud, ephemeral, ephemeral_ssd
|
||||
}
|
||||
|
||||
type AvailableInstanceTypesType struct {
|
||||
InstanceTypes []string
|
||||
}
|
||||
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/datatype&zonetype
|
||||
type ZoneType struct {
|
||||
ZoneId string
|
||||
LocalName string
|
||||
AvailableInstanceTypes AvailableInstanceTypesType
|
||||
AvailableResourceCreation AvailableResourceCreationType
|
||||
AvailableDiskCategories AvailableDiskCategoriesType
|
||||
}
|
||||
|
||||
type DescribeZonesResponse struct {
|
||||
common.Response
|
||||
Zones struct {
|
||||
Zone []ZoneType
|
||||
}
|
||||
}
|
||||
|
||||
// DescribeZones describes zones
|
||||
func (client *Client) DescribeZones(regionId common.Region) (zones []ZoneType, err error) {
|
||||
args := DescribeZonesArgs{
|
||||
RegionId: regionId,
|
||||
}
|
||||
response := DescribeZonesResponse{}
|
||||
|
||||
err = client.Invoke("DescribeZones", &args, &response)
|
||||
|
||||
if err == nil {
|
||||
return response.Zones.Zone, nil
|
||||
}
|
||||
|
||||
return []ZoneType{}, err
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
package ram
|
||||
|
||||
type UserRequest struct {
|
||||
User
|
||||
}
|
||||
|
||||
type UserResponse struct {
|
||||
RamCommonResponse
|
||||
User User
|
||||
}
|
||||
|
||||
type UpdateUserRequest struct {
|
||||
UserName string
|
||||
NewUserName string
|
||||
NewDisplayName string
|
||||
NewMobilePhone string
|
||||
NewEmail string
|
||||
NewComments string
|
||||
}
|
||||
|
||||
type ListUserRequest struct {
|
||||
Marker string
|
||||
MaxItems int8
|
||||
}
|
||||
|
||||
type ListUserResponse struct {
|
||||
RamCommonResponse
|
||||
IsTruncated bool
|
||||
Marker string
|
||||
Users struct {
|
||||
User []User
|
||||
}
|
||||
}
|
||||
|
||||
func (client *RamClient) CreateUser(user UserRequest) (UserResponse, error) {
|
||||
var userResponse UserResponse
|
||||
err := client.Invoke("CreateUser", user, &userResponse)
|
||||
if err != nil {
|
||||
return UserResponse{}, err
|
||||
}
|
||||
return userResponse, nil
|
||||
}
|
||||
|
||||
func (client *RamClient) GetUser(userQuery UserQueryRequest) (UserResponse, error) {
|
||||
var userResponse UserResponse
|
||||
err := client.Invoke("GetUser", userQuery, &userResponse)
|
||||
if err != nil {
|
||||
return UserResponse{}, nil
|
||||
}
|
||||
return userResponse, nil
|
||||
}
|
||||
|
||||
func (client *RamClient) UpdateUser(newUser UpdateUserRequest) (UserResponse, error) {
|
||||
var userResponse UserResponse
|
||||
err := client.Invoke("UpdateUser", newUser, &userResponse)
|
||||
if err != nil {
|
||||
return UserResponse{}, err
|
||||
}
|
||||
return userResponse, nil
|
||||
}
|
||||
|
||||
func (client *RamClient) DeleteUser(userQuery UserQueryRequest) (RamCommonResponse, error) {
|
||||
var commonResp RamCommonResponse
|
||||
err := client.Invoke("DeleteUser", userQuery, &commonResp)
|
||||
if err != nil {
|
||||
return RamCommonResponse{}, err
|
||||
}
|
||||
return commonResp, nil
|
||||
}
|
||||
|
||||
func (client *RamClient) ListUsers(listParams ListUserRequest) (ListUserResponse, error) {
|
||||
var userList ListUserResponse
|
||||
err := client.Invoke("ListUsers", listParams, &userList)
|
||||
if err != nil {
|
||||
return ListUserResponse{}, err
|
||||
}
|
||||
return userList, nil
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package ram
|
||||
|
||||
/*
|
||||
CreateAccessKey()
|
||||
UpdateAccessKey()
|
||||
DeleteAccessKey()
|
||||
ListAccessKeys()
|
||||
*/
|
||||
type State string
|
||||
|
||||
type AccessKeyResponse struct {
|
||||
RamCommonResponse
|
||||
AccessKey AccessKey
|
||||
}
|
||||
|
||||
type UpdateAccessKeyRequest struct {
|
||||
UserAccessKeyId string
|
||||
Status State
|
||||
UserName string
|
||||
}
|
||||
|
||||
type AccessKeyListResponse struct {
|
||||
RamCommonResponse
|
||||
AccessKeys struct {
|
||||
AccessKey []AccessKey
|
||||
}
|
||||
}
|
||||
|
||||
func (client *RamClient) CreateAccessKey(userQuery UserQueryRequest) (AccessKeyResponse, error) {
|
||||
var accesskeyResp AccessKeyResponse
|
||||
err := client.Invoke("CreateAccessKey", userQuery, &accesskeyResp)
|
||||
if err != nil {
|
||||
return AccessKeyResponse{}, err
|
||||
}
|
||||
return accesskeyResp, nil
|
||||
}
|
||||
|
||||
func (client *RamClient) UpdateAccessKey(accessKeyRequest UpdateAccessKeyRequest) (RamCommonResponse, error) {
|
||||
var commonResp RamCommonResponse
|
||||
err := client.Invoke("UpdateAccessKey", accessKeyRequest, &commonResp)
|
||||
if err != nil {
|
||||
return RamCommonResponse{}, err
|
||||
}
|
||||
return commonResp, nil
|
||||
}
|
||||
|
||||
func (client *RamClient) DeleteAccessKey(accessKeyRequest UpdateAccessKeyRequest) (RamCommonResponse, error) {
|
||||
var commonResp RamCommonResponse
|
||||
err := client.Invoke("DeleteAccessKey", accessKeyRequest, &commonResp)
|
||||
if err != nil {
|
||||
return RamCommonResponse{}, err
|
||||
}
|
||||
return commonResp, nil
|
||||
}
|
||||
|
||||
func (client *RamClient) ListAccessKeys(userQuery UserQueryRequest) (AccessKeyListResponse, error) {
|
||||
var accessKeyListResp AccessKeyListResponse
|
||||
err := client.Invoke("ListAccessKeys", userQuery, &accessKeyListResp)
|
||||
if err != nil {
|
||||
return AccessKeyListResponse{}, err
|
||||
}
|
||||
return accessKeyListResp, nil
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package ram
|
||||
|
||||
/*
|
||||
ringtail 2016/1/19
|
||||
All RAM apis provided
|
||||
*/
|
||||
|
||||
type RamClientInterface interface {
|
||||
//ram user
|
||||
CreateUser(user UserRequest) (UserResponse, error)
|
||||
GetUser(userQuery UserQueryRequest) (UserResponse, error)
|
||||
UpdateUser(newUser UpdateUserRequest) (UserResponse, error)
|
||||
DeleteUser(userQuery UserQueryRequest) (RamCommonResponse, error)
|
||||
ListUsers(listParams ListUserRequest) (ListUserResponse, error)
|
||||
|
||||
//TODO login ram console
|
||||
CreateLoginProfile()
|
||||
GetLoginProfile()
|
||||
DeleteLoginProfile()
|
||||
UpdateLoginProfile()
|
||||
|
||||
//ram ak
|
||||
CreateAccessKey(userQuery UserQueryRequest) (AccessKeyResponse, error)
|
||||
UpdateAccessKey(accessKeyRequest UpdateAccessKeyRequest) (RamCommonResponse, error)
|
||||
DeleteAccessKey(accessKeyRequest UpdateAccessKeyRequest) (RamCommonResponse, error)
|
||||
ListAccessKeys(userQuery UserQueryRequest) (AccessKeyListResponse, error)
|
||||
|
||||
//TODO MFA
|
||||
CreateVirtualMFADevices()
|
||||
ListVirtualMFADevices()
|
||||
DeleteVirtualMFADevices()
|
||||
BindMFADevice()
|
||||
GetUserMFAInfo()
|
||||
|
||||
//TODO group
|
||||
CreateGroup()
|
||||
GetGroup()
|
||||
UpdateGroup()
|
||||
ListGroup()
|
||||
DeleteGroup()
|
||||
AddUserToGroup()
|
||||
RemoveUserFromGroup()
|
||||
ListGroupsForUser()
|
||||
ListUsersForGroup()
|
||||
|
||||
CreateRole(role RoleRequest) (RoleResponse, error)
|
||||
GetRole(roleQuery RoleQueryRequest) (RoleResponse, error)
|
||||
UpdateRole(newRole UpdateRoleRequest) (RoleResponse, error)
|
||||
ListRoles() (ListRoleResponse, error)
|
||||
DeleteRole(roleQuery RoleQueryRequest) (RamCommonResponse, error)
|
||||
|
||||
//DONE policy
|
||||
CreatePolicy(policyReq PolicyRequest) (PolicyResponse, error)
|
||||
GetPolicy(policyReq PolicyRequest) (PolicyResponse, error)
|
||||
DeletePolicy(policyReq PolicyRequest) (RamCommonResponse, error)
|
||||
ListPolicies(policyQuery PolicyQueryRequest) (PolicyQueryResponse, error)
|
||||
ListPoliciesForUser(userQuery UserQueryRequest) (PolicyListResponse, error)
|
||||
|
||||
//TODO policy
|
||||
CreatePolicyVersion(policyReq PolicyRequest) (PolicyVersionResponse, error)
|
||||
GetPolicyVersion(policyReq PolicyRequest) (PolicyVersionResponse, error)
|
||||
DeletePolicyVersion(policyReq PolicyRequest) (RamCommonResponse, error)
|
||||
ListPolicyVersions(policyReq PolicyRequest) (PolicyVersionResponse, error)
|
||||
AttachPolicyToUser(attachPolicyRequest AttachPolicyRequest) (RamCommonResponse, error)
|
||||
DetachPolicyFromUser(attachPolicyRequest AttachPolicyRequest) (RamCommonResponse, error)
|
||||
ListEnitiesForPolicy()
|
||||
SetDefaultPolicyVersion()
|
||||
ListPoliciesForGroup()
|
||||
AttachPolicyToRole(attachPolicyRequest AttachPolicyToRoleRequest) (RamCommonResponse, error)
|
||||
DetachPolicyFromRole(attachPolicyRequest AttachPolicyToRoleRequest) (RamCommonResponse, error)
|
||||
ListPoliciesForRole(roleQuery RoleQueryRequest) (PolicyListResponse, error)
|
||||
|
||||
//TODO security apis
|
||||
SetAccountAlias(accountAlias AccountAlias) (RamCommonResponse, error)
|
||||
GetAccountAlias() (AccountAliasResponse, error)
|
||||
ClearAccountAlias() (RamCommonResponse, error)
|
||||
SetPasswordPolicy(passwordPolicy PasswordPolicyRequest) (PasswordPolicyResponse, error)
|
||||
GetPasswordPolicy(accountAlias AccountAlias) (PasswordPolicyResponse, error)
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package ram
|
||||
|
||||
import (
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
// RAMDefaultEndpoint is the default API endpoint of RAM services
|
||||
RAMDefaultEndpoint = "https://ram.aliyuncs.com"
|
||||
RAMAPIVersion = "2015-05-01"
|
||||
)
|
||||
|
||||
type RamClient struct {
|
||||
common.Client
|
||||
}
|
||||
|
||||
func NewClient(accessKeyId string, accessKeySecret string) RamClientInterface {
|
||||
endpoint := os.Getenv("RAM_ENDPOINT")
|
||||
if endpoint == "" {
|
||||
endpoint = RAMDefaultEndpoint
|
||||
}
|
||||
return NewClientWithEndpoint(endpoint, accessKeyId, accessKeySecret)
|
||||
}
|
||||
|
||||
func NewClientWithEndpoint(endpoint string, accessKeyId string, accessKeySecret string) RamClientInterface {
|
||||
client := &RamClient{}
|
||||
client.Init(endpoint, RAMAPIVersion, accessKeyId, accessKeySecret)
|
||||
return client
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
package ram
|
||||
|
||||
//common errors
|
||||
var ()
|
|
@ -0,0 +1,11 @@
|
|||
package ram
|
||||
|
||||
func (client *RamClient) CreateGroup() {}
|
||||
func (client *RamClient) GetGroup() {}
|
||||
func (client *RamClient) UpdateGroup() {}
|
||||
func (client *RamClient) ListGroup() {}
|
||||
func (client *RamClient) DeleteGroup() {}
|
||||
func (client *RamClient) AddUserToGroup() {}
|
||||
func (client *RamClient) RemoveUserFromGroup() {}
|
||||
func (client *RamClient) ListGroupsForUser() {}
|
||||
func (client *RamClient) ListUsersForGroup() {}
|
|
@ -0,0 +1,11 @@
|
|||
package ram
|
||||
|
||||
func (client *RamClient) CreateVirtualMFADevices() {}
|
||||
|
||||
func (client *RamClient) ListVirtualMFADevices() {}
|
||||
|
||||
func (client *RamClient) DeleteVirtualMFADevices() {}
|
||||
|
||||
func (client *RamClient) BindMFADevice() {}
|
||||
|
||||
func (client *RamClient) GetUserMFAInfo() {}
|
|
@ -0,0 +1,195 @@
|
|||
package ram
|
||||
|
||||
type PolicyRequest struct {
|
||||
PolicyName string
|
||||
PolicyType string
|
||||
Description string
|
||||
PolicyDocument string
|
||||
SetAsDefault string
|
||||
VersionId string
|
||||
}
|
||||
type PolicyListResponse struct {
|
||||
RamCommonResponse
|
||||
Policies struct {
|
||||
Policy []Policy
|
||||
}
|
||||
}
|
||||
|
||||
type PolicyResponse struct {
|
||||
RamCommonResponse
|
||||
Policy Policy
|
||||
}
|
||||
|
||||
type PolicyQueryRequest struct {
|
||||
PolicyType string
|
||||
Marker string
|
||||
MaxItems int8
|
||||
}
|
||||
|
||||
type PolicyQueryResponse struct {
|
||||
IsTruncated bool
|
||||
Marker string
|
||||
Policies struct {
|
||||
Policy []Policy
|
||||
}
|
||||
}
|
||||
|
||||
type PolicyVersionResponse struct {
|
||||
RamCommonResponse
|
||||
IsDefaultVersion bool
|
||||
VersionId string
|
||||
CreateDate string
|
||||
PolicyDocument string
|
||||
}
|
||||
|
||||
type AttachPolicyRequest struct {
|
||||
PolicyRequest
|
||||
UserName string
|
||||
}
|
||||
|
||||
type AttachPolicyToRoleRequest struct {
|
||||
PolicyRequest
|
||||
RoleName string
|
||||
}
|
||||
|
||||
func (client *RamClient) CreatePolicy(policyReq PolicyRequest) (PolicyResponse, error) {
|
||||
var resp PolicyResponse
|
||||
err := client.Invoke("CreatePolicy", policyReq, &resp)
|
||||
if err != nil {
|
||||
return PolicyResponse{}, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (client *RamClient) GetPolicy(policyReq PolicyRequest) (PolicyResponse, error) {
|
||||
var resp PolicyResponse
|
||||
err := client.Invoke("GetPolicy", policyReq, &resp)
|
||||
if err != nil {
|
||||
return PolicyResponse{}, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (client *RamClient) DeletePolicy(policyReq PolicyRequest) (RamCommonResponse, error) {
|
||||
var resp RamCommonResponse
|
||||
err := client.Invoke("DeletePolicy", policyReq, &resp)
|
||||
if err != nil {
|
||||
return RamCommonResponse{}, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (client *RamClient) ListPolicies(policyQuery PolicyQueryRequest) (PolicyQueryResponse, error) {
|
||||
var resp PolicyQueryResponse
|
||||
err := client.Invoke("ListPolicies", policyQuery, &resp)
|
||||
if err != nil {
|
||||
return PolicyQueryResponse{}, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (client *RamClient) CreatePolicyVersion(policyReq PolicyRequest) (PolicyVersionResponse, error) {
|
||||
var resp PolicyVersionResponse
|
||||
err := client.Invoke("CreatePolicyVersion", policyReq, &resp)
|
||||
if err != nil {
|
||||
return PolicyVersionResponse{}, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (client *RamClient) GetPolicyVersion(policyReq PolicyRequest) (PolicyVersionResponse, error) {
|
||||
var resp PolicyVersionResponse
|
||||
err := client.Invoke("GetPolicyVersion", policyReq, &resp)
|
||||
if err != nil {
|
||||
return PolicyVersionResponse{}, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (client *RamClient) DeletePolicyVersion(policyReq PolicyRequest) (RamCommonResponse, error) {
|
||||
var resp RamCommonResponse
|
||||
err := client.Invoke("DeletePolicyVersion", policyReq, &resp)
|
||||
if err != nil {
|
||||
return RamCommonResponse{}, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (client *RamClient) ListPolicyVersions(policyReq PolicyRequest) (PolicyVersionResponse, error) {
|
||||
var resp PolicyVersionResponse
|
||||
err := client.Invoke("ListPolicyVersions", policyReq, &resp)
|
||||
if err != nil {
|
||||
return PolicyVersionResponse{}, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
//TODO
|
||||
func (client *RamClient) SetDefaultPolicyVersion() {}
|
||||
|
||||
func (client *RamClient) AttachPolicyToUser(attachPolicyRequest AttachPolicyRequest) (RamCommonResponse, error) {
|
||||
var resp RamCommonResponse
|
||||
err := client.Invoke("AttachPolicyToUser", attachPolicyRequest, &resp)
|
||||
if err != nil {
|
||||
return RamCommonResponse{}, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (client *RamClient) DetachPolicyFromUser(attachPolicyRequest AttachPolicyRequest) (RamCommonResponse, error) {
|
||||
var resp RamCommonResponse
|
||||
err := client.Invoke("DetachPolicyFromUser", attachPolicyRequest, &resp)
|
||||
if err != nil {
|
||||
return RamCommonResponse{}, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
//TODO
|
||||
func (client *RamClient) ListEnitiesForPolicy() {}
|
||||
|
||||
func (client *RamClient) ListPoliciesForUser(userQuery UserQueryRequest) (PolicyListResponse, error) {
|
||||
var resp PolicyListResponse
|
||||
err := client.Invoke("ListPoliciesForUser", userQuery, &resp)
|
||||
if err != nil {
|
||||
return PolicyListResponse{}, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
//
|
||||
//Role related
|
||||
//
|
||||
func (client *RamClient) AttachPolicyToRole(attachPolicyRequest AttachPolicyToRoleRequest) (RamCommonResponse, error) {
|
||||
var resp RamCommonResponse
|
||||
err := client.Invoke("AttachPolicyToRole", attachPolicyRequest, &resp)
|
||||
if err != nil {
|
||||
return RamCommonResponse{}, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (client *RamClient) DetachPolicyFromRole(attachPolicyRequest AttachPolicyToRoleRequest) (RamCommonResponse, error) {
|
||||
var resp RamCommonResponse
|
||||
err := client.Invoke("DetachPolicyFromRole", attachPolicyRequest, &resp)
|
||||
if err != nil {
|
||||
return RamCommonResponse{}, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (client *RamClient) ListPoliciesForRole(roleQuery RoleQueryRequest) (PolicyListResponse, error) {
|
||||
var resp PolicyListResponse
|
||||
err := client.Invoke("ListPoliciesForRole", roleQuery, &resp)
|
||||
if err != nil {
|
||||
return PolicyListResponse{}, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
//
|
||||
//Group related
|
||||
//
|
||||
//TODO
|
||||
//
|
||||
func (client *RamClient) ListPoliciesForGroup() {}
|
|
@ -0,0 +1,24 @@
|
|||
package ram
|
||||
|
||||
/*
|
||||
CreateLoginProfile()
|
||||
GetLoginProfile()
|
||||
DeleteLoginProfile()
|
||||
UpdateLoginProfile()
|
||||
*/
|
||||
|
||||
func (client *RamClient) CreateLoginProfile() {
|
||||
|
||||
}
|
||||
|
||||
func (client *RamClient) GetLoginProfile() {
|
||||
|
||||
}
|
||||
|
||||
func (client *RamClient) DeleteLoginProfile() {
|
||||
|
||||
}
|
||||
|
||||
func (client *RamClient) UpdateLoginProfile() {
|
||||
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package ram
|
||||
|
||||
type RoleRequest struct {
|
||||
RoleName string
|
||||
AssumeRolePolicyDocument string
|
||||
Description string
|
||||
}
|
||||
|
||||
type RoleResponse struct {
|
||||
RamCommonResponse
|
||||
Role Role
|
||||
}
|
||||
|
||||
type RoleQueryRequest struct {
|
||||
RoleName string
|
||||
}
|
||||
|
||||
type UpdateRoleRequest struct {
|
||||
RoleName string
|
||||
NewAssumeRolePolicyDocument string
|
||||
}
|
||||
|
||||
type ListRoleResponse struct {
|
||||
RamCommonResponse
|
||||
Roles struct {
|
||||
Role []Role
|
||||
}
|
||||
}
|
||||
|
||||
func (client *RamClient) CreateRole(role RoleRequest) (RoleResponse, error) {
|
||||
var roleResponse RoleResponse
|
||||
err := client.Invoke("CreateRole", role, &roleResponse)
|
||||
if err != nil {
|
||||
return RoleResponse{}, err
|
||||
}
|
||||
return roleResponse, nil
|
||||
}
|
||||
|
||||
func (client *RamClient) GetRole(roleQuery RoleQueryRequest) (RoleResponse, error) {
|
||||
var roleResponse RoleResponse
|
||||
err := client.Invoke("GetRole", roleQuery, &roleResponse)
|
||||
if err != nil {
|
||||
return RoleResponse{}, nil
|
||||
}
|
||||
return roleResponse, nil
|
||||
}
|
||||
|
||||
func (client *RamClient) UpdateRole(newRole UpdateRoleRequest) (RoleResponse, error) {
|
||||
var roleResponse RoleResponse
|
||||
err := client.Invoke("UpdateRole", newRole, &roleResponse)
|
||||
if err != nil {
|
||||
return RoleResponse{}, err
|
||||
}
|
||||
return roleResponse, nil
|
||||
}
|
||||
|
||||
func (client *RamClient) ListRoles() (ListRoleResponse, error) {
|
||||
var roleList ListRoleResponse
|
||||
err := client.Invoke("ListRoles", struct{}{}, &roleList)
|
||||
if err != nil {
|
||||
return ListRoleResponse{}, err
|
||||
}
|
||||
return roleList, nil
|
||||
}
|
||||
|
||||
func (client *RamClient) DeleteRole(roleQuery RoleQueryRequest) (RamCommonResponse, error) {
|
||||
var commonResp RamCommonResponse
|
||||
err := client.Invoke("DeleteRole", roleQuery, &commonResp)
|
||||
if err != nil {
|
||||
return RamCommonResponse{}, err
|
||||
}
|
||||
return commonResp, nil
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package ram
|
||||
|
||||
//TODO implement ram api about security
|
||||
/*
|
||||
SetAccountAlias()
|
||||
GetAccountAlias()
|
||||
ClearAccountAlias()
|
||||
SetPasswordPolicy()
|
||||
GetPasswordPolicy()
|
||||
*/
|
||||
type AccountAliasResponse struct {
|
||||
RamCommonResponse
|
||||
AccountAlias string
|
||||
}
|
||||
|
||||
type PasswordPolicyResponse struct {
|
||||
RamCommonResponse
|
||||
PasswordPolicy
|
||||
}
|
||||
|
||||
type PasswordPolicyRequest struct {
|
||||
PasswordPolicy
|
||||
}
|
||||
|
||||
func (client *RamClient) SetAccountAlias(accountalias AccountAlias) (RamCommonResponse, error) {
|
||||
return RamCommonResponse{}, nil
|
||||
}
|
||||
|
||||
func (client *RamClient) GetAccountAlias() (AccountAliasResponse, error) {
|
||||
return AccountAliasResponse{}, nil
|
||||
}
|
||||
func (client *RamClient) ClearAccountAlias() (RamCommonResponse, error) {
|
||||
return RamCommonResponse{}, nil
|
||||
}
|
||||
func (client *RamClient) SetPasswordPolicy(passwordPolicy PasswordPolicyRequest) (PasswordPolicyResponse, error) {
|
||||
return PasswordPolicyResponse{}, nil
|
||||
}
|
||||
func (client *RamClient) GetPasswordPolicy(accountAlias AccountAlias) (PasswordPolicyResponse, error) {
|
||||
return PasswordPolicyResponse{}, nil
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
package ram
|
||||
|
||||
import (
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
)
|
||||
|
||||
/*
|
||||
All common struct
|
||||
*/
|
||||
|
||||
const (
|
||||
Active State = "Active"
|
||||
Inactive State = "Inactive"
|
||||
)
|
||||
|
||||
/*
|
||||
AccountAlias
|
||||
类型:String
|
||||
必须:是
|
||||
描述:指定云账号的别名, 长度限制为3-63个字符
|
||||
限制:^[a-z0-9](([a-z0-9]|-(?!-))*[a-z0-9])?$
|
||||
*/
|
||||
type AccountAlias string
|
||||
|
||||
type UserQueryRequest struct {
|
||||
UserName string
|
||||
}
|
||||
|
||||
type User struct {
|
||||
UserId string
|
||||
UserName string
|
||||
DisplayName string
|
||||
MobilePhone string
|
||||
Email string
|
||||
Comments string
|
||||
CreateDate string
|
||||
UpdateDate string
|
||||
LastLoginDate string
|
||||
}
|
||||
|
||||
type LoginProfile struct {
|
||||
}
|
||||
|
||||
type MFADevice struct {
|
||||
}
|
||||
|
||||
type VirtualMFADevice struct {
|
||||
}
|
||||
|
||||
type AccessKey struct {
|
||||
AccessKeyId string
|
||||
AccessKeySecret string
|
||||
Status State
|
||||
CreateDate string
|
||||
}
|
||||
|
||||
type Group struct {
|
||||
}
|
||||
|
||||
type Role struct {
|
||||
RoleId string
|
||||
RoleName string
|
||||
Arn string
|
||||
Description string
|
||||
AssumeRolePolicyDocument string
|
||||
CreateDate string
|
||||
UpdateDate string
|
||||
}
|
||||
|
||||
type Policy struct {
|
||||
PolicyName string
|
||||
PolicyType string
|
||||
Description string
|
||||
DefaultVersion string
|
||||
CreateDate string
|
||||
UpdateDate string
|
||||
AttachmentCount int64
|
||||
}
|
||||
|
||||
type PolicyDocument struct {
|
||||
Statement []PolicyItem
|
||||
Version string
|
||||
}
|
||||
|
||||
type PolicyItem struct {
|
||||
Action string
|
||||
Effect string
|
||||
Resource string
|
||||
}
|
||||
|
||||
type AssumeRolePolicyDocument struct {
|
||||
Statement []AssumeRolePolicyItem
|
||||
Version string
|
||||
}
|
||||
|
||||
type AssumeRolePolicyItem struct {
|
||||
Action string
|
||||
Effect string
|
||||
Principal AssumeRolePolicyPrincpal
|
||||
}
|
||||
|
||||
type AssumeRolePolicyPrincpal struct {
|
||||
RAM []string
|
||||
}
|
||||
|
||||
/*
|
||||
"PasswordPolicy": {
|
||||
"MinimumPasswordLength": 12,
|
||||
"RequireLowercaseCharacters": true,
|
||||
"RequireUppercaseCharacters": true,
|
||||
"RequireNumbers": true,
|
||||
"RequireSymbols": true
|
||||
}
|
||||
*/
|
||||
|
||||
type PasswordPolicy struct {
|
||||
MinimumPasswordLength int8
|
||||
RequireLowercaseCharacters bool
|
||||
RequireUppercaseCharacters bool
|
||||
RequireNumbers bool
|
||||
RequireSymbols bool
|
||||
}
|
||||
|
||||
type RamCommonResponse struct {
|
||||
common.Response
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
package slb
|
||||
|
||||
import "github.com/denverdino/aliyungo/common"
|
||||
|
||||
type UploadServerCertificateArgs struct {
|
||||
RegionId common.Region
|
||||
ServerCertificate string
|
||||
ServerCertificateName string
|
||||
PrivateKey string
|
||||
}
|
||||
|
||||
type UploadServerCertificateResponse struct {
|
||||
common.Response
|
||||
ServerCertificateId string
|
||||
ServerCertificateName string
|
||||
Fingerprint string
|
||||
}
|
||||
|
||||
// UploadServerCertificate Upload server certificate
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#pub/slb/api-reference/api-servercertificate&UploadServerCertificate
|
||||
func (client *Client) UploadServerCertificate(args *UploadServerCertificateArgs) (response *UploadServerCertificateResponse, err error) {
|
||||
response = &UploadServerCertificateResponse{}
|
||||
err = client.Invoke("UploadServerCertificate", args, response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response, err
|
||||
}
|
||||
|
||||
type DeleteServerCertificateArgs struct {
|
||||
RegionId common.Region
|
||||
ServerCertificateId string
|
||||
}
|
||||
|
||||
type DeleteServerCertificateResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// DeleteServerCertificate Delete server certificate
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#pub/slb/api-reference/api-servercertificate&DeleteServerCertificate
|
||||
func (client *Client) DeleteServerCertificate(regionId common.Region, serverCertificateId string) (err error) {
|
||||
args := &DeleteServerCertificateArgs{
|
||||
RegionId: regionId,
|
||||
ServerCertificateId: serverCertificateId,
|
||||
}
|
||||
response := &DeleteServerCertificateResponse{}
|
||||
return client.Invoke("DeleteServerCertificate", args, response)
|
||||
}
|
||||
|
||||
type SetServerCertificateNameArgs struct {
|
||||
RegionId common.Region
|
||||
ServerCertificateId string
|
||||
ServerCertificateName string
|
||||
}
|
||||
|
||||
type SetServerCertificateNameResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// SetServerCertificateName Set name of server certificate
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#pub/slb/api-reference/api-servercertificate&SetServerCertificateName
|
||||
func (client *Client) SetServerCertificateName(regionId common.Region, serverCertificateId string, name string) (err error) {
|
||||
args := &SetServerCertificateNameArgs{
|
||||
RegionId: regionId,
|
||||
ServerCertificateId: serverCertificateId,
|
||||
ServerCertificateName: name,
|
||||
}
|
||||
response := &SetServerCertificateNameResponse{}
|
||||
return client.Invoke("SetServerCertificateName", args, response)
|
||||
}
|
||||
|
||||
type DescribeServerCertificatesArgs struct {
|
||||
RegionId common.Region
|
||||
ServerCertificateId string
|
||||
}
|
||||
|
||||
type ServerCertificateType struct {
|
||||
RegionId common.Region
|
||||
ServerCertificateId string
|
||||
ServerCertificateName string
|
||||
Fingerprint string
|
||||
}
|
||||
|
||||
type DescribeServerCertificatesResponse struct {
|
||||
common.Response
|
||||
ServerCertificates struct {
|
||||
ServerCertificate []ServerCertificateType
|
||||
}
|
||||
}
|
||||
|
||||
// DescribeServerCertificates Describe server certificates
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#pub/slb/api-reference/api-servercertificate&DescribeServerCertificates
|
||||
func (client *Client) DescribeServerCertificatesArgs(regionId common.Region, serverCertificateId string) (serverCertificates []ServerCertificateType, err error) {
|
||||
args := &DescribeServerCertificatesArgs{
|
||||
RegionId: regionId,
|
||||
ServerCertificateId: serverCertificateId,
|
||||
}
|
||||
response := &DescribeServerCertificatesResponse{}
|
||||
err = client.Invoke("DescribeServerCertificates", args, response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response.ServerCertificates.ServerCertificate, err
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package slb
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
common.Client
|
||||
}
|
||||
|
||||
const (
|
||||
// SLBDefaultEndpoint is the default API endpoint of SLB services
|
||||
SLBDefaultEndpoint = "https://slb.aliyuncs.com"
|
||||
SLBAPIVersion = "2014-05-15"
|
||||
|
||||
SLBServiceCode = "slb"
|
||||
)
|
||||
|
||||
// NewClient creates a new instance of ECS client
|
||||
func NewClient(accessKeyId, accessKeySecret string) *Client {
|
||||
endpoint := os.Getenv("SLB_ENDPOINT")
|
||||
if endpoint == "" {
|
||||
endpoint = SLBDefaultEndpoint
|
||||
}
|
||||
return NewClientWithEndpoint(endpoint, accessKeyId, accessKeySecret)
|
||||
}
|
||||
|
||||
func NewClientWithEndpoint(endpoint string, accessKeyId, accessKeySecret string) *Client {
|
||||
client := &Client{}
|
||||
client.Init(endpoint, SLBAPIVersion, accessKeyId, accessKeySecret)
|
||||
return client
|
||||
}
|
||||
|
||||
func NewSLBClient(accessKeyId, accessKeySecret string, regionID common.Region) *Client {
|
||||
endpoint := os.Getenv("SLB_ENDPOINT")
|
||||
if endpoint == "" {
|
||||
endpoint = SLBDefaultEndpoint
|
||||
}
|
||||
|
||||
return NewClientWithRegion(endpoint, accessKeyId, accessKeySecret, regionID)
|
||||
}
|
||||
|
||||
func NewClientWithRegion(endpoint string, accessKeyId, accessKeySecret string, regionID common.Region) *Client {
|
||||
client := &Client{}
|
||||
client.NewInit(endpoint, SLBAPIVersion, accessKeyId, accessKeySecret, SLBServiceCode, regionID)
|
||||
return client
|
||||
}
|
|
@ -0,0 +1,531 @@
|
|||
package slb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
)
|
||||
|
||||
type ListenerStatus string
|
||||
|
||||
const (
|
||||
Starting = ListenerStatus("starting")
|
||||
Running = ListenerStatus("running")
|
||||
Configuring = ListenerStatus("configuring")
|
||||
Stopping = ListenerStatus("stopping")
|
||||
Stopped = ListenerStatus("stopped")
|
||||
)
|
||||
|
||||
type SchedulerType string
|
||||
|
||||
const (
|
||||
WRRScheduler = SchedulerType("wrr")
|
||||
WLCScheduler = SchedulerType("wlc")
|
||||
)
|
||||
|
||||
type FlagType string
|
||||
|
||||
const (
|
||||
OnFlag = FlagType("on")
|
||||
OffFlag = FlagType("off")
|
||||
)
|
||||
|
||||
type StickySessionType string
|
||||
|
||||
const (
|
||||
InsertStickySessionType = StickySessionType("insert")
|
||||
ServerStickySessionType = StickySessionType("server")
|
||||
)
|
||||
|
||||
const BackendServerPort = -520
|
||||
|
||||
type HealthCheckHttpCodeType string
|
||||
|
||||
const (
|
||||
HTTP_2XX = HealthCheckHttpCodeType("http_2xx")
|
||||
HTTP_3XX = HealthCheckHttpCodeType("http_3xx")
|
||||
HTTP_4XX = HealthCheckHttpCodeType("http_4xx")
|
||||
HTTP_5XX = HealthCheckHttpCodeType("http_5xx")
|
||||
)
|
||||
|
||||
func EncodeHealthCheckHttpCodeType(healthCheckHttpCodes []HealthCheckHttpCodeType) (HealthCheckHttpCodeType, error) {
|
||||
code := ""
|
||||
|
||||
if nil == healthCheckHttpCodes || len(healthCheckHttpCodes) < 1 {
|
||||
return "", fmt.Errorf("Invalid size of healthCheckHttpCodes")
|
||||
}
|
||||
|
||||
for _, healthCheckHttpCode := range healthCheckHttpCodes {
|
||||
if strings.EqualFold(string(HTTP_2XX), string(healthCheckHttpCode)) ||
|
||||
strings.EqualFold(string(HTTP_3XX), string(healthCheckHttpCode)) ||
|
||||
strings.EqualFold(string(HTTP_4XX), string(healthCheckHttpCode)) ||
|
||||
strings.EqualFold(string(HTTP_5XX), string(healthCheckHttpCode)) {
|
||||
if "" == code {
|
||||
code = string(healthCheckHttpCode)
|
||||
} else {
|
||||
if strings.Contains(code, string(healthCheckHttpCode)) {
|
||||
return "", fmt.Errorf("Duplicates healthCheckHttpCode(%v in %v)", healthCheckHttpCode, healthCheckHttpCodes)
|
||||
}
|
||||
code += code + "," + string(healthCheckHttpCode)
|
||||
}
|
||||
} else {
|
||||
return "", fmt.Errorf("Invalid healthCheckHttpCode(%v in %v)", healthCheckHttpCode, healthCheckHttpCodes)
|
||||
}
|
||||
}
|
||||
return HealthCheckHttpCodeType(code), nil
|
||||
}
|
||||
|
||||
type CommonLoadBalancerListenerResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
type HTTPListenerType struct {
|
||||
LoadBalancerId string
|
||||
ListenerPort int
|
||||
BackendServerPort int
|
||||
Bandwidth int
|
||||
Scheduler SchedulerType
|
||||
StickySession FlagType
|
||||
StickySessionType StickySessionType
|
||||
CookieTimeout int
|
||||
Cookie string
|
||||
HealthCheck FlagType
|
||||
HealthCheckDomain string
|
||||
HealthCheckURI string
|
||||
HealthCheckConnectPort int
|
||||
HealthyThreshold int
|
||||
UnhealthyThreshold int
|
||||
HealthCheckTimeout int
|
||||
HealthCheckInterval int
|
||||
HealthCheckHttpCode HealthCheckHttpCodeType
|
||||
VServerGroupId string
|
||||
Gzip FlagType
|
||||
}
|
||||
type CreateLoadBalancerHTTPListenerArgs HTTPListenerType
|
||||
|
||||
// CreateLoadBalancerHTTPListener create HTTP listener on loadbalancer
|
||||
//
|
||||
// You can read doc at https://docs.aliyun.com/#/pub/slb/api-reference/api-related-listener&CreateLoadBalancerHTTPListener
|
||||
func (client *Client) CreateLoadBalancerHTTPListener(args *CreateLoadBalancerHTTPListenerArgs) (err error) {
|
||||
response := &CommonLoadBalancerListenerResponse{}
|
||||
err = client.Invoke("CreateLoadBalancerHTTPListener", args, response)
|
||||
return err
|
||||
}
|
||||
|
||||
type HTTPSListenerType struct {
|
||||
HTTPListenerType
|
||||
ServerCertificateId string
|
||||
}
|
||||
|
||||
type CreateLoadBalancerHTTPSListenerArgs HTTPSListenerType
|
||||
|
||||
// CreateLoadBalancerHTTPSListener create HTTPS listener on loadbalancer
|
||||
//
|
||||
// You can read doc at https://docs.aliyun.com/#/pub/slb/api-reference/api-related-listener&CreateLoadBalancerHTTPSListener
|
||||
func (client *Client) CreateLoadBalancerHTTPSListener(args *CreateLoadBalancerHTTPSListenerArgs) (err error) {
|
||||
response := &CommonLoadBalancerListenerResponse{}
|
||||
err = client.Invoke("CreateLoadBalancerHTTPSListener", args, response)
|
||||
return err
|
||||
}
|
||||
|
||||
type HealthCheckType string
|
||||
|
||||
const (
|
||||
TCPHealthCheckType = HealthCheckType("tcp")
|
||||
HTTPHealthCheckType = HealthCheckType("http")
|
||||
)
|
||||
|
||||
type TCPListenerType struct {
|
||||
LoadBalancerId string
|
||||
ListenerPort int
|
||||
BackendServerPort int
|
||||
Bandwidth int
|
||||
Scheduler SchedulerType
|
||||
PersistenceTimeout int
|
||||
HealthCheckType HealthCheckType
|
||||
HealthCheckDomain string
|
||||
HealthCheckURI string
|
||||
HealthCheckConnectPort int
|
||||
HealthyThreshold int
|
||||
UnhealthyThreshold int
|
||||
HealthCheckConnectTimeout int
|
||||
HealthCheckInterval int
|
||||
HealthCheckHttpCode HealthCheckHttpCodeType
|
||||
VServerGroupId string
|
||||
}
|
||||
|
||||
type CreateLoadBalancerTCPListenerArgs TCPListenerType
|
||||
|
||||
// CreateLoadBalancerTCPListener create TCP listener on loadbalancer
|
||||
//
|
||||
// You can read doc at https://docs.aliyun.com/#/pub/slb/api-reference/api-related-listener&CreateLoadBalancerTCPListener
|
||||
func (client *Client) CreateLoadBalancerTCPListener(args *CreateLoadBalancerTCPListenerArgs) (err error) {
|
||||
response := &CommonLoadBalancerListenerResponse{}
|
||||
err = client.Invoke("CreateLoadBalancerTCPListener", args, response)
|
||||
return err
|
||||
}
|
||||
|
||||
type UDPListenerType struct {
|
||||
LoadBalancerId string
|
||||
ListenerPort int
|
||||
BackendServerPort int
|
||||
Bandwidth int
|
||||
Scheduler SchedulerType
|
||||
PersistenceTimeout int
|
||||
HealthCheckConnectPort int
|
||||
HealthyThreshold int
|
||||
UnhealthyThreshold int
|
||||
HealthCheckConnectTimeout int
|
||||
HealthCheckInterval int
|
||||
VServerGroupId string
|
||||
}
|
||||
type CreateLoadBalancerUDPListenerArgs UDPListenerType
|
||||
|
||||
// CreateLoadBalancerUDPListener create UDP listener on loadbalancer
|
||||
//
|
||||
// You can read doc at https://docs.aliyun.com/#/pub/slb/api-reference/api-related-listener&CreateLoadBalancerUDPListener
|
||||
func (client *Client) CreateLoadBalancerUDPListener(args *CreateLoadBalancerUDPListenerArgs) (err error) {
|
||||
response := &CommonLoadBalancerListenerResponse{}
|
||||
err = client.Invoke("CreateLoadBalancerUDPListener", args, response)
|
||||
return err
|
||||
}
|
||||
|
||||
type CommonLoadBalancerListenerArgs struct {
|
||||
LoadBalancerId string
|
||||
ListenerPort int
|
||||
}
|
||||
|
||||
// DeleteLoadBalancerListener Delete listener
|
||||
//
|
||||
// You can read doc at https://docs.aliyun.com/#/pub/slb/api-reference/api-related-listener&DeleteLoadBalancerListener
|
||||
func (client *Client) DeleteLoadBalancerListener(loadBalancerId string, port int) (err error) {
|
||||
args := &CommonLoadBalancerListenerArgs{
|
||||
LoadBalancerId: loadBalancerId,
|
||||
ListenerPort: port,
|
||||
}
|
||||
response := &CommonLoadBalancerListenerResponse{}
|
||||
err = client.Invoke("DeleteLoadBalancerListener", args, response)
|
||||
return err
|
||||
}
|
||||
|
||||
// StartLoadBalancerListener Start listener
|
||||
//
|
||||
// You can read doc at https://docs.aliyun.com/#/pub/slb/api-reference/api-related-listener&StartLoadBalancerListener
|
||||
func (client *Client) StartLoadBalancerListener(loadBalancerId string, port int) (err error) {
|
||||
args := &CommonLoadBalancerListenerArgs{
|
||||
LoadBalancerId: loadBalancerId,
|
||||
ListenerPort: port,
|
||||
}
|
||||
response := &CommonLoadBalancerListenerResponse{}
|
||||
err = client.Invoke("StartLoadBalancerListener", args, response)
|
||||
return err
|
||||
}
|
||||
|
||||
// StopLoadBalancerListener Stop listener
|
||||
//
|
||||
// You can read doc at https://docs.aliyun.com/#/pub/slb/api-reference/api-related-listener&StopLoadBalancerListener
|
||||
func (client *Client) StopLoadBalancerListener(loadBalancerId string, port int) (err error) {
|
||||
args := &CommonLoadBalancerListenerArgs{
|
||||
LoadBalancerId: loadBalancerId,
|
||||
ListenerPort: port,
|
||||
}
|
||||
response := &CommonLoadBalancerListenerResponse{}
|
||||
err = client.Invoke("StopLoadBalancerListener", args, response)
|
||||
return err
|
||||
}
|
||||
|
||||
type AccessControlStatus string
|
||||
|
||||
const (
|
||||
OpenWhileList = AccessControlStatus("open_white_list")
|
||||
Close = AccessControlStatus("close")
|
||||
)
|
||||
|
||||
type SetListenerAccessControlStatusArgs struct {
|
||||
LoadBalancerId string
|
||||
ListenerPort int
|
||||
AccessControlStatus AccessControlStatus
|
||||
}
|
||||
|
||||
// SetListenerAccessControlStatus Set listener access control status
|
||||
//
|
||||
// You can read doc at https://docs.aliyun.com/#/pub/slb/api-reference/api-related-listener&SetListenerAccessControlStatus
|
||||
func (client *Client) SetListenerAccessControlStatus(loadBalancerId string, port int, status AccessControlStatus) (err error) {
|
||||
args := &SetListenerAccessControlStatusArgs{
|
||||
LoadBalancerId: loadBalancerId,
|
||||
ListenerPort: port,
|
||||
AccessControlStatus: status,
|
||||
}
|
||||
response := &CommonLoadBalancerListenerResponse{}
|
||||
err = client.Invoke("SetListenerAccessControlStatus", args, response)
|
||||
return err
|
||||
}
|
||||
|
||||
type CommonListenerWhiteListItemArgs struct {
|
||||
LoadBalancerId string
|
||||
ListenerPort int
|
||||
SourceItems string
|
||||
}
|
||||
|
||||
// AddListenerWhiteListItem Add listener white-list item
|
||||
//
|
||||
// You can read doc at https://docs.aliyun.com/#/pub/slb/api-reference/api-related-listener&AddListenerWhiteListItem
|
||||
func (client *Client) AddListenerWhiteListItem(loadBalancerId string, port int, sourceItems string) (err error) {
|
||||
args := &CommonListenerWhiteListItemArgs{
|
||||
LoadBalancerId: loadBalancerId,
|
||||
ListenerPort: port,
|
||||
SourceItems: sourceItems,
|
||||
}
|
||||
response := &CommonLoadBalancerListenerResponse{}
|
||||
err = client.Invoke("AddListenerWhiteListItem", args, response)
|
||||
return err
|
||||
}
|
||||
|
||||
// RemoveListenerWhiteListItem Remove listener white-list item
|
||||
//
|
||||
// You can read doc at https://docs.aliyun.com/#/pub/slb/api-reference/api-related-listener&RemoveListenerWhiteListItem
|
||||
func (client *Client) RemoveListenerWhiteListItem(loadBalancerId string, port int, sourceItems string) (err error) {
|
||||
args := &CommonListenerWhiteListItemArgs{
|
||||
LoadBalancerId: loadBalancerId,
|
||||
ListenerPort: port,
|
||||
SourceItems: sourceItems,
|
||||
}
|
||||
response := &CommonLoadBalancerListenerResponse{}
|
||||
err = client.Invoke("RemoveListenerWhiteListItem", args, response)
|
||||
return err
|
||||
}
|
||||
|
||||
type SetLoadBalancerHTTPListenerAttributeArgs CreateLoadBalancerHTTPListenerArgs
|
||||
|
||||
// SetLoadBalancerHTTPListenerAttribute Set HTTP listener attribute
|
||||
//
|
||||
// You can read doc at https://docs.aliyun.com/#/pub/slb/api-reference/api-related-listener&SetLoadBalancerHTTPListenerAttribute
|
||||
func (client *Client) SetLoadBalancerHTTPListenerAttribute(args *SetLoadBalancerHTTPListenerAttributeArgs) (err error) {
|
||||
response := &CommonLoadBalancerListenerResponse{}
|
||||
err = client.Invoke("SetLoadBalancerHTTPListenerAttribute", args, response)
|
||||
return err
|
||||
}
|
||||
|
||||
type SetLoadBalancerHTTPSListenerAttributeArgs CreateLoadBalancerHTTPSListenerArgs
|
||||
|
||||
// SetLoadBalancerHTTPSListenerAttribute Set HTTPS listener attribute
|
||||
//
|
||||
// You can read doc at https://docs.aliyun.com/#/pub/slb/api-reference/api-related-listener&SetLoadBalancerHTTPSListenerAttribute
|
||||
func (client *Client) SetLoadBalancerHTTPSListenerAttribute(args *SetLoadBalancerHTTPSListenerAttributeArgs) (err error) {
|
||||
response := &CommonLoadBalancerListenerResponse{}
|
||||
err = client.Invoke("SetLoadBalancerHTTPSListenerAttribute", args, response)
|
||||
return err
|
||||
}
|
||||
|
||||
type SetLoadBalancerTCPListenerAttributeArgs CreateLoadBalancerTCPListenerArgs
|
||||
|
||||
// SetLoadBalancerTCPListenerAttribute Set TCP listener attribute
|
||||
//
|
||||
// You can read doc at https://docs.aliyun.com/#/pub/slb/api-reference/api-related-listener&SetLoadBalancerTCPListenerAttribute
|
||||
func (client *Client) SetLoadBalancerTCPListenerAttribute(args *SetLoadBalancerTCPListenerAttributeArgs) (err error) {
|
||||
response := &CommonLoadBalancerListenerResponse{}
|
||||
err = client.Invoke("SetLoadBalancerTCPListenerAttribute", args, response)
|
||||
return err
|
||||
}
|
||||
|
||||
type SetLoadBalancerUDPListenerAttributeArgs CreateLoadBalancerUDPListenerArgs
|
||||
|
||||
// SetLoadBalancerUDPListenerAttribute Set UDP listener attribute
|
||||
//
|
||||
// You can read doc at https://docs.aliyun.com/#/pub/slb/api-reference/api-related-listener&SetLoadBalancerUDPListenerAttribute
|
||||
func (client *Client) SetLoadBalancerUDPListenerAttribute(args *SetLoadBalancerUDPListenerAttributeArgs) (err error) {
|
||||
response := &CommonLoadBalancerListenerResponse{}
|
||||
err = client.Invoke("SetLoadBalancerUDPListenerAttribute", args, response)
|
||||
return err
|
||||
}
|
||||
|
||||
type DescribeLoadBalancerListenerAttributeResponse struct {
|
||||
common.Response
|
||||
Status ListenerStatus
|
||||
}
|
||||
|
||||
type DescribeLoadBalancerHTTPListenerAttributeResponse struct {
|
||||
DescribeLoadBalancerListenerAttributeResponse
|
||||
HTTPListenerType
|
||||
}
|
||||
|
||||
// DescribeLoadBalancerHTTPListenerAttribute Describe HTTP listener attribute
|
||||
//
|
||||
// You can read doc at https://docs.aliyun.com/#/pub/slb/api-reference/api-related-listener&DescribeLoadBalancerHTTPListenerAttribute
|
||||
func (client *Client) DescribeLoadBalancerHTTPListenerAttribute(loadBalancerId string, port int) (response *DescribeLoadBalancerHTTPListenerAttributeResponse, err error) {
|
||||
args := &CommonLoadBalancerListenerArgs{
|
||||
LoadBalancerId: loadBalancerId,
|
||||
ListenerPort: port,
|
||||
}
|
||||
response = &DescribeLoadBalancerHTTPListenerAttributeResponse{}
|
||||
err = client.Invoke("DescribeLoadBalancerHTTPListenerAttribute", args, response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response, err
|
||||
}
|
||||
|
||||
type DescribeLoadBalancerHTTPSListenerAttributeResponse struct {
|
||||
DescribeLoadBalancerListenerAttributeResponse
|
||||
HTTPSListenerType
|
||||
}
|
||||
|
||||
// DescribeLoadBalancerHTTPSListenerAttribute Describe HTTPS listener attribute
|
||||
//
|
||||
// You can read doc at https://docs.aliyun.com/#/pub/slb/api-reference/api-related-listener&DescribeLoadBalancerHTTPSListenerAttribute
|
||||
func (client *Client) DescribeLoadBalancerHTTPSListenerAttribute(loadBalancerId string, port int) (response *DescribeLoadBalancerHTTPSListenerAttributeResponse, err error) {
|
||||
args := &CommonLoadBalancerListenerArgs{
|
||||
LoadBalancerId: loadBalancerId,
|
||||
ListenerPort: port,
|
||||
}
|
||||
response = &DescribeLoadBalancerHTTPSListenerAttributeResponse{}
|
||||
err = client.Invoke("DescribeLoadBalancerHTTPSListenerAttribute", args, response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response, err
|
||||
}
|
||||
|
||||
type DescribeLoadBalancerTCPListenerAttributeResponse struct {
|
||||
DescribeLoadBalancerListenerAttributeResponse
|
||||
TCPListenerType
|
||||
}
|
||||
|
||||
// DescribeLoadBalancerTCPListenerAttribute Describe TCP listener attribute
|
||||
//
|
||||
// You can read doc at https://docs.aliyun.com/#/pub/slb/api-reference/api-related-listener&DescribeLoadBalancerTCPListenerAttribute
|
||||
func (client *Client) DescribeLoadBalancerTCPListenerAttribute(loadBalancerId string, port int) (response *DescribeLoadBalancerTCPListenerAttributeResponse, err error) {
|
||||
args := &CommonLoadBalancerListenerArgs{
|
||||
LoadBalancerId: loadBalancerId,
|
||||
ListenerPort: port,
|
||||
}
|
||||
response = &DescribeLoadBalancerTCPListenerAttributeResponse{}
|
||||
err = client.Invoke("DescribeLoadBalancerTCPListenerAttribute", args, response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response, err
|
||||
}
|
||||
|
||||
type DescribeLoadBalancerUDPListenerAttributeResponse struct {
|
||||
DescribeLoadBalancerListenerAttributeResponse
|
||||
UDPListenerType
|
||||
}
|
||||
|
||||
// DescribeLoadBalancerUDPListenerAttribute Describe UDP listener attribute
|
||||
//
|
||||
// You can read doc at https://docs.aliyun.com/#/pub/slb/api-reference/api-related-listener&DescribeLoadBalancerUDPListenerAttribute
|
||||
func (client *Client) DescribeLoadBalancerUDPListenerAttribute(loadBalancerId string, port int) (response *DescribeLoadBalancerUDPListenerAttributeResponse, err error) {
|
||||
args := &CommonLoadBalancerListenerArgs{
|
||||
LoadBalancerId: loadBalancerId,
|
||||
ListenerPort: port,
|
||||
}
|
||||
response = &DescribeLoadBalancerUDPListenerAttributeResponse{}
|
||||
err = client.Invoke("DescribeLoadBalancerUDPListenerAttribute", args, response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response, err
|
||||
}
|
||||
|
||||
type ListenerType string
|
||||
|
||||
const (
|
||||
UDP = ListenerType("UDP")
|
||||
TCP = ListenerType("TCP")
|
||||
HTTP = ListenerType("HTTP")
|
||||
HTTPS = ListenerType("HTTPS")
|
||||
)
|
||||
|
||||
const DefaultWaitForInterval = 5 //5 seconds
|
||||
const DefaultTimeout = 60 //60 seconds
|
||||
|
||||
// WaitForListener waits for listener to given status
|
||||
func (client *Client) WaitForListener(loadBalancerId string, port int, listenerType ListenerType) (status ListenerStatus, err error) {
|
||||
timeout := DefaultTimeout
|
||||
|
||||
args := &CommonLoadBalancerListenerArgs{
|
||||
LoadBalancerId: loadBalancerId,
|
||||
ListenerPort: port,
|
||||
}
|
||||
|
||||
method := fmt.Sprintf("DescribeLoadBalancer%sListenerAttribute", listenerType)
|
||||
response := &DescribeLoadBalancerListenerAttributeResponse{}
|
||||
|
||||
for {
|
||||
timeout = timeout - DefaultWaitForInterval
|
||||
if timeout <= 0 {
|
||||
return response.Status, common.GetClientErrorFromString("Timeout")
|
||||
}
|
||||
time.Sleep(DefaultWaitForInterval * time.Second)
|
||||
//Sleep first to ensure the previous request is sent
|
||||
err = client.Invoke(method, args, response)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if response.Status == Running || response.Status == Stopped {
|
||||
break
|
||||
}
|
||||
}
|
||||
return response.Status, nil
|
||||
}
|
||||
|
||||
// WaitForListener waits for listener to given status
|
||||
func (client *Client) WaitForListenerAsyn(loadBalancerId string, port int, listenerType ListenerType, status ListenerStatus, timeout int) error {
|
||||
if timeout <= 0 {
|
||||
timeout = DefaultTimeout
|
||||
}
|
||||
|
||||
args := &CommonLoadBalancerListenerArgs{
|
||||
LoadBalancerId: loadBalancerId,
|
||||
ListenerPort: port,
|
||||
}
|
||||
|
||||
method := fmt.Sprintf("DescribeLoadBalancer%sListenerAttribute", listenerType)
|
||||
response := &DescribeLoadBalancerListenerAttributeResponse{}
|
||||
|
||||
for {
|
||||
err := client.Invoke(method, args, response)
|
||||
e, _ := err.(*common.Error)
|
||||
if e != nil {
|
||||
if e.StatusCode == 404 || e.Code == "InvalidLoadBalancerId.NotFound" {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
} else if response != nil && response.Status == status {
|
||||
//TODO
|
||||
break
|
||||
}
|
||||
timeout = timeout - DefaultWaitForInterval
|
||||
if timeout <= 0 {
|
||||
return common.GetClientErrorFromString("Timeout")
|
||||
}
|
||||
time.Sleep(DefaultWaitForInterval * time.Second)
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type DescribeListenerAccessControlAttributeResponse struct {
|
||||
common.Response
|
||||
AccessControlStatus AccessControlStatus
|
||||
SourceItems string
|
||||
}
|
||||
|
||||
// DescribeListenerAccessControlAttribute Describe listener access control attribute
|
||||
//
|
||||
// You can read doc at https://docs.aliyun.com/#/pub/slb/api-reference/api-related-listener&DescribeListenerAccessControlAttribute
|
||||
func (client *Client) DescribeListenerAccessControlAttribute(loadBalancerId string, port int) (response *DescribeListenerAccessControlAttributeResponse, err error) {
|
||||
args := &CommonLoadBalancerListenerArgs{
|
||||
LoadBalancerId: loadBalancerId,
|
||||
ListenerPort: port,
|
||||
}
|
||||
response = &DescribeListenerAccessControlAttributeResponse{}
|
||||
err = client.Invoke("DescribeListenerAccessControlAttribute", args, response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response, err
|
||||
}
|
|
@ -0,0 +1,238 @@
|
|||
package slb
|
||||
|
||||
import (
|
||||
"github.com/denverdino/aliyungo/common"
|
||||
"github.com/denverdino/aliyungo/util"
|
||||
)
|
||||
|
||||
type AddressType string
|
||||
|
||||
const (
|
||||
InternetAddressType = AddressType("internet")
|
||||
IntranetAddressType = AddressType("intranet")
|
||||
)
|
||||
|
||||
type InternetChargeType string
|
||||
|
||||
const (
|
||||
PayByBandwidth = InternetChargeType("paybybandwidth")
|
||||
PayByTraffic = InternetChargeType("paybytraffic")
|
||||
)
|
||||
|
||||
type CreateLoadBalancerArgs struct {
|
||||
RegionId common.Region
|
||||
LoadBalancerName string
|
||||
AddressType AddressType
|
||||
VSwitchId string
|
||||
InternetChargeType InternetChargeType
|
||||
Bandwidth int
|
||||
ClientToken string
|
||||
}
|
||||
|
||||
type CreateLoadBalancerResponse struct {
|
||||
common.Response
|
||||
LoadBalancerId string
|
||||
Address string
|
||||
NetworkType string
|
||||
VpcId string
|
||||
VSwitchId string
|
||||
LoadBalancerName string
|
||||
}
|
||||
|
||||
// CreateLoadBalancer create loadbalancer
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/slb/api-reference/api-related-loadbalancer&CreateLoadBalancer
|
||||
func (client *Client) CreateLoadBalancer(args *CreateLoadBalancerArgs) (response *CreateLoadBalancerResponse, err error) {
|
||||
response = &CreateLoadBalancerResponse{}
|
||||
err = client.Invoke("CreateLoadBalancer", args, response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response, err
|
||||
}
|
||||
|
||||
type DeleteLoadBalancerArgs struct {
|
||||
LoadBalancerId string
|
||||
}
|
||||
|
||||
type DeleteLoadBalancerResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// DeleteLoadBalancer delete loadbalancer
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/slb/api-reference/api-related-loadbalancer&DeleteLoadBalancer
|
||||
func (client *Client) DeleteLoadBalancer(loadBalancerId string) (err error) {
|
||||
args := &DeleteLoadBalancerArgs{
|
||||
LoadBalancerId: loadBalancerId,
|
||||
}
|
||||
response := &DeleteLoadBalancerResponse{}
|
||||
err = client.Invoke("DeleteLoadBalancer", args, response)
|
||||
return err
|
||||
}
|
||||
|
||||
type ModifyLoadBalancerInternetSpecArgs struct {
|
||||
LoadBalancerId string
|
||||
InternetChargeType InternetChargeType
|
||||
Bandwidth int
|
||||
}
|
||||
|
||||
type ModifyLoadBalancerInternetSpecResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// ModifyLoadBalancerInternetSpec Modify loadbalancer internet spec
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/slb/api-reference/api-related-loadbalancer&ModifyLoadBalancerInternetSpec
|
||||
|
||||
func (client *Client) ModifyLoadBalancerInternetSpec(args *ModifyLoadBalancerInternetSpecArgs) (err error) {
|
||||
response := &ModifyLoadBalancerInternetSpecResponse{}
|
||||
err = client.Invoke("ModifyLoadBalancerInternetSpec", args, response)
|
||||
return err
|
||||
}
|
||||
|
||||
type Status string
|
||||
|
||||
const InactiveStatus = Status("inactive")
|
||||
const ActiveStatus = Status("active")
|
||||
const LockedStatus = Status("locked")
|
||||
|
||||
type SetLoadBalancerStatusArgs struct {
|
||||
LoadBalancerId string
|
||||
LoadBalancerStatus Status
|
||||
}
|
||||
|
||||
type SetLoadBalancerStatusResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// SetLoadBalancerStatus Set loadbalancer status
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/slb/api-reference/api-related-loadbalancer&SetLoadBalancerStatus
|
||||
|
||||
func (client *Client) SetLoadBalancerStatus(loadBalancerId string, status Status) (err error) {
|
||||
args := &SetLoadBalancerStatusArgs{
|
||||
LoadBalancerId: loadBalancerId,
|
||||
LoadBalancerStatus: status,
|
||||
}
|
||||
response := &SetLoadBalancerStatusResponse{}
|
||||
err = client.Invoke("SetLoadBalancerStatus", args, response)
|
||||
return err
|
||||
}
|
||||
|
||||
type SetLoadBalancerNameArgs struct {
|
||||
LoadBalancerId string
|
||||
LoadBalancerName string
|
||||
}
|
||||
|
||||
type SetLoadBalancerNameResponse struct {
|
||||
common.Response
|
||||
}
|
||||
|
||||
// SetLoadBalancerName Set loadbalancer name
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/slb/api-reference/api-related-loadbalancer&SetLoadBalancerName
|
||||
|
||||
func (client *Client) SetLoadBalancerName(loadBalancerId string, name string) (err error) {
|
||||
args := &SetLoadBalancerNameArgs{
|
||||
LoadBalancerId: loadBalancerId,
|
||||
LoadBalancerName: name,
|
||||
}
|
||||
response := &SetLoadBalancerNameResponse{}
|
||||
err = client.Invoke("SetLoadBalancerName", args, response)
|
||||
return err
|
||||
}
|
||||
|
||||
type DescribeLoadBalancersArgs struct {
|
||||
RegionId common.Region
|
||||
LoadBalancerId string
|
||||
LoadBalancerName string
|
||||
AddressType AddressType
|
||||
NetworkType string
|
||||
VpcId string
|
||||
VSwitchId string
|
||||
Address string
|
||||
InternetChargeType InternetChargeType
|
||||
ServerId string
|
||||
}
|
||||
|
||||
type ListenerPortAndProtocolType struct {
|
||||
ListenerPort int
|
||||
ListenerProtocol string
|
||||
}
|
||||
|
||||
type BackendServerType struct {
|
||||
ServerId string
|
||||
Weight int
|
||||
}
|
||||
|
||||
type LoadBalancerType struct {
|
||||
LoadBalancerId string
|
||||
LoadBalancerName string
|
||||
LoadBalancerStatus string
|
||||
Address string
|
||||
RegionId common.Region
|
||||
RegionIdAlias string
|
||||
AddressType AddressType
|
||||
VSwitchId string
|
||||
VpcId string
|
||||
NetworkType string
|
||||
Bandwidth int
|
||||
InternetChargeType InternetChargeType
|
||||
CreateTime string //Why not ISO 6801
|
||||
CreateTimeStamp util.ISO6801Time
|
||||
ListenerPorts struct {
|
||||
ListenerPort []int
|
||||
}
|
||||
ListenerPortsAndProtocol struct {
|
||||
ListenerPortAndProtocol []ListenerPortAndProtocolType
|
||||
}
|
||||
BackendServers struct {
|
||||
BackendServer []BackendServerType
|
||||
}
|
||||
}
|
||||
|
||||
type DescribeLoadBalancersResponse struct {
|
||||
common.Response
|
||||
LoadBalancers struct {
|
||||
LoadBalancer []LoadBalancerType
|
||||
}
|
||||
}
|
||||
|
||||
// DescribeLoadBalancers Describe loadbalancers
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/slb/api-reference/api-related-loadbalancer&DescribeLoadBalancers
|
||||
|
||||
func (client *Client) DescribeLoadBalancers(args *DescribeLoadBalancersArgs) (loadBalancers []LoadBalancerType, err error) {
|
||||
response := &DescribeLoadBalancersResponse{}
|
||||
err = client.Invoke("DescribeLoadBalancers", args, response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response.LoadBalancers.LoadBalancer, err
|
||||
}
|
||||
|
||||
type DescribeLoadBalancerAttributeArgs struct {
|
||||
LoadBalancerId string
|
||||
}
|
||||
|
||||
type DescribeLoadBalancerAttributeResponse struct {
|
||||
common.Response
|
||||
LoadBalancerType
|
||||
}
|
||||
|
||||
// DescribeLoadBalancerAttribute Describe loadbalancer attribute
|
||||
//
|
||||
// You can read doc at http://docs.aliyun.com/#/pub/slb/api-reference/api-related-loadbalancer&DescribeLoadBalancerAttribute
|
||||
|
||||
func (client *Client) DescribeLoadBalancerAttribute(loadBalancerId string) (loadBalancer *LoadBalancerType, err error) {
|
||||
args := &DescribeLoadBalancersArgs{
|
||||
LoadBalancerId: loadBalancerId,
|
||||
}
|
||||
response := &DescribeLoadBalancerAttributeResponse{}
|
||||
err = client.Invoke("DescribeLoadBalancerAttribute", args, response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &response.LoadBalancerType, err
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue