Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add qingcloud_instance_volume_attachment resource #187

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 26 additions & 16 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,36 +1,46 @@
name: goreleaser

# This GitHub action can publish assets for release when a tag is created.
# Currently its setup to run on any tag that matches the pattern "v*" (ie. v0.1.0).
#
# This uses an action (hashicorp/ghaction-import-gpg) that assumes you set your
# private key in the `GPG_PRIVATE_KEY` secret and passphrase in the `PASSPHRASE`
# secret. If you would rather own your own GPG handling, please fork this action
# or use an alternative one for key handling.
#
# You will need to pass the `--batch` flag to `gpg` in your signing step
# in `goreleaser` to indicate this is being used in a non-interactive mode.
#
name: release
on:
push:
tags:
- 'v*'

jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
-
name: Checkout
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
-
name: Set up Go
- name: Unshallow
run: git fetch --prune --unshallow
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.14
-
name: Import GPG key
go-version: 1.16
- name: Describe plugin
id: plugin_describe
run: echo "::set-output name=api_version::$(go run . describe | jq -r '.api_version')"
- name: Import GPG key
id: import_gpg
uses: crazy-max/ghaction-import-gpg@v2
uses: hashicorp/ghaction-import-gpg@v2.1.0
env:
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
-
name: Run GoReleaser
PASSPHRASE: ${{ secrets.PASSPHRASE }}
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
version: latest
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
API_VERSION: ${{ steps.plugin_describe.outputs.api_version }}
15 changes: 15 additions & 0 deletions qingcloud/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package qingcloud

import (
"fmt"
"strings"
)

func ParseResourceId(id string, length int) (parts []string, err error) {
parts = strings.Split(id, ":")

if len(parts) != length {
err = WrapError(fmt.Errorf("Invalid Resource Id %s. Expected parts' length %d, got %d", id, length, len(parts)))
}
return parts, err
}
3 changes: 3 additions & 0 deletions qingcloud/constants.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package qingcloud

type Status string

const (
qingcloudResourceTypeInstance = "instance"
qingcloudResourceTypeVolume = "volume"
Expand All @@ -11,6 +13,7 @@ const (
qingcloudResourceTypeLoadBalancer = "loadbalancer"

StatusActive = "active"
InUse = Status("in-use")

DEFAULT_ZONE = "pek3a"
DEFAULT_ENDPOINT = "https://api.qingcloud.com:443/iaas"
Expand Down
53 changes: 53 additions & 0 deletions qingcloud/errors.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,56 @@
package qingcloud

import (
"fmt"
"log"
"runtime"
"strings"
)

const SERVERBUSY = 5100

func WrapError(cause error) error {
if cause == nil {
return nil
}
_, filepath, line, ok := runtime.Caller(1)
if !ok {
log.Printf("\u001B[31m[ERROR]\u001B[0m runtime.Caller error in WrapError.")
return WrapComplexError(cause, nil, "", -1)
}
parts := strings.Split(filepath, "/")
if len(parts) > 3 {
filepath = strings.Join(parts[len(parts)-3:], "/")
}
return WrapComplexError(cause, nil, filepath, line)
}

func WrapComplexError(cause, err error, filepath string, fileline int) error {
return &ComplexError{
Cause: cause,
Err: err,
Path: filepath,
Line: fileline,
}
}

type ComplexError struct {
Cause error
Err error
Path string
Line int
}

func (e ComplexError) Error() string {
if e.Cause == nil {
e.Cause = Error("<nil cause>")
}
if e.Err == nil {
return fmt.Sprintf("\u001B[31m[ERROR]\u001B[0m %s:%d:\n%s", e.Path, e.Line, e.Cause.Error())
}
return fmt.Sprintf("\u001B[31m[ERROR]\u001B[0m %s:%d: %s:\n%s", e.Path, e.Line, e.Err.Error(), e.Cause.Error())
}

func Error(msg string, args ...interface{}) error {
return fmt.Errorf(msg, args...)
}
29 changes: 15 additions & 14 deletions qingcloud/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,21 @@ func Provider() terraform.ResourceProvider {
"qingcloud_vpn_cert": dataSourceQingcloudVpnCert(),
},
ResourcesMap: map[string]*schema.Resource{
"qingcloud_eip": resourceQingcloudEip(),
"qingcloud_keypair": resourceQingcloudKeypair(),
"qingcloud_security_group": resourceQingcloudSecurityGroup(),
"qingcloud_security_group_rule": resourceQingcloudSecurityGroupRule(),
"qingcloud_vxnet": resourceQingcloudVxnet(),
"qingcloud_vpc": resourceQingcloudVpc(),
"qingcloud_instance": resourceQingcloudInstance(),
"qingcloud_volume": resourceQingcloudVolume(),
"qingcloud_tag": resourceQingcloudTag(),
"qingcloud_vpc_static": resourceQingcloudVpcStatic(),
"qingcloud_loadbalancer": resourceQingcloudLoadBalancer(),
"qingcloud_loadbalancer_listener": resourceQingcloudLoadBalancerListener(),
"qingcloud_loadbalancer_backend": resourceQingcloudLoadBalancerBackend(),
"qingcloud_server_certificate": resourceQingcloudServerCertificate(),
"qingcloud_eip": resourceQingcloudEip(),
"qingcloud_keypair": resourceQingcloudKeypair(),
"qingcloud_security_group": resourceQingcloudSecurityGroup(),
"qingcloud_security_group_rule": resourceQingcloudSecurityGroupRule(),
"qingcloud_vxnet": resourceQingcloudVxnet(),
"qingcloud_vpc": resourceQingcloudVpc(),
"qingcloud_instance": resourceQingcloudInstance(),
"qingcloud_instance_volume_attachment": resourceQingcloudInstanceVolumeAttachment(),
"qingcloud_volume": resourceQingcloudVolume(),
"qingcloud_tag": resourceQingcloudTag(),
"qingcloud_vpc_static": resourceQingcloudVpcStatic(),
"qingcloud_loadbalancer": resourceQingcloudLoadBalancer(),
"qingcloud_loadbalancer_listener": resourceQingcloudLoadBalancerListener(),
"qingcloud_loadbalancer_backend": resourceQingcloudLoadBalancerBackend(),
"qingcloud_server_certificate": resourceQingcloudServerCertificate(),
},
ConfigureFunc: providerConfigure,
}
Expand Down
22 changes: 16 additions & 6 deletions qingcloud/resource_qingcloud_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package qingcloud

import (
"fmt"

"github.com/hashicorp/terraform/helper/schema"
qc "github.com/yunify/qingcloud-sdk-go/service"
)
Expand Down Expand Up @@ -66,11 +65,10 @@ func resourceQingcloudInstance() *schema.Resource {
Default: 1024,
},
resourceInstanceClass: {
Type: schema.TypeInt,
ForceNew: true,
Optional: true,
ValidateFunc: withinArrayInt(0, 1, 2, 3, 4, 5, 6, 100, 101, 200, 201, 300, 301),
Default: 0,
Type: schema.TypeInt,
ForceNew: true,
Optional: true,
Default: 0,
},
resourceInstanceManagedVxnetID: {
Type: schema.TypeString,
Expand Down Expand Up @@ -198,6 +196,11 @@ func resourceQingcloudInstanceRead(d *schema.ResourceData, meta interface{}) err
var err error
simpleRetry(func() error {
output, err = clt.DescribeInstances(input)
if err == nil && (len(output.InstanceSet[0].VxNets) == 0 ||
qc.StringValue(output.InstanceSet[0].VxNets[0].PrivateIP) == "") {
return fmt.Errorf("no private ip found for instance %s",
*output.InstanceSet[0].InstanceID)
}
return isServerBusy(err)
})
if err != nil {
Expand Down Expand Up @@ -306,6 +309,13 @@ func resourceQingcloudInstanceDelete(d *schema.ResourceData, meta interface{}) e
return err
}
clt := meta.(*QingCloudClient).instance
if isDelete, err := isInstanceDeletedWrapper(d.Id(), clt); err != nil || isDelete {
if err != nil {
return err
}
d.SetId("")
return nil
}
input := new(qc.TerminateInstancesInput)
input.Instances = []*string{qc.String(d.Id())}
var err error
Expand Down
24 changes: 24 additions & 0 deletions qingcloud/resource_qingcloud_instance_help.go
Original file line number Diff line number Diff line change
Expand Up @@ -440,3 +440,27 @@ func isInstanceDeleted(instanceSet []*qc.Instance) bool {
}
return false
}

func describeInstances(id string, clt *qc.InstanceService) (*qc.DescribeInstancesOutput, error) {
inputDescribe := new(qc.DescribeInstancesInput)
inputDescribe.Instances = []*string{qc.String(id)}
inputDescribe.Verbose = qc.Int(1)
var output *qc.DescribeInstancesOutput
var errDescribe error
simpleRetry(func() error {
output, errDescribe = clt.DescribeInstances(inputDescribe)
return isServerBusy(errDescribe)
})
if errDescribe != nil {
return nil, errDescribe
}
return output, nil
}

func isInstanceDeletedWrapper(id string, clt *qc.InstanceService) (bool, error) {
output, err := describeInstances(id, clt)
if err != nil {
return false, err
}
return isInstanceDeleted(output.InstanceSet), nil
}
118 changes: 118 additions & 0 deletions qingcloud/resource_qingcloud_instance_volume_attachment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package qingcloud

import (
"fmt"
"github.com/hashicorp/terraform/helper/schema"
qc "github.com/yunify/qingcloud-sdk-go/service"
"log"
"time"
)

const (
resourceVolumes = "volume_id"
resourceInstanceId = "instance_id"
)

func resourceQingcloudInstanceVolumeAttachment() *schema.Resource {
return &schema.Resource{
Create: resourceQingcloudInstanceVolumeAttachmentCreate,
Read: resourceQingcloudInstanceVolumeAttachmentRead,
Update: resourceQingcloudInstanceVolumeAttachmentUpdate,
Delete: resourceQingcloudInstanceVolumeAttachmentDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(5 * time.Minute),
Delete: schema.DefaultTimeout(5 * time.Minute),
},
Schema: map[string]*schema.Schema{
resourceVolumes: {
Type: schema.TypeString,
Required: true,
Description: "绑定的磁盘id",
},
resourceInstanceId: {
Type: schema.TypeString,
Required: true,
Description: "绑定的机器id",
},
},
}
}

func resourceQingcloudInstanceVolumeAttachmentCreate(d *schema.ResourceData, meta interface{}) error {
clt := meta.(*QingCloudClient).volume
input := new(qc.AttachVolumesInput)
volumeId := []string{d.Get(resourceVolumes).(string)}
input.Volumes = qc.StringSlice(volumeId)
input.Instance = qc.String(d.Get(resourceInstanceId).(string))
var err error
simpleRetry(func() error {
_, err = clt.AttachVolumes(input)
return WrapError(isServerBusy(err))
})
if err != nil {
return WrapError(err)
}
if _, err := VolumeAttachTransitionStateRefresh(clt, volumeId[0]); err != nil {
return WrapError(err)
}
d.SetId(volumeId[0] + ":" + *input.Instance)
return resourceQingcloudInstanceVolumeAttachmentRead(d, meta)
}

func resourceQingcloudInstanceVolumeAttachmentRead(d *schema.ResourceData, meta interface{}) error {
clt := meta.(*QingCloudClient).volume
parts, err := ParseResourceId(d.Id(), 2)
if err != nil {
return WrapError(err)
}
input := new(qc.DescribeVolumesInput)
input.Volumes = []*string{qc.String(parts[0])}
var output *qc.DescribeVolumesOutput
simpleRetry(func() error {
output, err = clt.DescribeVolumes(input)
return WrapError(isServerBusy(err))
})
if err != nil {
return WrapError(err)
}
if len(output.VolumeSet) == 0 {
d.SetId("")
return nil
}
volume := output.VolumeSet[0]
if *volume.Status != string(InUse) && *volume.Instances[0].InstanceID != parts[1] {
return WrapError(fmt.Errorf("the specified %s %s is not found", "VolumeAttach", d.Id()))
}
d.Set("volume_id", parts[0])
d.Set("instance_id", parts[1])
return nil
}

func resourceQingcloudInstanceVolumeAttachmentUpdate(d *schema.ResourceData, meta interface{}) error {
log.Println(fmt.Sprintf("[WARNING] The resouce has not update operation."))
return resourceQingcloudInstanceVolumeAttachmentRead(d, meta)
}

func resourceQingcloudInstanceVolumeAttachmentDelete(d *schema.ResourceData, meta interface{}) error {
clt := meta.(*QingCloudClient).volume
input := new(qc.DetachVolumesInput)
volumeId := []string{d.Get(resourceVolumes).(string)}
input.Volumes = qc.StringSlice(volumeId)
input.Instance = qc.String(d.Get(resourceInstanceId).(string))
var err error
simpleRetry(func() error {
_, err = clt.DetachVolumes(input)
return WrapError(isServerBusy(err))
})
if err != nil {
return WrapError(err)
}
if _, err := VolumeDetachTransitionStateRefresh(clt, volumeId[0]); err != nil {
return WrapError(err)
}
d.SetId("")
return nil
}
Loading