extract and vendor ucloud (#10953)

This commit is contained in:
Sylvia Moss 2021-04-21 19:25:04 +02:00 committed by GitHub
parent bc35a737c0
commit 4be2c350bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 18 additions and 4529 deletions

View File

@ -49,10 +49,6 @@
/builder/hcloud/ @LKaemmerling /builder/hcloud/ @LKaemmerling
/website/pages/docs/builders/hcloud* @LKaemmerling /website/pages/docs/builders/hcloud* @LKaemmerling
/examples/ucloud/ @shawnmssu
/builder/ucloud/ @shawnmssu
/website/pages/docs/builders/ucloud* @shawnmssu
/builder/yandex/ @GennadySpb @alexanderKhaustov @seukyaso /builder/yandex/ @GennadySpb @alexanderKhaustov @seukyaso
/website/pages/docs/builders/yandex* @GennadySpb @alexanderKhaustov @seukyaso /website/pages/docs/builders/yandex* @GennadySpb @alexanderKhaustov @seukyaso
@ -74,4 +70,3 @@
/post-processor/yandex-export/ @GennadySpb /post-processor/yandex-export/ @GennadySpb
/post-processor/yandex-import/ @GennadySpb /post-processor/yandex-import/ @GennadySpb
/post-processor/vsphere-template/ nelson@bennu.cl /post-processor/vsphere-template/ nelson@bennu.cl
/post-processor/ucloud-import/ @shawnmssu

View File

@ -1,358 +0,0 @@
//go:generate packer-sdc struct-markdown
package common
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"runtime"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
"github.com/hashicorp/packer/builder/ucloud/version"
"github.com/ucloud/ucloud-sdk-go/external"
"github.com/ucloud/ucloud-sdk-go/private/protocol/http"
"github.com/ucloud/ucloud-sdk-go/services/uaccount"
"github.com/ucloud/ucloud-sdk-go/services/ufile"
"github.com/ucloud/ucloud-sdk-go/services/uhost"
"github.com/ucloud/ucloud-sdk-go/services/unet"
"github.com/ucloud/ucloud-sdk-go/services/vpc"
"github.com/ucloud/ucloud-sdk-go/ucloud"
"github.com/ucloud/ucloud-sdk-go/ucloud/auth"
"github.com/ucloud/ucloud-sdk-go/ucloud/log"
)
type AccessConfig struct {
// This is the UCloud public key. It must be provided unless `profile` is set,
// but it can also be sourced from the `UCLOUD_PUBLIC_KEY` environment variable.
PublicKey string `mapstructure:"public_key" required:"true"`
// This is the UCloud private key. It must be provided unless `profile` is set,
// but it can also be sourced from the `UCLOUD_PRIVATE_KEY` environment variable.
PrivateKey string `mapstructure:"private_key" required:"true"`
// This is the UCloud region. It must be provided, but it can also be sourced from
// the `UCLOUD_REGION` environment variables.
Region string `mapstructure:"region" required:"true"`
// This is the UCloud project id. It must be provided, but it can also be sourced
// from the `UCLOUD_PROJECT_ID` environment variables.
ProjectId string `mapstructure:"project_id" required:"true"`
// This is the base url. (Default: `https://api.ucloud.cn`).
BaseUrl string `mapstructure:"base_url" required:"false"`
// This is the UCloud profile name as set in the shared credentials file, it can
// also be sourced from the `UCLOUD_PROFILE` environment variables.
Profile string `mapstructure:"profile" required:"false"`
// This is the path to the shared credentials file, it can also be sourced from
// the `UCLOUD_SHARED_CREDENTIAL_FILE` environment variables. If this is not set
// and a profile is specified, `~/.ucloud/credential.json` will be used.
SharedCredentialsFile string `mapstructure:"shared_credentials_file" required:"false"`
client *UCloudClient
}
type cloudShellCredential struct {
Cookie string `json:"cookie"`
Profile string `json:"profile"`
CSRFToken string `json:"csrf_token"`
}
func (c *AccessConfig) Client() (*UCloudClient, error) {
if c.client != nil {
return c.client, nil
}
cfg := ucloud.NewConfig()
cfg.Region = c.Region
cfg.ProjectId = c.ProjectId
if c.BaseUrl != "" {
cfg.BaseUrl = c.BaseUrl
}
cfg.LogLevel = log.PanicLevel
cfg.UserAgent = fmt.Sprintf("Packer-UCloud/%s", version.UcloudPluginVersion.FormattedVersion())
// set default max retry count
cfg.MaxRetries = 3
cred := auth.NewCredential()
var cloudShellCredHandler ucloud.HttpRequestHandler
if len(c.Profile) > 0 {
// load public/private key from shared credential file
credV, err := external.LoadUCloudCredentialFile(c.SharedCredentialsFile, c.Profile)
if err != nil {
return nil, fmt.Errorf("cannot load shared %q credential file, %s", c.Profile, err)
}
cred = *credV
} else if len(c.PublicKey) > 0 && len(c.PrivateKey) > 0 {
cred.PublicKey = c.PublicKey
cred.PrivateKey = c.PrivateKey
} else if v := os.Getenv("CLOUD_SHELL"); v == "true" {
csCred := make([]cloudShellCredential, 0)
// load credential from default cloud shell credential path
if err := loadJSONFile(defaultCloudShellCredPath(), &csCred); err != nil {
return nil, fmt.Errorf("must set credential about public_key and private_key, %s", err)
}
// get default cloud shell credential
defaultCsCred := &cloudShellCredential{}
for i := 0; i < len(csCred); i++ {
if csCred[i].Profile == "default" {
defaultCsCred = &csCred[i]
break
}
}
if defaultCsCred == nil || len(defaultCsCred.Cookie) == 0 || len(defaultCsCred.CSRFToken) == 0 {
return nil, fmt.Errorf("must set credential about public_key and private_key, default credential is null")
}
// set cloud shell client handler
cloudShellCredHandler = func(c *ucloud.Client, req *http.HttpRequest) (*http.HttpRequest, error) {
if err := req.SetHeader("Cookie", defaultCsCred.Cookie); err != nil {
return nil, err
}
if err := req.SetHeader("Csrf-Token", defaultCsCred.CSRFToken); err != nil {
return nil, err
}
return req, nil
}
} else {
return nil, fmt.Errorf("must set credential about public_key and private_key")
}
c.client = &UCloudClient{}
c.client.UHostConn = uhost.NewClient(&cfg, &cred)
c.client.UNetConn = unet.NewClient(&cfg, &cred)
c.client.VPCConn = vpc.NewClient(&cfg, &cred)
c.client.UAccountConn = uaccount.NewClient(&cfg, &cred)
c.client.UFileConn = ufile.NewClient(&cfg, &cred)
if cloudShellCredHandler != nil {
if err := c.client.UHostConn.AddHttpRequestHandler(cloudShellCredHandler); err != nil {
return nil, err
}
if err := c.client.UNetConn.AddHttpRequestHandler(cloudShellCredHandler); err != nil {
return nil, err
}
if err := c.client.VPCConn.AddHttpRequestHandler(cloudShellCredHandler); err != nil {
return nil, err
}
if err := c.client.UAccountConn.AddHttpRequestHandler(cloudShellCredHandler); err != nil {
return nil, err
}
if err := c.client.UFileConn.AddHttpRequestHandler(cloudShellCredHandler); err != nil {
return nil, err
}
}
return c.client, nil
}
func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
var errs []error
if err := c.Config(); err != nil {
errs = append(errs, err)
}
if c.Region == "" {
c.Region = os.Getenv("UCLOUD_REGION")
}
if c.Region == "" {
errs = append(errs, fmt.Errorf("%q must be set", "region"))
}
if c.ProjectId == "" {
c.ProjectId = os.Getenv("UCLOUD_PROJECT_ID")
}
if c.ProjectId == "" {
errs = append(errs, fmt.Errorf("%q must be set", "projectId"))
}
if c.BaseUrl != "" {
if _, err := url.Parse(c.BaseUrl); err != nil {
errs = append(errs, fmt.Errorf("%q is invalid, should be an valid ucloud base_url, got %q, parse error: %s", "base_url", c.BaseUrl, err))
}
}
if c.Profile == "" {
c.Profile = os.Getenv("UCLOUD_PROFILE")
}
if c.SharedCredentialsFile == "" {
c.SharedCredentialsFile = os.Getenv("UCLOUD_SHARED_CREDENTIAL_FILE")
}
if len(errs) > 0 {
return errs
}
return nil
}
func (c *AccessConfig) Config() error {
if c.PublicKey == "" {
c.PublicKey = os.Getenv("UCLOUD_PUBLIC_KEY")
}
if c.PrivateKey == "" {
c.PrivateKey = os.Getenv("UCLOUD_PRIVATE_KEY")
}
if c.Profile == "" {
c.Profile = os.Getenv("UCLOUD_PROFILE")
}
if c.SharedCredentialsFile == "" {
c.SharedCredentialsFile = os.Getenv("UCLOUD_SHARED_CREDENTIAL_FILE")
}
if (c.PublicKey == "" || c.PrivateKey == "") && c.Profile == "" && os.Getenv("CLOUD_SHELL") != "true" {
return fmt.Errorf("%q, %q must be set in template file or environment variables", "public_key", "private_key")
}
return nil
}
func (c *AccessConfig) ValidateProjectId(projectId string) error {
supportedProjectIds, err := c.getSupportedProjectIds()
if err != nil {
return err
}
for _, supportedProjectId := range supportedProjectIds {
if projectId == supportedProjectId {
return nil
}
}
return fmt.Errorf("%q is invalid, should be an valid ucloud project_id, got %q", "project_id", projectId)
}
func (c *AccessConfig) ValidateRegion(region string) error {
supportedRegions, err := c.getSupportedRegions()
if err != nil {
return err
}
for _, supportedRegion := range supportedRegions {
if region == supportedRegion {
return nil
}
}
return fmt.Errorf("%q is invalid, should be an valid ucloud region, got %q", "region", region)
}
func (c *AccessConfig) ValidateZone(region, zone string) error {
supportedZones, err := c.getSupportedZones(region)
if err != nil {
return err
}
for _, supportedZone := range supportedZones {
if zone == supportedZone {
return nil
}
}
return fmt.Errorf("%q is invalid, should be an valid ucloud zone, got %q", "availability_zone", zone)
}
func (c *AccessConfig) getSupportedProjectIds() ([]string, error) {
client, err := c.Client()
if err != nil {
return nil, err
}
conn := client.UAccountConn
req := conn.NewGetProjectListRequest()
resp, err := conn.GetProjectList(req)
if err != nil {
return nil, err
}
validProjectIds := make([]string, len(resp.ProjectSet))
for _, val := range resp.ProjectSet {
if !IsStringIn(val.ProjectId, validProjectIds) {
validProjectIds = append(validProjectIds, val.ProjectId)
}
}
return validProjectIds, nil
}
func (c *AccessConfig) getSupportedRegions() ([]string, error) {
client, err := c.Client()
if err != nil {
return nil, err
}
conn := client.UAccountConn
req := conn.NewGetRegionRequest()
resp, err := conn.GetRegion(req)
if err != nil {
return nil, err
}
validRegions := make([]string, len(resp.Regions))
for _, val := range resp.Regions {
if !IsStringIn(val.Region, validRegions) {
validRegions = append(validRegions, val.Region)
}
}
return validRegions, nil
}
func (c *AccessConfig) getSupportedZones(region string) ([]string, error) {
client, err := c.Client()
if err != nil {
return nil, err
}
conn := client.UAccountConn
req := conn.NewGetRegionRequest()
resp, err := conn.GetRegion(req)
if err != nil {
return nil, err
}
validZones := make([]string, len(resp.Regions))
for _, val := range resp.Regions {
if val.Region == region && !IsStringIn(val.Zone, validZones) {
validZones = append(validZones, val.Zone)
}
}
return validZones, nil
}
func defaultCloudShellCredPath() string {
return filepath.Join(userHomeDir(), ".ucloud", "credential.json")
}
func loadJSONFile(path string, p interface{}) error {
f, err := os.Open(path)
if err != nil {
return err
}
c, err := ioutil.ReadAll(f)
if err != nil {
return err
}
err = json.Unmarshal(c, p)
if err != nil {
return err
}
return nil
}
func userHomeDir() string {
if runtime.GOOS == "windows" {
home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
if home == "" {
home = os.Getenv("USERPROFILE")
}
return home
}
return os.Getenv("HOME")
}

View File

@ -1,35 +0,0 @@
package common
import (
"os"
"testing"
)
func testAccessConfig() *AccessConfig {
return &AccessConfig{
PublicKey: "test_pub",
PrivateKey: "test_pri",
ProjectId: "test_pro",
}
}
func TestAccessConfigPrepareRegion(t *testing.T) {
c := testAccessConfig()
c.Region = ""
if err := c.Prepare(nil); err == nil {
t.Fatalf("should have err")
}
c.Region = "cn-sh2"
if err := c.Prepare(nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
os.Setenv("UCLOUD_REGION", "cn-bj2")
c.Region = ""
if err := c.Prepare(nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
}

View File

@ -1,102 +0,0 @@
package common
import (
"fmt"
"log"
"sort"
"strings"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/ucloud/ucloud-sdk-go/ucloud"
)
type Artifact struct {
UCloudImages *ImageInfoSet
BuilderIdValue string
Client *UCloudClient
// StateData should store data such as GeneratedData
// to be shared with post-processors
StateData map[string]interface{}
}
func (a *Artifact) BuilderId() string {
return a.BuilderIdValue
}
func (*Artifact) Files() []string {
return nil
}
func (a *Artifact) Id() string {
m := make([]string, 0, len(a.UCloudImages.GetAll()))
for _, v := range a.UCloudImages.GetAll() {
m = append(m, fmt.Sprintf("%s:%s:%s", v.ProjectId, v.Region, v.ImageId))
}
sort.Strings(m)
return strings.Join(m, ",")
}
func (a *Artifact) String() string {
m := make([]string, 0, len(a.UCloudImages.GetAll()))
for _, v := range a.UCloudImages.GetAll() {
m = append(m, fmt.Sprintf("%s: %s: %s", v.ProjectId, v.Region, v.ImageId))
}
sort.Strings(m)
return fmt.Sprintf("UCloud images were created:\n\n%s", strings.Join(m, "\n"))
}
func (a *Artifact) State(name string) interface{} {
if _, ok := a.StateData[name]; ok {
return a.StateData[name]
}
switch name {
case "atlas.artifact.metadata":
return a.stateAtlasMetadata()
default:
return nil
}
}
func (a *Artifact) Destroy() error {
conn := a.Client.UHostConn
errors := make([]error, 0)
for _, v := range a.UCloudImages.GetAll() {
log.Printf("Delete ucloud image %s from %s:%s", v.ImageId, v.ProjectId, v.Region)
req := conn.NewTerminateCustomImageRequest()
req.ProjectId = ucloud.String(v.ProjectId)
req.Region = ucloud.String(v.Region)
req.ImageId = ucloud.String(v.ImageId)
if _, err := conn.TerminateCustomImage(req); err != nil {
errors = append(errors, err)
}
}
if len(errors) > 0 {
if len(errors) == 1 {
return errors[0]
} else {
return &packersdk.MultiError{Errors: errors}
}
}
return nil
}
func (a *Artifact) stateAtlasMetadata() interface{} {
metadata := make(map[string]string)
for _, v := range a.UCloudImages.GetAll() {
k := fmt.Sprintf("%s:%s", v.ProjectId, v.Region)
metadata[k] = v.ImageId
}
return metadata
}

View File

@ -1,92 +0,0 @@
package common
import (
"reflect"
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
func TestArtifact_Impl(t *testing.T) {
var _ packersdk.Artifact = new(Artifact)
}
func TestArtifactId(t *testing.T) {
expected := `project1:region1:foo,project2:region2:bar`
images := NewImageInfoSet(nil)
images.Set(ImageInfo{
Region: "region1",
ProjectId: "project1",
ImageId: "foo",
})
images.Set(ImageInfo{
Region: "region2",
ProjectId: "project2",
ImageId: "bar",
})
a := &Artifact{
UCloudImages: images,
}
result := a.Id()
if result != expected {
t.Fatalf("bad: %s", result)
}
}
func TestArtifactState_atlasMetadata(t *testing.T) {
images := NewImageInfoSet(nil)
images.Set(ImageInfo{
Region: "region1",
ProjectId: "project1",
ImageId: "foo",
})
images.Set(ImageInfo{
Region: "region2",
ProjectId: "project2",
ImageId: "bar",
})
a := &Artifact{
UCloudImages: images,
}
actual := a.State("atlas.artifact.metadata")
expected := map[string]string{
"project1:region1": "foo",
"project2:region2": "bar",
}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual)
}
}
func TestArtifactState_StateData(t *testing.T) {
expectedData := "this is the data"
artifact := &Artifact{
StateData: map[string]interface{}{"state_data": expectedData},
}
// Valid state
result := artifact.State("state_data")
if result != expectedData {
t.Fatalf("Bad: State data was %s instead of %s", result, expectedData)
}
// Invalid state
result = artifact.State("invalid_key")
if result != nil {
t.Fatalf("Bad: State should be nil for invalid state data name")
}
// Nil StateData should not fail and should return nil
artifact = &Artifact{}
result = artifact.State("key")
if result != nil {
t.Fatalf("Bad: State should be nil for nil StateData")
}
}

View File

@ -1,142 +0,0 @@
package common
import (
"github.com/ucloud/ucloud-sdk-go/services/uaccount"
"github.com/ucloud/ucloud-sdk-go/services/ufile"
"github.com/ucloud/ucloud-sdk-go/services/uhost"
"github.com/ucloud/ucloud-sdk-go/services/unet"
"github.com/ucloud/ucloud-sdk-go/services/vpc"
"github.com/ucloud/ucloud-sdk-go/ucloud"
uerr "github.com/ucloud/ucloud-sdk-go/ucloud/error"
)
type UCloudClient struct {
UHostConn *uhost.UHostClient
UNetConn *unet.UNetClient
VPCConn *vpc.VPCClient
UAccountConn *uaccount.UAccountClient
UFileConn *ufile.UFileClient
}
func (c *UCloudClient) DescribeFirewallById(sgId string) (*unet.FirewallDataSet, error) {
if sgId == "" {
return nil, NewNotFoundError("security group", sgId)
}
conn := c.UNetConn
req := conn.NewDescribeFirewallRequest()
req.FWId = ucloud.String(sgId)
resp, err := conn.DescribeFirewall(req)
if err != nil {
if uErr, ok := err.(uerr.Error); ok && uErr.Code() == 54002 {
return nil, NewNotFoundError("security group", sgId)
}
return nil, err
}
if len(resp.DataSet) < 1 {
return nil, NewNotFoundError("security group", sgId)
}
return &resp.DataSet[0], nil
}
func (c *UCloudClient) DescribeSubnetById(subnetId string) (*vpc.VPCSubnetInfoSet, error) {
if subnetId == "" {
return nil, NewNotFoundError("Subnet", subnetId)
}
conn := c.VPCConn
req := conn.NewDescribeSubnetRequest()
req.SubnetIds = []string{subnetId}
resp, err := conn.DescribeSubnet(req)
if err != nil {
return nil, err
}
if resp == nil || len(resp.DataSet) < 1 {
return nil, NewNotFoundError("Subnet", subnetId)
}
return &resp.DataSet[0], nil
}
func (c *UCloudClient) DescribeVPCById(vpcId string) (*vpc.VPCInfo, error) {
if vpcId == "" {
return nil, NewNotFoundError("VPC", vpcId)
}
conn := c.VPCConn
req := conn.NewDescribeVPCRequest()
req.VPCIds = []string{vpcId}
resp, err := conn.DescribeVPC(req)
if err != nil {
return nil, err
}
if resp == nil || len(resp.DataSet) < 1 {
return nil, NewNotFoundError("VPC", vpcId)
}
return &resp.DataSet[0], nil
}
func (c *UCloudClient) DescribeImageById(imageId string) (*uhost.UHostImageSet, error) {
if imageId == "" {
return nil, NewNotFoundError("image", imageId)
}
req := c.UHostConn.NewDescribeImageRequest()
req.ImageId = ucloud.String(imageId)
resp, err := c.UHostConn.DescribeImage(req)
if err != nil {
return nil, err
}
if len(resp.ImageSet) < 1 {
return nil, NewNotFoundError("image", imageId)
}
return &resp.ImageSet[0], nil
}
func (c *UCloudClient) DescribeUHostById(uhostId string) (*uhost.UHostInstanceSet, error) {
if uhostId == "" {
return nil, NewNotFoundError("instance", uhostId)
}
req := c.UHostConn.NewDescribeUHostInstanceRequest()
req.UHostIds = []string{uhostId}
resp, err := c.UHostConn.DescribeUHostInstance(req)
if err != nil {
return nil, err
}
if len(resp.UHostSet) < 1 {
return nil, nil
}
return &resp.UHostSet[0], nil
}
func (c *UCloudClient) DescribeImageByInfo(projectId, regionId, imageId string) (*uhost.UHostImageSet, error) {
req := c.UHostConn.NewDescribeImageRequest()
req.ProjectId = ucloud.String(projectId)
req.ImageId = ucloud.String(imageId)
req.Region = ucloud.String(regionId)
resp, err := c.UHostConn.DescribeImage(req)
if err != nil {
return nil, err
}
if len(resp.ImageSet) < 1 {
return nil, NewNotFoundError("image", imageId)
}
return &resp.ImageSet[0], nil
}

View File

@ -1,38 +0,0 @@
package common
const (
// DefaultPasswordStr, DefaultPasswordNum and DefaultPasswordSpe are used to general default value of root password of UHost instance
DefaultPasswordNum = "012346789"
DefaultPasswordStr = "abcdefghijklmnopqrstuvwxyz"
DefaultPasswordSpe = "-_"
)
const (
InstanceStateRunning = "Running"
InstanceStateStopped = "Stopped"
ImageStateAvailable = "Available"
ImageStateUnavailable = "Unavailable"
BootDiskStateNormal = "Normal"
OsTypeWindows = "Windows"
SecurityGroupNonWeb = "recommend non web"
IpTypePrivate = "Private"
)
const (
DefaultCreateImageTimeout = 3600
)
var BootDiskTypeMap = NewStringConverter(map[string]string{
"cloud_ssd": "CLOUD_SSD",
"local_normal": "LOCAL_NORMAL",
"local_ssd": "LOCAL_SSD",
"cloud_rssd": "CLOUD_RSSD",
})
var ChargeModeMap = NewStringConverter(map[string]string{
"post_accurate_bandwidth": "PostAccurateBandwidth",
"traffic": "Traffic",
"bandwidth": "Bandwidth",
})

View File

@ -1,62 +0,0 @@
package common
import (
"fmt"
)
type NotFoundError struct {
message string
}
type ExpectedStateError struct {
message string
}
type NotCompletedError struct {
message string
}
func (e *ExpectedStateError) Error() string {
return e.message
}
func (e *NotFoundError) Error() string {
return e.message
}
func (e *NotCompletedError) Error() string {
return e.message
}
func NewNotFoundError(product, id string) error {
return &NotFoundError{fmt.Sprintf("the %s %s is not found", product, id)}
}
func NewExpectedStateError(product, id string) error {
return &ExpectedStateError{fmt.Sprintf("the %s %s not be expected state", product, id)}
}
func NewNotCompletedError(product string) error {
return &NotCompletedError{fmt.Sprintf("%s is not completed", product)}
}
func IsNotFoundError(err error) bool {
if _, ok := err.(*NotFoundError); ok {
return true
}
return false
}
func IsExpectedStateError(err error) bool {
if _, ok := err.(*ExpectedStateError); ok {
return true
}
return false
}
func IsNotCompleteError(err error) bool {
if _, ok := err.(*NotCompletedError); ok {
return true
}
return false
}

View File

@ -1,106 +0,0 @@
//go:generate packer-sdc mapstructure-to-hcl2 -type ImageDestination
//go:generate packer-sdc struct-markdown
package common
import (
"fmt"
"regexp"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
)
type ImageDestination struct {
// The destination project id, where copying image in.
ProjectId string `mapstructure:"project_id" required:"false"`
// The destination region, where copying image in.
Region string `mapstructure:"region" required:"false"`
// The copied image name. If not defined, builder will use `image_name` as default name.
Name string `mapstructure:"name" required:"false"`
// The copied image description.
Description string `mapstructure:"description" required:"false"`
}
type ImageConfig struct {
// The name of the user-defined image, which contains 1-63 characters and only
// support Chinese, English, numbers, '-\_,.:[]'.
ImageName string `mapstructure:"image_name" required:"true"`
// The description of the image.
ImageDescription string `mapstructure:"image_description" required:"false"`
// The array of mappings regarding the copied images to the destination regions and projects.
//
// - `project_id` (string) - The destination project id, where copying image in.
//
// - `region` (string) - The destination region, where copying image in.
//
// - `name` (string) - The copied image name. If not defined, builder will use `image_name` as default name.
//
// - `description` (string) - The copied image description.
//
// ```json
// {
// "image_copy_to_mappings": [
// {
// "project_id": "{{user `ucloud_project_id`}}",
// "region": "cn-sh2",
// "description": "test",
// "name": "packer-test-basic-sh"
// }
// ]
// }
// ```
ImageDestinations []ImageDestination `mapstructure:"image_copy_to_mappings" required:"false"`
// Timeout of creating image or copying image. The default timeout is 3600 seconds if this option
// is not set or is set to 0.
WaitImageReadyTimeout int `mapstructure:"wait_image_ready_timeout" required:"false"`
}
var ImageNamePattern = regexp.MustCompile(`^[A-Za-z0-9\p{Han}-_\[\]:,.]{1,63}$`)
func (c *ImageConfig) Prepare(ctx *interpolate.Context) []error {
var errs []error
imageName := c.ImageName
if imageName == "" {
errs = append(errs, fmt.Errorf("%q must be set", "image_name"))
} else if !ImageNamePattern.MatchString(imageName) {
errs = append(errs, fmt.Errorf("expected %q to be 1-63 characters and only support chinese, english, numbers, '-_,.:[]', got %q", "image_name", imageName))
}
if len(c.ImageDestinations) > 0 {
for _, imageDestination := range c.ImageDestinations {
if imageDestination.Name == "" {
imageDestination.Name = imageName
}
errs = append(errs, imageDestination.validate()...)
}
}
if c.WaitImageReadyTimeout <= 0 {
c.WaitImageReadyTimeout = DefaultCreateImageTimeout
}
if len(errs) > 0 {
return errs
}
return nil
}
func (imageDestination *ImageDestination) validate() []error {
var errs []error
if imageDestination.Region == "" {
errs = append(errs, fmt.Errorf("%q must be set", "image_copy_region"))
}
if imageDestination.ProjectId == "" {
errs = append(errs, fmt.Errorf("%q must be set", "image_copy_project"))
}
if imageDestination.Name != "" && !ImageNamePattern.MatchString(imageDestination.Name) {
errs = append(errs, fmt.Errorf("expected %q to be 1-63 characters and only support chinese, english, numbers, '-_,.:[]', got %q", "image_copy_name", imageDestination.Name))
}
return errs
}

View File

@ -1,37 +0,0 @@
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
package common
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/zclconf/go-cty/cty"
)
// FlatImageDestination is an auto-generated flat version of ImageDestination.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatImageDestination struct {
ProjectId *string `mapstructure:"project_id" required:"false" cty:"project_id" hcl:"project_id"`
Region *string `mapstructure:"region" required:"false" cty:"region" hcl:"region"`
Name *string `mapstructure:"name" required:"false" cty:"name" hcl:"name"`
Description *string `mapstructure:"description" required:"false" cty:"description" hcl:"description"`
}
// FlatMapstructure returns a new FlatImageDestination.
// FlatImageDestination is an auto-generated flat version of ImageDestination.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*ImageDestination) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatImageDestination)
}
// HCL2Spec returns the hcl spec of a ImageDestination.
// This spec is used by HCL to read the fields of ImageDestination.
// The decoded values from this spec will then be applied to a FlatImageDestination.
func (*FlatImageDestination) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"project_id": &hcldec.AttrSpec{Name: "project_id", Type: cty.String, Required: false},
"region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false},
"name": &hcldec.AttrSpec{Name: "name", Type: cty.String, Required: false},
"description": &hcldec.AttrSpec{Name: "description", Type: cty.String, Required: false},
}
return s
}

View File

@ -1,63 +0,0 @@
package common
import (
"testing"
)
func testImageConfig() *ImageConfig {
return &ImageConfig{
ImageName: "foo",
}
}
func TestImageConfigPrepare_name(t *testing.T) {
c := testImageConfig()
if err := c.Prepare(nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
c.ImageName = ""
if err := c.Prepare(nil); err == nil {
t.Fatal("should have error")
}
}
func TestImageConfigPrepare_destinations(t *testing.T) {
c := testImageConfig()
c.ImageDestinations = nil
if err := c.Prepare(nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
c.ImageDestinations = []ImageDestination{
{
ProjectId: "foo",
Region: "cn-bj2",
Name: "bar",
},
{
ProjectId: "bar",
Region: "cn-sh2",
Name: "foo",
},
}
if err := c.Prepare(nil); err != nil {
t.Fatalf("bad: %s", err)
}
c.ImageDestinations = []ImageDestination{
{
ProjectId: "foo",
Name: "bar",
},
{
ProjectId: "bar",
Region: "cn-sh2",
},
}
if err := c.Prepare(nil); err == nil {
t.Fatal("should have error")
}
}

View File

@ -1,196 +0,0 @@
//go:generate packer-sdc struct-markdown
package common
import (
"fmt"
"os"
"regexp"
"github.com/hashicorp/packer-plugin-sdk/communicator"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
"github.com/hashicorp/packer-plugin-sdk/uuid"
)
type RunConfig struct {
// This is the UCloud availability zone where UHost instance is located. such as: `cn-bj2-02`.
// You may refer to [list of availability_zone](https://docs.ucloud.cn/api/summary/regionlist)
Zone string `mapstructure:"availability_zone" required:"true"`
// This is the ID of base image which you want to create your customized images with.
SourceImageId string `mapstructure:"source_image_id" required:"true"`
// The type of UHost instance.
// You may refer to [list of instance type](https://docs.ucloud.cn/compute/terraform/specification/instance)
InstanceType string `mapstructure:"instance_type" required:"true"`
// The name of instance, which contains 1-63 characters and only support Chinese,
// English, numbers, '-', '\_', '.'.
InstanceName string `mapstructure:"instance_name" required:"false"`
// The type of boot disk associated to UHost instance.
// Possible values are: `cloud_ssd` and `cloud_rssd` for cloud boot disk, `local_normal` and `local_ssd`
// for local boot disk. (Default: `cloud_ssd`). The `cloud_ssd` and `local_ssd` are not fully supported
// by all regions as boot disk type, please proceed to UCloud console for more details.
//
//~> **Note:** It takes around 10 mins for boot disk initialization when `boot_disk_type` is `local_normal` or `local_ssd`.
BootDiskType string `mapstructure:"boot_disk_type" required:"false"`
// The ID of VPC linked to the UHost instance. If not defined `vpc_id`, the instance will use the default VPC in the current region.
VPCId string `mapstructure:"vpc_id" required:"false"`
// The ID of subnet under the VPC. If `vpc_id` is defined, the `subnet_id` is mandatory required.
// If `vpc_id` and `subnet_id` are not defined, the instance will use the default subnet in the current region.
SubnetId string `mapstructure:"subnet_id" required:"false"`
// The ID of the fire wall associated to UHost instance. If `security_group_id` is not defined,
// the instance will use the non-recommended web fire wall, and open port include 22, 3389 by default.
// It is supported by ICMP fire wall protocols.
// You may refer to [security group_id](https://docs.ucloud.cn/network/firewall/firewall).
SecurityGroupId string `mapstructure:"security_group_id" required:"false"`
// Maximum bandwidth to the elastic public network, measured in Mbps (Mega bit per second). (Default: `10`).
EipBandwidth int `mapstructure:"eip_bandwidth" required:"false"`
// Elastic IP charge mode. Possible values are: `traffic` as pay by traffic, `bandwidth` as pay by bandwidth,
// `post_accurate_bandwidth` as post pay mode. (Default: `traffic`).
// Note currently default `traffic` eip charge mode not not fully support by all `availability_zone`
// in the `region`, please proceed to [UCloud console](https://console.ucloud.cn/unet/eip/create) for more details.
// You may refer to [eip introduction](https://docs.ucloud.cn/unet/eip/introduction).
EipChargeMode string `mapstructure:"eip_charge_mode" required:"false"`
// User data to apply when launching the instance.
// Note that you need to be careful about escaping characters due to the templates
// being JSON. It is often more convenient to use user_data_file, instead.
// Packer will not automatically wait for a user script to finish before
// shutting down the instance this must be handled in a provisioner.
// You may refer to [user_data_document](https://docs.ucloud.cn/uhost/guide/metadata/userdata)
UserData string `mapstructure:"user_data" required:"false"`
// Path to a file that will be used for the user data when launching the instance.
UserDataFile string `mapstructure:"user_data_file" required:"false"`
// Specifies a minimum CPU platform for the the VM instance. (Default: `Intel/Auto`).
// You may refer to [min_cpu_platform](https://docs.ucloud.cn/uhost/introduction/uhost/type_new)
// - The Intel CPU platform:
// - `Intel/Auto` as the Intel CPU platform version will be selected randomly by system;
// - `Intel/IvyBridge` as Intel V2, the version of Intel CPU platform selected by system will be `Intel/IvyBridge` and above;
// - `Intel/Haswell` as Intel V3, the version of Intel CPU platform selected by system will be `Intel/Haswell` and above;
// - `Intel/Broadwell` as Intel V4, the version of Intel CPU platform selected by system will be `Intel/Broadwell` and above;
// - `Intel/Skylake` as Intel V5, the version of Intel CPU platform selected by system will be `Intel/Skylake` and above;
// - `Intel/Cascadelake` as Intel V6, the version of Intel CPU platform selected by system will be `Intel/Cascadelake`;
// - The AMD CPU platform:
// - `Amd/Auto` as the Amd CPU platform version will be selected randomly by system;
// - `Amd/Epyc2` as the version of Amd CPU platform selected by system will be `Amd/Epyc2` and above;
MinCpuPlatform string `mapstructure:"min_cpu_platform" required:"false"`
// Communicator settings
Comm communicator.Config `mapstructure:",squash"`
// If this value is true, packer will connect to the created UHost instance via a private ip
// instead of allocating an EIP (elastic public ip).(Default: `false`).
UseSSHPrivateIp bool `mapstructure:"use_ssh_private_ip"`
}
var instanceNamePattern = regexp.MustCompile(`^[A-Za-z0-9\p{Han}-_.]{1,63}$`)
func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
errs := c.Comm.Prepare(ctx)
if c.Zone == "" {
errs = append(errs, fmt.Errorf("%q must be set", "availability_zone"))
}
if c.SourceImageId == "" {
errs = append(errs, fmt.Errorf("%q must be set", "source_image_id"))
}
if c.InstanceType == "" {
errs = append(errs, fmt.Errorf("%q must be set", "instance_type"))
} else if _, err := ParseInstanceType(c.InstanceType); err != nil {
errs = append(errs, err)
}
if (c.VPCId != "" && c.SubnetId == "") || (c.VPCId == "" && c.SubnetId != "") {
errs = append(errs, fmt.Errorf("expected both %q and %q to set or not set", "vpc_id", "subnet_id"))
}
if c.BootDiskType == "" {
c.BootDiskType = "cloud_ssd"
} else if err := CheckStringIn(c.BootDiskType,
[]string{"local_normal", "local_ssd", "cloud_ssd", "cloud_rssd"}); err != nil {
errs = append(errs, err)
}
if c.InstanceName == "" {
c.InstanceName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID()[:8])
} else if !instanceNamePattern.MatchString(c.InstanceName) {
errs = append(errs, fmt.Errorf("expected %q to be 1-63 characters and only support chinese, english, numbers, '-_.', got %q", "instance_name", c.InstanceName))
}
if c.UseSSHPrivateIp == true && c.VPCId == "" {
errs = append(errs, fmt.Errorf("%q must be set when use_ssh_private_ip is true", "vpc_id"))
}
if c.Comm.SSHPassword != "" && len(validateInstancePassword(c.Comm.SSHPassword)) != 0 {
for _, v := range validateInstancePassword(c.Comm.SSHPassword) {
errs = append(errs, v)
}
}
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))
}
}
if c.MinCpuPlatform == "" {
c.MinCpuPlatform = "Intel/Auto"
} else if err := CheckStringIn(c.MinCpuPlatform,
[]string{
"Intel/Auto",
"Intel/IvyBridge",
"Intel/Haswell",
"Intel/Broadwell",
"Intel/Skylake",
"Intel/Cascadelake",
"Amd/Auto",
"Amd/Epyc2",
}); err != nil {
errs = append(errs, err)
}
if c.EipChargeMode == "" {
c.EipChargeMode = "traffic"
} else if err := CheckStringIn(c.EipChargeMode, []string{"traffic", "bandwidth", "post_accurate_bandwidth"}); err != nil {
errs = append(errs, err)
}
if c.EipBandwidth == 0 {
c.EipBandwidth = 10
}
return errs
}
var instancePasswordUpperPattern = regexp.MustCompile(`[A-Z]`)
var instancePasswordLowerPattern = regexp.MustCompile(`[a-z]`)
var instancePasswordNumPattern = regexp.MustCompile(`[0-9]`)
var instancePasswordSpecialPattern = regexp.MustCompile(`[` + "`" + `()~!@#$%^&*-+=_|{}\[\]:;'<>,.?/]`)
var instancePasswordPattern = regexp.MustCompile(`^[A-Za-z0-9` + "`" + `()~!@#$%^&*-+=_|{}\[\]:;'<>,.?/]{8,30}$`)
func validateInstancePassword(password string) (errors []error) {
if !instancePasswordPattern.MatchString(password) {
errors = append(errors, fmt.Errorf("%q is invalid, should have between 8-30 characters and any characters must be legal, got %q", "ssh_password", password))
}
categoryCount := 0
if instancePasswordUpperPattern.MatchString(password) {
categoryCount++
}
if instancePasswordLowerPattern.MatchString(password) {
categoryCount++
}
if instancePasswordNumPattern.MatchString(password) {
categoryCount++
}
if instancePasswordSpecialPattern.MatchString(password) {
categoryCount++
}
if categoryCount < 2 {
errors = append(errors, fmt.Errorf("%q is invalid, should have least 2 items of capital letters, lower case letters, numbers and special characters, got %q", "ssh_password", password))
}
return
}

View File

@ -1,65 +0,0 @@
package common
import (
"testing"
"github.com/hashicorp/packer-plugin-sdk/communicator"
)
func testConfig() *RunConfig {
return &RunConfig{
SourceImageId: "ucloud_image",
InstanceType: "n-basic-2",
Zone: "cn-bj2-02",
Comm: communicator.Config{
SSH: communicator.SSH{
SSHUsername: "ucloud",
},
},
}
}
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_SourceImage(t *testing.T) {
c := testConfig()
c.SourceImageId = ""
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)
}
}

View File

@ -1,31 +0,0 @@
package common
type stringConverter struct {
c map[string]string
r map[string]string
}
func NewStringConverter(input map[string]string) stringConverter {
reversed := make(map[string]string)
for k, v := range input {
reversed[v] = k
}
return stringConverter{
c: input,
r: reversed,
}
}
func (c stringConverter) Convert(src string) string {
if dst, ok := c.c[src]; ok {
return dst
}
return src
}
func (c stringConverter) UnConvert(dst string) string {
if src, ok := c.r[dst]; ok {
return src
}
return dst
}

View File

@ -1,216 +0,0 @@
package common
import (
"fmt"
"strconv"
"strings"
"sync"
)
type InstanceType struct {
CPU int
Memory int
HostType string
HostScaleType string
}
func ParseInstanceType(s string) (*InstanceType, error) {
split := strings.Split(s, "-")
if len(split) < 3 {
return nil, fmt.Errorf("instance type is invalid, got %q", s)
}
if split[1] == "customized" {
return parseInstanceTypeByCustomize(split...)
}
return parseInstanceTypeByNormal(split...)
}
func (i *InstanceType) String() string {
if i.Iscustomized() {
return fmt.Sprintf("%s-%s-%v-%v", i.HostType, i.HostScaleType, i.CPU, i.Memory)
} else {
return fmt.Sprintf("%s-%s-%v", i.HostType, i.HostScaleType, i.CPU)
}
}
func (i *InstanceType) Iscustomized() bool {
return i.HostScaleType == "customized"
}
var instanceTypeScaleMap = map[string]int{
"highcpu": 1 * 1024,
"basic": 2 * 1024,
"standard": 4 * 1024,
"highmem": 8 * 1024,
}
var availableHostTypes = []string{"n"}
func parseInstanceTypeByCustomize(split ...string) (*InstanceType, error) {
if len(split) != 4 {
return nil, fmt.Errorf("instance type is invalid, expected like n-customized-1-2")
}
hostType := split[0]
err := CheckStringIn(hostType, availableHostTypes)
if err != nil {
return nil, fmt.Errorf("instance type is invalid, the host type of customized %q can only be %q", "instance_type", "n")
}
hostScaleType := split[1]
cpu, err := strconv.Atoi(split[2])
if err != nil {
return nil, fmt.Errorf("cpu count is invalid, please use a number")
}
memory, err := strconv.Atoi(split[3])
if err != nil {
return nil, fmt.Errorf("memory count is invalid, please use a number")
}
if cpu != 1 && (cpu%2) != 0 {
return nil, fmt.Errorf("expected the number of cores of cpu must be divisible by 2 without a remainder (except single core), got %d", cpu)
}
if memory != 1 && (memory%2) != 0 {
return nil, fmt.Errorf("expected the number of memory must be divisible by 2 without a remainder (except single memory), got %d", memory)
}
if cpu/memory > 2 || memory/cpu > 12 || (cpu/memory == 2 && cpu%memory != 0) || (memory/cpu == 12 && memory%cpu != 0) {
return nil, fmt.Errorf("the ratio of cpu to memory should be range of 2:1 ~ 1:12, got %d:%d", cpu, memory)
}
if (memory/cpu == 1 || memory/cpu == 2 || memory/cpu == 4 || memory/cpu == 8) && memory%cpu == 0 {
return nil, fmt.Errorf("instance type is invalid, expected %q like %q,"+
"the Type can be highcpu, basic, standard, highmem when the ratio of cpu to memory is 1:1, 1:2, 1:4, 1:8", "n-Type-CPU", "n-standard-1")
}
if cpu < 1 || 32 < cpu {
return nil, fmt.Errorf("expected cpu to be in the range (1 - 32), got %d", cpu)
}
if memory < 1 || 128 < memory {
return nil, fmt.Errorf("expected memory to be in the range (1 - 128),got %d", memory)
}
t := &InstanceType{}
t.HostType = hostType
t.HostScaleType = hostScaleType
t.CPU = cpu
t.Memory = memory * 1024
return t, nil
}
var availableOutstandingCpu = []int{2, 4, 8, 16, 32, 64, 96}
func parseInstanceTypeByNormal(split ...string) (*InstanceType, error) {
if len(split) != 3 {
return nil, fmt.Errorf("instance type is invalid, expected like n-standard-1")
}
hostType := split[0]
err := CheckStringIn(hostType, []string{"n", "o", "c"})
if err != nil {
return nil, fmt.Errorf("instance type is invalid, the host type of %q must be one of %#v", "instance_type", []string{"n", "o", "c"})
}
hostScaleType := split[1]
if scale, ok := instanceTypeScaleMap[hostScaleType]; !ok {
return nil, fmt.Errorf("instance type is invalid, expected like %q,"+
"the Type can be highcpu, basic, standard, highmem when the ratio of cpu to memory is 1:1, 1:2, 1:4, 1:8, got %q ", "n-standard-1", hostScaleType)
} else {
cpu, err := strconv.Atoi(split[2])
if err != nil {
return nil, fmt.Errorf("cpu count is invalid, please use a number")
}
if cpu != 1 && (cpu%2) != 0 {
return nil, fmt.Errorf("expected the number of cores of cpu must be divisible by 2 without a remainder (except single core), got %d", cpu)
}
if hostType == "o" {
if err := CheckIntIn(cpu, availableOutstandingCpu); err != nil {
return nil, fmt.Errorf("expected cpu of `O` instance type must be one of %#v, got %q", availableOutstandingCpu, cpu)
}
if hostScaleType == "highmem" && cpu == 64 {
return nil, fmt.Errorf("this instance type %q is not supported, please refer to instance type document", "o-highmem-64")
}
} else {
if hostScaleType == "highmem" && cpu > 16 {
return nil, fmt.Errorf("expected cpu to be in the range (1 - 16) for `N` and `C` highmem instance type, got %d", cpu)
}
if cpu < 1 || 32 < cpu {
return nil, fmt.Errorf("expected cpu to be in the range (1 - 32) for `N` and `C` instance type, got %d", cpu)
}
}
memory := cpu * scale
t := &InstanceType{}
t.HostType = hostType
t.HostScaleType = hostScaleType
t.CPU = cpu
t.Memory = memory
return t, nil
}
}
type ImageInfo struct {
ImageId string
ProjectId string
Region string
}
func (i *ImageInfo) Id() string {
return fmt.Sprintf("%s:%s", i.ProjectId, i.Region)
}
type ImageInfoSet struct {
m map[string]ImageInfo
once sync.Once
}
func NewImageInfoSet(vL []ImageInfo) *ImageInfoSet {
s := ImageInfoSet{}
for _, v := range vL {
s.Set(v)
}
return &s
}
func (i *ImageInfoSet) init() {
i.m = make(map[string]ImageInfo)
}
func (i *ImageInfoSet) Set(img ImageInfo) {
i.once.Do(i.init)
i.m[img.Id()] = img
}
func (i *ImageInfoSet) Remove(id string) {
i.once.Do(i.init)
delete(i.m, id)
}
func (i *ImageInfoSet) Get(projectId, region string) *ImageInfo {
k := fmt.Sprintf("%s:%s", projectId, region)
if v, ok := i.m[k]; ok {
return &v
}
return nil
}
func (i *ImageInfoSet) GetAll() []ImageInfo {
var vL []ImageInfo
for _, img := range i.m {
vL = append(vL, img)
}
return vL
}

View File

@ -1,54 +0,0 @@
package common
import (
"testing"
)
func Test_parseInstanceType(t *testing.T) {
type args struct {
s string
}
tests := []struct {
name string
args args
want *InstanceType
wantErr bool
}{
{"ok_highcpu", args{"n-highcpu-1"}, &InstanceType{1, 1024, "n", "highcpu"}, false},
{"ok_basic", args{"n-basic-1"}, &InstanceType{1, 2048, "n", "basic"}, false},
{"ok_standard", args{"n-standard-1"}, &InstanceType{1, 4096, "n", "standard"}, false},
{"ok_highmem", args{"n-highmem-1"}, &InstanceType{1, 8192, "n", "highmem"}, false},
{"ok_customized", args{"n-customized-1-12"}, &InstanceType{1, 12288, "n", "customized"}, false},
{"err_customized", args{"n-customized-1-5"}, nil, true},
{"err_type", args{"nx-highcpu-1"}, nil, true},
{"err_scale_type", args{"n-invalid-1"}, nil, true},
{"err_cpu_too_much", args{"n-highcpu-33"}, nil, true},
{"err_cpu_too_less", args{"n-highcpu-0"}, nil, true},
{"err_cpu_is_invalid", args{"n-highcpu-x"}, nil, true},
{"err_customized_format_len", args{"n-customized-1"}, nil, true},
{"err_customized_format_number", args{"n-customized-x"}, nil, true},
{"err_customized_should_be_standard", args{"n-customized-1-2"}, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseInstanceType(tt.args.s)
if (err != nil) != tt.wantErr {
t.Errorf("ParseInstanceType() arg %s got %#v error = %v, wantErr %v", tt.args.s, got, err, tt.wantErr)
return
}
if got == nil {
return
}
if !(tt.want.CPU == got.CPU) ||
!(tt.want.Memory == got.Memory) ||
!(tt.want.HostType == got.HostType) ||
!(tt.want.HostScaleType == got.HostScaleType) {
t.Errorf("ParseInstanceType() = %v, want %v", got, tt.want)
}
})
}
}

View File

@ -1,75 +0,0 @@
package common
import (
"fmt"
"strings"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/ucloud/ucloud-sdk-go/services/uhost"
)
func CheckStringIn(val string, available []string) error {
for _, choice := range available {
if val == choice {
return nil
}
}
return fmt.Errorf("should be one of %q, got %q", strings.Join(available, ","), val)
}
func CheckIntIn(val int, available []int) error {
for _, choice := range available {
if val == choice {
return nil
}
}
return fmt.Errorf("should be one of %v, got %d", available, val)
}
func IsStringIn(val string, available []string) bool {
for _, choice := range available {
if val == choice {
return true
}
}
return false
}
// SSHHost returns a function that can be given to the SSH communicator
func SSHHost(usePrivateIp bool) func(multistep.StateBag) (string, error) {
return func(state multistep.StateBag) (string, error) {
instance := state.Get("instance").(*uhost.UHostInstanceSet)
var privateIp, publicIp string
for _, v := range instance.IPSet {
if v.Type == IpTypePrivate {
privateIp = v.IP
} else {
publicIp = v.IP
}
}
if usePrivateIp {
return privateIp, nil
}
return publicIp, nil
}
}
func Halt(state multistep.StateBag, err error, prefix string) multistep.StepAction {
ui := state.Get("ui").(packersdk.Ui)
if prefix != "" {
err = fmt.Errorf("%s: %s", prefix, err)
}
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}

View File

@ -1,38 +0,0 @@
{
"variables": {
"ucloud_public_key": "{{env `UCLOUD_PUBLIC_KEY`}}",
"ucloud_private_key": "{{env `UCLOUD_PRIVATE_KEY`}}",
"ucloud_project_id": "{{env `UCLOUD_PROJECT_ID`}}"
},
"builders": [
{
"type": "ucloud-uhost",
"public_key": "{{user `ucloud_public_key`}}",
"private_key": "{{user `ucloud_private_key`}}",
"project_id": "{{user `ucloud_project_id`}}",
"region": "cn-bj2",
"availability_zone": "cn-bj2-02",
"instance_type": "n-basic-2",
"source_image_id": "uimage-f1chxn",
"ssh_username": "root",
"image_name": "packer-test-basic-bj",
"image_copy_to_mappings": [
{
"project_id": "{{user `ucloud_project_id`}}",
"region": "cn-sh2",
"description": "test",
"name": "packer-test-basic-sh"
}
]
}
],
"provisioners": [
{
"type": "shell",
"inline": [
"yum install -y nginx"
]
}
]
}

View File

@ -1,25 +0,0 @@
install
cdrom
lang en_US.UTF-8
keyboard us
network --bootproto=dhcp
rootpw ucloud_packer
firewall --disabled
selinux --permissive
timezone UTC
unsupported_hardware
bootloader --location=mbr
text
skipx
zerombr
clearpart --all
autopart
auth --enableshadow --passalgo=sha512
firstboot --disabled
reboot
%packages --nobase --ignoremissing
sudo
gcc
make
%end

View File

@ -1,55 +0,0 @@
{"variables": {
"ucloud_public_key": "{{env `UCLOUD_PUBLIC_KEY`}}",
"ucloud_private_key": "{{env `UCLOUD_PRIVATE_KEY`}}",
"ucloud_project_id": "{{env `UCLOUD_PROJECT_ID`}}",
"disk_size": "4096",
"iso_checksum": "md5:0da4a1206e7642906e33c0f155d2f835",
"iso_name": "CentOS-6.10-x86_64-minimal.iso",
"ks_path": "centos-6.10/ks.cfg",
"mirror": "http://mirrors.ustc.edu.cn/centos",
"mirror_directory": "6.10/isos/x86_64",
"template": "centos-6.10-x86_64"
},
"builders":[
{
"type": "qemu",
"boot_command": [
"<tab> text ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/{{user `ks_path`}}<enter><wait>"
],
"boot_wait": "10s",
"disk_size": "{{user `disk_size`}}",
"http_directory": "http",
"iso_checksum": "{{user `iso_checksum`}}",
"iso_checksum_type": "{{user `iso_checksum_type`}}",
"iso_url": "{{user `mirror`}}/{{user `mirror_directory`}}/{{user `iso_name`}}",
"output_directory": "packer-{{user `template`}}-qemu",
"shutdown_command": "echo 'packer'|sudo -S shutdown -P now",
"ssh_password": "ucloud_packer",
"ssh_port": 22,
"ssh_username": "root",
"ssh_timeout": "10000s",
"vm_name": "{{ user `template` }}.raw",
"net_device": "virtio-net",
"disk_interface": "virtio",
"format": "raw",
"use_default_display": "false",
"qemuargs": [
["-display", "cocoa"]
]
}
],
"post-processors":[
{
"type":"ucloud-import",
"public_key": "{{user `ucloud_public_key`}}",
"private_key": "{{user `ucloud_private_key`}}",
"project_id": "{{user `ucloud_project_id`}}",
"region":"cn-bj2",
"ufile_bucket_name": "packer-test",
"image_name": "packer_import_test",
"image_os_type": "CentOS",
"image_os_name": "CentOS 6.10 64位",
"format": "raw"
}
]
}

View File

@ -1,32 +0,0 @@
{
"variables": {
"ucloud_public_key": "{{env `UCLOUD_PUBLIC_KEY`}}",
"ucloud_private_key": "{{env `UCLOUD_PRIVATE_KEY`}}",
"ucloud_project_id": "{{env `UCLOUD_PROJECT_ID`}}",
"password": "ucloud_2019"
},
"builders": [{
"type": "ucloud-uhost",
"public_key": "{{user `ucloud_public_key`}}",
"private_key": "{{user `ucloud_private_key`}}",
"project_id": "{{user `ucloud_project_id`}}",
"region": "cn-bj2",
"availability_zone": "cn-bj2-02",
"instance_type": "n-basic-2",
"source_image_id": "uimage-irofn4",
"ssh_password": "{{user `password`}}",
"ssh_username": "ubuntu",
"image_name": "packer-test-ubuntu-bj"
}],
"provisioners": [{
"type": "shell",
"execute_command": "echo '{{user `password`}}' | sudo -S '{{.Path}}'",
"inline": [
"sleep 30",
"sudo apt update",
"sudo apt install nginx -y"
]
}]
}

View File

@ -1,163 +0,0 @@
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
// The ucloud-uhost contains a packersdk.Builder implementation that
// builds uhost images for UCloud UHost instance.
package uhost
import (
"context"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer-plugin-sdk/common"
"github.com/hashicorp/packer-plugin-sdk/communicator"
"github.com/hashicorp/packer-plugin-sdk/multistep"
"github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/template/config"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
ucloudcommon "github.com/hashicorp/packer/builder/ucloud/common"
)
// The unique ID for this builder
const BuilderId = "ucloud.uhost"
type Config struct {
common.PackerConfig `mapstructure:",squash"`
ucloudcommon.AccessConfig `mapstructure:",squash"`
ucloudcommon.ImageConfig `mapstructure:",squash"`
ucloudcommon.RunConfig `mapstructure:",squash"`
ctx interpolate.Context
}
type Builder struct {
config Config
runner multistep.Runner
}
func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() }
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
err := config.Decode(&b.config, &config.DecodeOpts{
PluginType: BuilderId,
Interpolate: true,
InterpolateContext: &b.config.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"run_command",
},
},
}, raws...)
b.config.ctx.EnableEnv = true
if err != nil {
return nil, nil, err
}
// Accumulate any errors
var errs *packersdk.MultiError
errs = packersdk.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(&b.config.ctx)...)
errs = packersdk.MultiErrorAppend(errs, b.config.ImageConfig.Prepare(&b.config.ctx)...)
errs = packersdk.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...)
if errs != nil && len(errs.Errors) > 0 {
return nil, nil, errs
}
packersdk.LogSecretFilter.Set(b.config.PublicKey, b.config.PrivateKey)
return nil, nil, nil
}
func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) (packersdk.Artifact, error) {
client, err := b.config.Client()
if err != nil {
return nil, err
}
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("client", client)
state.Put("hook", hook)
state.Put("ui", ui)
var steps []multistep.Step
// Build the steps
steps = []multistep.Step{
&stepPreValidate{
ProjectId: b.config.ProjectId,
Region: b.config.Region,
Zone: b.config.Zone,
ImageDestinations: b.config.ImageDestinations,
},
&stepCheckSourceImageId{
SourceUHostImageId: b.config.SourceImageId,
},
&stepConfigVPC{
VPCId: b.config.VPCId,
},
&stepConfigSubnet{
SubnetId: b.config.SubnetId,
},
&stepConfigSecurityGroup{
SecurityGroupId: b.config.SecurityGroupId,
},
&stepCreateInstance{
InstanceType: b.config.InstanceType,
Region: b.config.Region,
Zone: b.config.Zone,
SourceImageId: b.config.SourceImageId,
InstanceName: b.config.InstanceName,
BootDiskType: b.config.BootDiskType,
UsePrivateIp: b.config.UseSSHPrivateIp,
EipBandwidth: b.config.EipBandwidth,
EipChargeMode: b.config.EipChargeMode,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
MinCpuPlatform: b.config.MinCpuPlatform,
},
&communicator.StepConnect{
Config: &b.config.RunConfig.Comm,
Host: ucloudcommon.SSHHost(
b.config.UseSSHPrivateIp),
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
},
&commonsteps.StepProvision{},
&stepStopInstance{},
&stepCreateImage{},
&stepCopyUCloudImage{
ImageDestinations: b.config.ImageDestinations,
RegionId: b.config.Region,
ProjectId: b.config.ProjectId,
WaitImageReadyTimeout: b.config.WaitImageReadyTimeout,
},
}
// Run!
b.runner = commonsteps.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(ctx, state)
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
// If there are no ucloud images, then just return
if _, ok := state.GetOk("ucloud_images"); !ok {
return nil, nil
}
// Build the artifact and return it
artifact := &ucloudcommon.Artifact{
UCloudImages: state.Get("ucloud_images").(*ucloudcommon.ImageInfoSet),
BuilderIdValue: BuilderId,
Client: client,
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
}
return artifact, nil
}

View File

@ -1,194 +0,0 @@
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
package uhost
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer/builder/ucloud/common"
"github.com/zclconf/go-cty/cty"
)
// FlatConfig is an auto-generated flat version of Config.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatConfig struct {
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"`
PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"`
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
PublicKey *string `mapstructure:"public_key" required:"true" cty:"public_key" hcl:"public_key"`
PrivateKey *string `mapstructure:"private_key" required:"true" cty:"private_key" hcl:"private_key"`
Region *string `mapstructure:"region" required:"true" cty:"region" hcl:"region"`
ProjectId *string `mapstructure:"project_id" required:"true" cty:"project_id" hcl:"project_id"`
BaseUrl *string `mapstructure:"base_url" required:"false" cty:"base_url" hcl:"base_url"`
Profile *string `mapstructure:"profile" required:"false" cty:"profile" hcl:"profile"`
SharedCredentialsFile *string `mapstructure:"shared_credentials_file" required:"false" cty:"shared_credentials_file" hcl:"shared_credentials_file"`
ImageName *string `mapstructure:"image_name" required:"true" cty:"image_name" hcl:"image_name"`
ImageDescription *string `mapstructure:"image_description" required:"false" cty:"image_description" hcl:"image_description"`
ImageDestinations []common.FlatImageDestination `mapstructure:"image_copy_to_mappings" required:"false" cty:"image_copy_to_mappings" hcl:"image_copy_to_mappings"`
WaitImageReadyTimeout *int `mapstructure:"wait_image_ready_timeout" required:"false" cty:"wait_image_ready_timeout" hcl:"wait_image_ready_timeout"`
Zone *string `mapstructure:"availability_zone" required:"true" cty:"availability_zone" hcl:"availability_zone"`
SourceImageId *string `mapstructure:"source_image_id" required:"true" cty:"source_image_id" hcl:"source_image_id"`
InstanceType *string `mapstructure:"instance_type" required:"true" cty:"instance_type" hcl:"instance_type"`
InstanceName *string `mapstructure:"instance_name" required:"false" cty:"instance_name" hcl:"instance_name"`
BootDiskType *string `mapstructure:"boot_disk_type" required:"false" cty:"boot_disk_type" hcl:"boot_disk_type"`
VPCId *string `mapstructure:"vpc_id" required:"false" cty:"vpc_id" hcl:"vpc_id"`
SubnetId *string `mapstructure:"subnet_id" required:"false" cty:"subnet_id" hcl:"subnet_id"`
SecurityGroupId *string `mapstructure:"security_group_id" required:"false" cty:"security_group_id" hcl:"security_group_id"`
EipBandwidth *int `mapstructure:"eip_bandwidth" required:"false" cty:"eip_bandwidth" hcl:"eip_bandwidth"`
EipChargeMode *string `mapstructure:"eip_charge_mode" required:"false" cty:"eip_charge_mode" hcl:"eip_charge_mode"`
UserData *string `mapstructure:"user_data" required:"false" cty:"user_data" hcl:"user_data"`
UserDataFile *string `mapstructure:"user_data_file" required:"false" cty:"user_data_file" hcl:"user_data_file"`
MinCpuPlatform *string `mapstructure:"min_cpu_platform" required:"false" cty:"min_cpu_platform" hcl:"min_cpu_platform"`
Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"`
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"`
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"`
SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port" hcl:"ssh_port"`
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"`
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" undocumented:"true" cty:"ssh_private_key_file" hcl:"ssh_private_key_file"`
SSHCertificateFile *string `mapstructure:"ssh_certificate_file" cty:"ssh_certificate_file" hcl:"ssh_certificate_file"`
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty" hcl:"ssh_pty"`
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout" hcl:"ssh_timeout"`
SSHWaitTimeout *string `mapstructure:"ssh_wait_timeout" undocumented:"true" cty:"ssh_wait_timeout" hcl:"ssh_wait_timeout"`
SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" undocumented:"true" cty:"ssh_agent_auth" hcl:"ssh_agent_auth"`
SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding" hcl:"ssh_disable_agent_forwarding"`
SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts" hcl:"ssh_handshake_attempts"`
SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host" hcl:"ssh_bastion_host"`
SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port" hcl:"ssh_bastion_port"`
SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth" hcl:"ssh_bastion_agent_auth"`
SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username" hcl:"ssh_bastion_username"`
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password" hcl:"ssh_bastion_password"`
SSHBastionInteractive *bool `mapstructure:"ssh_bastion_interactive" cty:"ssh_bastion_interactive" hcl:"ssh_bastion_interactive"`
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file" hcl:"ssh_bastion_private_key_file"`
SSHBastionCertificateFile *string `mapstructure:"ssh_bastion_certificate_file" cty:"ssh_bastion_certificate_file" hcl:"ssh_bastion_certificate_file"`
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method" hcl:"ssh_file_transfer_method"`
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host" hcl:"ssh_proxy_host"`
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port" hcl:"ssh_proxy_port"`
SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username" hcl:"ssh_proxy_username"`
SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password" hcl:"ssh_proxy_password"`
SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval" hcl:"ssh_keep_alive_interval"`
SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout" hcl:"ssh_read_write_timeout"`
SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels" hcl:"ssh_remote_tunnels"`
SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels" hcl:"ssh_local_tunnels"`
SSHPublicKey []byte `mapstructure:"ssh_public_key" undocumented:"true" cty:"ssh_public_key" hcl:"ssh_public_key"`
SSHPrivateKey []byte `mapstructure:"ssh_private_key" undocumented:"true" cty:"ssh_private_key" hcl:"ssh_private_key"`
WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username" hcl:"winrm_username"`
WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password" hcl:"winrm_password"`
WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host" hcl:"winrm_host"`
WinRMNoProxy *bool `mapstructure:"winrm_no_proxy" cty:"winrm_no_proxy" hcl:"winrm_no_proxy"`
WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port" hcl:"winrm_port"`
WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout" hcl:"winrm_timeout"`
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl" hcl:"winrm_use_ssl"`
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure" hcl:"winrm_insecure"`
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"`
UseSSHPrivateIp *bool `mapstructure:"use_ssh_private_ip" cty:"use_ssh_private_ip" hcl:"use_ssh_private_ip"`
}
// FlatMapstructure returns a new FlatConfig.
// FlatConfig is an auto-generated flat version of Config.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatConfig)
}
// HCL2Spec returns the hcl spec of a Config.
// This spec is used by HCL to read the fields of Config.
// The decoded values from this spec will then be applied to a FlatConfig.
func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
"packer_core_version": &hcldec.AttrSpec{Name: "packer_core_version", Type: cty.String, Required: false},
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"public_key": &hcldec.AttrSpec{Name: "public_key", Type: cty.String, Required: false},
"private_key": &hcldec.AttrSpec{Name: "private_key", Type: cty.String, Required: false},
"region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false},
"project_id": &hcldec.AttrSpec{Name: "project_id", Type: cty.String, Required: false},
"base_url": &hcldec.AttrSpec{Name: "base_url", Type: cty.String, Required: false},
"profile": &hcldec.AttrSpec{Name: "profile", Type: cty.String, Required: false},
"shared_credentials_file": &hcldec.AttrSpec{Name: "shared_credentials_file", Type: cty.String, Required: false},
"image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false},
"image_description": &hcldec.AttrSpec{Name: "image_description", Type: cty.String, Required: false},
"image_copy_to_mappings": &hcldec.BlockListSpec{TypeName: "image_copy_to_mappings", Nested: hcldec.ObjectSpec((*common.FlatImageDestination)(nil).HCL2Spec())},
"wait_image_ready_timeout": &hcldec.AttrSpec{Name: "wait_image_ready_timeout", Type: cty.Number, Required: false},
"availability_zone": &hcldec.AttrSpec{Name: "availability_zone", Type: cty.String, Required: false},
"source_image_id": &hcldec.AttrSpec{Name: "source_image_id", Type: cty.String, Required: false},
"instance_type": &hcldec.AttrSpec{Name: "instance_type", Type: cty.String, Required: false},
"instance_name": &hcldec.AttrSpec{Name: "instance_name", Type: cty.String, Required: false},
"boot_disk_type": &hcldec.AttrSpec{Name: "boot_disk_type", Type: cty.String, Required: false},
"vpc_id": &hcldec.AttrSpec{Name: "vpc_id", Type: cty.String, Required: false},
"subnet_id": &hcldec.AttrSpec{Name: "subnet_id", Type: cty.String, Required: false},
"security_group_id": &hcldec.AttrSpec{Name: "security_group_id", Type: cty.String, Required: false},
"eip_bandwidth": &hcldec.AttrSpec{Name: "eip_bandwidth", Type: cty.Number, Required: false},
"eip_charge_mode": &hcldec.AttrSpec{Name: "eip_charge_mode", Type: cty.String, Required: false},
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
"user_data_file": &hcldec.AttrSpec{Name: "user_data_file", Type: cty.String, Required: false},
"min_cpu_platform": &hcldec.AttrSpec{Name: "min_cpu_platform", Type: cty.String, Required: false},
"communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false},
"pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false},
"ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false},
"ssh_port": &hcldec.AttrSpec{Name: "ssh_port", Type: cty.Number, Required: false},
"ssh_username": &hcldec.AttrSpec{Name: "ssh_username", Type: cty.String, Required: false},
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
"ssh_private_key_file": &hcldec.AttrSpec{Name: "ssh_private_key_file", Type: cty.String, Required: false},
"ssh_certificate_file": &hcldec.AttrSpec{Name: "ssh_certificate_file", Type: cty.String, Required: false},
"ssh_pty": &hcldec.AttrSpec{Name: "ssh_pty", Type: cty.Bool, Required: false},
"ssh_timeout": &hcldec.AttrSpec{Name: "ssh_timeout", Type: cty.String, Required: false},
"ssh_wait_timeout": &hcldec.AttrSpec{Name: "ssh_wait_timeout", Type: cty.String, Required: false},
"ssh_agent_auth": &hcldec.AttrSpec{Name: "ssh_agent_auth", Type: cty.Bool, Required: false},
"ssh_disable_agent_forwarding": &hcldec.AttrSpec{Name: "ssh_disable_agent_forwarding", Type: cty.Bool, Required: false},
"ssh_handshake_attempts": &hcldec.AttrSpec{Name: "ssh_handshake_attempts", Type: cty.Number, Required: false},
"ssh_bastion_host": &hcldec.AttrSpec{Name: "ssh_bastion_host", Type: cty.String, Required: false},
"ssh_bastion_port": &hcldec.AttrSpec{Name: "ssh_bastion_port", Type: cty.Number, Required: false},
"ssh_bastion_agent_auth": &hcldec.AttrSpec{Name: "ssh_bastion_agent_auth", Type: cty.Bool, Required: false},
"ssh_bastion_username": &hcldec.AttrSpec{Name: "ssh_bastion_username", Type: cty.String, Required: false},
"ssh_bastion_password": &hcldec.AttrSpec{Name: "ssh_bastion_password", Type: cty.String, Required: false},
"ssh_bastion_interactive": &hcldec.AttrSpec{Name: "ssh_bastion_interactive", Type: cty.Bool, Required: false},
"ssh_bastion_private_key_file": &hcldec.AttrSpec{Name: "ssh_bastion_private_key_file", Type: cty.String, Required: false},
"ssh_bastion_certificate_file": &hcldec.AttrSpec{Name: "ssh_bastion_certificate_file", Type: cty.String, Required: false},
"ssh_file_transfer_method": &hcldec.AttrSpec{Name: "ssh_file_transfer_method", Type: cty.String, Required: false},
"ssh_proxy_host": &hcldec.AttrSpec{Name: "ssh_proxy_host", Type: cty.String, Required: false},
"ssh_proxy_port": &hcldec.AttrSpec{Name: "ssh_proxy_port", Type: cty.Number, Required: false},
"ssh_proxy_username": &hcldec.AttrSpec{Name: "ssh_proxy_username", Type: cty.String, Required: false},
"ssh_proxy_password": &hcldec.AttrSpec{Name: "ssh_proxy_password", Type: cty.String, Required: false},
"ssh_keep_alive_interval": &hcldec.AttrSpec{Name: "ssh_keep_alive_interval", Type: cty.String, Required: false},
"ssh_read_write_timeout": &hcldec.AttrSpec{Name: "ssh_read_write_timeout", Type: cty.String, Required: false},
"ssh_remote_tunnels": &hcldec.AttrSpec{Name: "ssh_remote_tunnels", Type: cty.List(cty.String), Required: false},
"ssh_local_tunnels": &hcldec.AttrSpec{Name: "ssh_local_tunnels", Type: cty.List(cty.String), Required: false},
"ssh_public_key": &hcldec.AttrSpec{Name: "ssh_public_key", Type: cty.List(cty.Number), Required: false},
"ssh_private_key": &hcldec.AttrSpec{Name: "ssh_private_key", Type: cty.List(cty.Number), Required: false},
"winrm_username": &hcldec.AttrSpec{Name: "winrm_username", Type: cty.String, Required: false},
"winrm_password": &hcldec.AttrSpec{Name: "winrm_password", Type: cty.String, Required: false},
"winrm_host": &hcldec.AttrSpec{Name: "winrm_host", Type: cty.String, Required: false},
"winrm_no_proxy": &hcldec.AttrSpec{Name: "winrm_no_proxy", Type: cty.Bool, Required: false},
"winrm_port": &hcldec.AttrSpec{Name: "winrm_port", Type: cty.Number, Required: false},
"winrm_timeout": &hcldec.AttrSpec{Name: "winrm_timeout", Type: cty.String, Required: false},
"winrm_use_ssl": &hcldec.AttrSpec{Name: "winrm_use_ssl", Type: cty.Bool, Required: false},
"winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false},
"winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false},
"use_ssh_private_ip": &hcldec.AttrSpec{Name: "use_ssh_private_ip", Type: cty.Bool, Required: false},
}
return s
}

View File

@ -1,221 +0,0 @@
package uhost
import (
"fmt"
"os"
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
builderT "github.com/hashicorp/packer/acctest"
ucloudcommon "github.com/hashicorp/packer/builder/ucloud/common"
"github.com/stretchr/testify/assert"
)
func TestBuilderAcc_validateRegion(t *testing.T) {
t.Parallel()
if os.Getenv(builderT.TestEnvVar) == "" {
t.Skip(fmt.Sprintf("Acceptance tests skipped unless env '%s' set", builderT.TestEnvVar))
return
}
testAccPreCheck(t)
access := &ucloudcommon.AccessConfig{Region: "cn-bj2"}
err := access.Config()
if err != nil {
t.Fatalf("Error on initing UCloud AccessConfig, %s", err)
}
err = access.ValidateRegion("cn-sh2")
if err != nil {
t.Fatalf("Expected pass with valid region but failed: %s", err)
}
err = access.ValidateRegion("invalidRegion")
if err == nil {
t.Fatal("Expected failure due to invalid region but passed")
}
}
func TestBuilderAcc_basic(t *testing.T) {
t.Parallel()
builderT.Test(t, builderT.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Builder: &Builder{},
Template: testBuilderAccBasic,
})
}
const testBuilderAccBasic = `
{ "builders": [{
"type": "test",
"region": "cn-bj2",
"availability_zone": "cn-bj2-02",
"instance_type": "n-basic-2",
"source_image_id":"uimage-f1chxn",
"ssh_username":"root",
"image_name": "packer-test-basic_{{timestamp}}"
}]
}`
func TestBuilderAcc_ubuntu(t *testing.T) {
t.Parallel()
builderT.Test(t, builderT.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Builder: &Builder{},
Template: testBuilderAccUbuntu,
})
}
const testBuilderAccUbuntu = `
{ "builders": [{
"type": "test",
"region": "cn-bj2",
"availability_zone": "cn-bj2-02",
"instance_type": "n-basic-2",
"source_image_id":"uimage-irofn4",
"ssh_username":"ubuntu",
"image_name": "packer-test-ubuntu_{{timestamp}}"
}]
}`
func TestBuilderAcc_regionCopy(t *testing.T) {
t.Parallel()
projectId := os.Getenv("UCLOUD_PROJECT_ID")
builderT.Test(t, builderT.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Builder: &Builder{},
Template: testBuilderAccRegionCopy(projectId),
Check: checkRegionCopy(
projectId,
[]ucloudcommon.ImageDestination{
{ProjectId: projectId, Region: "cn-sh2", Name: "packer-test-regionCopy-sh", Description: "test"},
}),
})
}
func testBuilderAccRegionCopy(projectId string) string {
return fmt.Sprintf(`
{
"builders": [{
"type": "test",
"region": "cn-bj2",
"availability_zone": "cn-bj2-02",
"instance_type": "n-basic-2",
"source_image_id":"uimage-f1chxn",
"ssh_username":"root",
"image_name": "packer-test-regionCopy-bj",
"image_copy_to_mappings": [{
"project_id": %q,
"region": "cn-sh2",
"name": "packer-test-regionCopy-sh",
"description": "test"
}]
}]
}`, projectId)
}
func checkRegionCopy(projectId string, imageDst []ucloudcommon.ImageDestination) builderT.TestCheckFunc {
return func(artifacts []packersdk.Artifact) error {
if len(artifacts) > 1 {
return fmt.Errorf("more than 1 artifact")
}
artifactSet := artifacts[0]
artifact, ok := artifactSet.(*ucloudcommon.Artifact)
if !ok {
return fmt.Errorf("unknown artifact: %#v", artifactSet)
}
destSet := ucloudcommon.NewImageInfoSet(nil)
for _, dest := range imageDst {
destSet.Set(ucloudcommon.ImageInfo{
Region: dest.Region,
ProjectId: dest.ProjectId,
})
}
for _, r := range artifact.UCloudImages.GetAll() {
if r.ProjectId == projectId && r.Region == "cn-bj2" {
destSet.Remove(r.Id())
continue
}
if destSet.Get(r.ProjectId, r.Region) == nil {
return fmt.Errorf("project%s : region%s is not the target but found in artifacts", r.ProjectId, r.Region)
}
destSet.Remove(r.Id())
}
if len(destSet.GetAll()) > 0 {
return fmt.Errorf("the following copying targets not found in corresponding artifacts : %#v", destSet.GetAll())
}
client, _ := testUCloudClient()
for _, r := range artifact.UCloudImages.GetAll() {
if r.ProjectId == projectId && r.Region == "cn-bj2" {
continue
}
imageSet, err := client.DescribeImageByInfo(r.ProjectId, r.Region, r.ImageId)
if err != nil {
if ucloudcommon.IsNotFoundError(err) {
return fmt.Errorf("image %s in artifacts can not be found", r.ImageId)
}
return err
}
if r.Region == "cn-sh2" && imageSet.ImageName != "packer-test-regionCopy-sh" {
return fmt.Errorf("the name of image %q in artifacts should be %s, got %s", r.ImageId, "packer-test-regionCopy-sh", imageSet.ImageName)
}
}
return nil
}
}
func testAccPreCheck(t *testing.T) {
if v := os.Getenv("UCLOUD_PUBLIC_KEY"); v == "" {
t.Fatal("UCLOUD_PUBLIC_KEY must be set for acceptance tests")
}
if v := os.Getenv("UCLOUD_PRIVATE_KEY"); v == "" {
t.Fatal("UCLOUD_PRIVATE_KEY must be set for acceptance tests")
}
if v := os.Getenv("UCLOUD_PROJECT_ID"); v == "" {
t.Fatal("UCLOUD_PROJECT_ID must be set for acceptance tests")
}
}
func TestUCloudClientBaseUrlConfigurable(t *testing.T) {
const url = "baseUrl"
access := &ucloudcommon.AccessConfig{BaseUrl: url, PublicKey: "test", PrivateKey: "test"}
client, err := access.Client()
assert.Nil(t, err)
assert.Equal(t, url, client.UAccountConn.Client.GetConfig().BaseUrl, "account conn's base url not configurable")
assert.Equal(t, url, client.UHostConn.Client.GetConfig().BaseUrl, "host conn's base url not configurable")
assert.Equal(t, url, client.UNetConn.Client.GetConfig().BaseUrl, "net conn's base url not configurable")
assert.Equal(t, url, client.VPCConn.Client.GetConfig().BaseUrl, "vpc conn's base url not configurable")
}
func testUCloudClient() (*ucloudcommon.UCloudClient, error) {
access := &ucloudcommon.AccessConfig{Region: "cn-bj2"}
err := access.Config()
if err != nil {
return nil, err
}
client, err := access.Client()
if err != nil {
return nil, err
}
return client, nil
}

View File

@ -1,141 +0,0 @@
package uhost
import (
"reflect"
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
ucloudcommon "github.com/hashicorp/packer/builder/ucloud/common"
)
func testBuilderConfig() map[string]interface{} {
return map[string]interface{}{
"public_key": "foo",
"private_key": "bar",
"project_id": "foo",
"source_image_id": "bar",
"availability_zone": "cn-bj2-02",
"instance_type": "n-basic-2",
"region": "cn-bj2",
"ssh_username": "root",
"image_name": "foo",
}
}
func TestBuilder_ImplementsBuilder(t *testing.T) {
var raw interface{}
raw = &Builder{}
if _, ok := raw.(packersdk.Builder); !ok {
t.Fatalf("Builder should be a builder")
}
}
func TestBuilder_Prepare_BadType(t *testing.T) {
b := &Builder{}
c := map[string]interface{}{
"public_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_ImageName(t *testing.T) {
var b Builder
config := testBuilderConfig()
// Test good
config["image_name"] = "foo"
_, 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["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")
}
}
func TestBuilderPrepare_ImageDestinations(t *testing.T) {
var b Builder
config := testBuilderConfig()
config["image_copy_to_mappings"] = []map[string]interface{}{
{
"project_id": "project1",
"region": "region1",
"name": "bar",
"description": "foo",
},
{
"project_id": "project2",
"region": "region2",
"name": "foo",
"description": "bar",
},
}
_, 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)
}
if !reflect.DeepEqual(b.config.ImageDestinations, []ucloudcommon.ImageDestination{
{
ProjectId: "project1",
Region: "region1",
Name: "bar",
Description: "foo",
},
{
ProjectId: "project2",
Region: "region2",
Name: "foo",
Description: "bar",
},
}) {
t.Fatalf("image_copy_mappings are not set properly, got: %#v", b.config.ImageDestinations)
}
}

View File

@ -1,46 +0,0 @@
package uhost
import (
"context"
"fmt"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
ucloudcommon "github.com/hashicorp/packer/builder/ucloud/common"
)
type stepCheckSourceImageId struct {
SourceUHostImageId string
}
func (s *stepCheckSourceImageId) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packersdk.Ui)
client := state.Get("client").(*ucloudcommon.UCloudClient)
ui.Say("Querying source image id...")
imageSet, err := client.DescribeImageById(s.SourceUHostImageId)
if err != nil {
if ucloudcommon.IsNotFoundError(err) {
return ucloudcommon.Halt(state, err, "")
}
return ucloudcommon.Halt(state, err, fmt.Sprintf("Error on querying specified source_image_id %q", s.SourceUHostImageId))
}
if imageSet.OsType == ucloudcommon.OsTypeWindows {
return ucloudcommon.Halt(state, err, "The ucloud-uhost builder does not support Windows images yet")
}
_, uOK := state.GetOk("user_data")
_, fOK := state.GetOk("user_data_file")
if uOK || fOK {
if !ucloudcommon.IsStringIn("CloudInit", imageSet.Features) {
return ucloudcommon.Halt(state, err, fmt.Sprintf("The image %s must have %q feature when set the %q or %q, got %#v", imageSet.ImageId, "CloudInit", "user_data", "user_data_file", imageSet.Features))
}
}
state.Put("source_image", imageSet)
return multistep.ActionContinue
}
func (s *stepCheckSourceImageId) Cleanup(multistep.StateBag) {}

View File

@ -1,79 +0,0 @@
package uhost
import (
"context"
"fmt"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
ucloudcommon "github.com/hashicorp/packer/builder/ucloud/common"
"github.com/ucloud/ucloud-sdk-go/ucloud"
)
type stepConfigSecurityGroup struct {
SecurityGroupId string
}
func (s *stepConfigSecurityGroup) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*ucloudcommon.UCloudClient)
conn := client.UNetConn
ui := state.Get("ui").(packersdk.Ui)
if len(s.SecurityGroupId) != 0 {
ui.Say(fmt.Sprintf("Trying to use specified security group %q...", s.SecurityGroupId))
securityGroupSet, err := client.DescribeFirewallById(s.SecurityGroupId)
if err != nil {
if ucloudcommon.IsNotFoundError(err) {
err = fmt.Errorf("the specified security group %q does not exist", s.SecurityGroupId)
return ucloudcommon.Halt(state, err, "")
}
return ucloudcommon.Halt(state, err, fmt.Sprintf("Error on querying specified security group %q", s.SecurityGroupId))
}
state.Put("security_group_id", securityGroupSet.FWId)
return multistep.ActionContinue
}
ui.Say("Trying to use default security group...")
var securityGroupId string
var limit = 100
var offset int
for {
req := conn.NewDescribeFirewallRequest()
req.Limit = ucloud.Int(limit)
req.Offset = ucloud.Int(offset)
resp, err := conn.DescribeFirewall(req)
if err != nil {
return ucloudcommon.Halt(state, err, "Error on querying default security group")
}
if resp == nil || len(resp.DataSet) < 1 {
break
}
for _, item := range resp.DataSet {
if item.Type == ucloudcommon.SecurityGroupNonWeb {
securityGroupId = item.FWId
break
}
}
if len(resp.DataSet) < limit {
break
}
offset = offset + limit
}
if securityGroupId == "" {
return ucloudcommon.Halt(state, fmt.Errorf("the default security group does not exist"), "")
}
state.Put("security_group_id", securityGroupId)
return multistep.ActionContinue
}
func (s *stepConfigSecurityGroup) Cleanup(state multistep.StateBag) {
}

View File

@ -1,41 +0,0 @@
package uhost
import (
"context"
"fmt"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
ucloudcommon "github.com/hashicorp/packer/builder/ucloud/common"
)
type stepConfigSubnet struct {
SubnetId string
}
func (s *stepConfigSubnet) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*ucloudcommon.UCloudClient)
ui := state.Get("ui").(packersdk.Ui)
if len(s.SubnetId) != 0 {
ui.Say(fmt.Sprintf("Trying to use specified subnet %q...", s.SubnetId))
subnetSet, err := client.DescribeSubnetById(s.SubnetId)
if err != nil {
if ucloudcommon.IsNotFoundError(err) {
err = fmt.Errorf("the specified subnet %q does not exist", s.SubnetId)
return ucloudcommon.Halt(state, err, "")
}
return ucloudcommon.Halt(state, err, fmt.Sprintf("Error on querying specified subnet %q", s.SubnetId))
}
state.Put("subnet_id", subnetSet.SubnetId)
return multistep.ActionContinue
}
ui.Say(fmt.Sprintf("Trying to use default subnet..."))
return multistep.ActionContinue
}
func (s *stepConfigSubnet) Cleanup(state multistep.StateBag) {
}

View File

@ -1,44 +0,0 @@
package uhost
import (
"context"
"fmt"
ucloudcommon "github.com/hashicorp/packer/builder/ucloud/common"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type stepConfigVPC struct {
VPCId string
}
func (s *stepConfigVPC) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*ucloudcommon.UCloudClient)
ui := state.Get("ui").(packersdk.Ui)
if len(s.VPCId) != 0 {
ui.Say(fmt.Sprintf("Trying to use specified vpc %q...", s.VPCId))
vpcSet, err := client.DescribeVPCById(s.VPCId)
if err != nil {
if ucloudcommon.IsNotFoundError(err) {
err = fmt.Errorf("the specified vpc %q does not exist", s.VPCId)
return ucloudcommon.Halt(state, err, "")
}
return ucloudcommon.Halt(state, err, fmt.Sprintf("Error on querying specified vpc %q", s.VPCId))
}
state.Put("vpc_id", vpcSet.VPCId)
return multistep.ActionContinue
}
ui.Say(fmt.Sprintf("Trying to use default vpc..."))
return multistep.ActionContinue
}
func (s *stepConfigVPC) Cleanup(state multistep.StateBag) {
}

View File

@ -1,147 +0,0 @@
package uhost
import (
"context"
"fmt"
"strings"
"time"
"github.com/hashicorp/packer-plugin-sdk/retry"
ucloudcommon "github.com/hashicorp/packer/builder/ucloud/common"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/ucloud/ucloud-sdk-go/ucloud"
)
type stepCopyUCloudImage struct {
ImageDestinations []ucloudcommon.ImageDestination
RegionId string
ProjectId string
WaitImageReadyTimeout int
}
func (s *stepCopyUCloudImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
if len(s.ImageDestinations) == 0 {
return multistep.ActionContinue
}
client := state.Get("client").(*ucloudcommon.UCloudClient)
conn := client.UHostConn
ui := state.Get("ui").(packersdk.Ui)
srcImageId := state.Get("image_id").(string)
artifactImages := state.Get("ucloud_images").(*ucloudcommon.ImageInfoSet)
expectedImages := ucloudcommon.NewImageInfoSet(nil)
ui.Say(fmt.Sprintf("Copying images from %q...", srcImageId))
for _, v := range s.ImageDestinations {
if v.ProjectId == s.ProjectId && v.Region == s.RegionId {
continue
}
req := conn.NewCopyCustomImageRequest()
req.TargetProjectId = ucloud.String(v.ProjectId)
req.TargetRegion = ucloud.String(v.Region)
req.SourceImageId = ucloud.String(srcImageId)
req.TargetImageName = ucloud.String(v.Name)
req.TargetImageDescription = ucloud.String(v.Description)
resp, err := conn.CopyCustomImage(req)
if err != nil {
return ucloudcommon.Halt(state, err, fmt.Sprintf("Error on copying image %q to %s:%s", srcImageId, v.ProjectId, v.Region))
}
image := ucloudcommon.ImageInfo{
Region: v.Region,
ProjectId: v.ProjectId,
ImageId: resp.TargetImageId,
}
expectedImages.Set(image)
artifactImages.Set(image)
ui.Message(fmt.Sprintf("Copying image from %s:%s:%s to %s:%s:%s",
s.ProjectId, s.RegionId, srcImageId, v.ProjectId, v.Region, resp.TargetImageId))
}
ui.Message("Waiting for the copied images to become available...")
err := retry.Config{
StartTimeout: time.Duration(s.WaitImageReadyTimeout) * time.Second,
ShouldRetry: func(err error) bool {
return ucloudcommon.IsNotCompleteError(err)
},
RetryDelay: (&retry.Backoff{InitialBackoff: 2 * time.Second, MaxBackoff: 12 * time.Second, Multiplier: 2}).Linear,
}.Run(ctx, func(ctx context.Context) error {
for _, v := range expectedImages.GetAll() {
imageSet, err := client.DescribeImageByInfo(v.ProjectId, v.Region, v.ImageId)
if err != nil {
return fmt.Errorf("reading copied image %s:%s:%s failed, %s", v.ProjectId, v.Region, v.ImageId, err)
}
if imageSet.State == ucloudcommon.ImageStateAvailable {
expectedImages.Remove(v.Id())
continue
}
if imageSet.State == ucloudcommon.ImageStateUnavailable {
return fmt.Errorf("the copied image %s:%s:%s got %q error", v.ProjectId, v.Region, v.ImageId, ucloudcommon.ImageStateUnavailable)
}
}
if len(expectedImages.GetAll()) != 0 {
return ucloudcommon.NewNotCompletedError("copying image")
}
return nil
})
if err != nil {
var s []string
for _, v := range expectedImages.GetAll() {
s = append(s, fmt.Sprintf("%s:%s:%s", v.ProjectId, v.Region, v.ImageId))
}
return ucloudcommon.Halt(state, err, fmt.Sprintf("Error on waiting for copying images %q to become available", strings.Join(s, ",")))
}
ui.Message(fmt.Sprintf("Copying image complete"))
return multistep.ActionContinue
}
func (s *stepCopyUCloudImage) Cleanup(state multistep.StateBag) {
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if !cancelled && !halted {
return
}
srcImageId := state.Get("image_id").(string)
ucloudImages := state.Get("ucloud_images").(*ucloudcommon.ImageInfoSet)
imageInfos := ucloudImages.GetAll()
if len(imageInfos) == 0 {
return
} else if len(imageInfos) == 1 && imageInfos[0].ImageId == srcImageId {
return
}
ui := state.Get("ui").(packersdk.Ui)
client := state.Get("client").(*ucloudcommon.UCloudClient)
conn := client.UHostConn
ui.Say(fmt.Sprintf("Deleting copied image because of cancellation or error..."))
for _, v := range imageInfos {
if v.ImageId == srcImageId {
continue
}
req := conn.NewTerminateCustomImageRequest()
req.ProjectId = ucloud.String(v.ProjectId)
req.Region = ucloud.String(v.Region)
req.ImageId = ucloud.String(v.ImageId)
_, err := conn.TerminateCustomImage(req)
if err != nil {
ui.Error(fmt.Sprintf("Error on deleting copied image %q", v.ImageId))
}
}
ui.Message("Deleting copied image complete")
}

View File

@ -1,106 +0,0 @@
package uhost
import (
"context"
"fmt"
"time"
"github.com/hashicorp/packer-plugin-sdk/retry"
ucloudcommon "github.com/hashicorp/packer/builder/ucloud/common"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/ucloud/ucloud-sdk-go/services/uhost"
"github.com/ucloud/ucloud-sdk-go/ucloud"
)
type stepCreateImage struct {
image *uhost.UHostImageSet
}
func (s *stepCreateImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*ucloudcommon.UCloudClient)
conn := client.UHostConn
instance := state.Get("instance").(*uhost.UHostInstanceSet)
ui := state.Get("ui").(packersdk.Ui)
config := state.Get("config").(*Config)
ui.Say(fmt.Sprintf("Creating image %s...", config.ImageName))
req := conn.NewCreateCustomImageRequest()
req.ImageName = ucloud.String(config.ImageName)
req.ImageDescription = ucloud.String(config.ImageDescription)
req.UHostId = ucloud.String(instance.UHostId)
resp, err := conn.CreateCustomImage(req)
if err != nil {
return ucloudcommon.Halt(state, err, "Error on creating image")
}
ui.Message(fmt.Sprintf("Waiting for the created image %q to become available...", resp.ImageId))
err = retry.Config{
StartTimeout: time.Duration(config.WaitImageReadyTimeout) * time.Second,
ShouldRetry: func(err error) bool {
return ucloudcommon.IsExpectedStateError(err)
},
RetryDelay: (&retry.Backoff{InitialBackoff: 2 * time.Second, MaxBackoff: 12 * time.Second, Multiplier: 2}).Linear,
}.Run(ctx, func(ctx context.Context) error {
inst, err := client.DescribeImageById(resp.ImageId)
if err != nil {
return err
}
if inst == nil || inst.State != ucloudcommon.ImageStateAvailable {
return ucloudcommon.NewExpectedStateError("image", resp.ImageId)
}
return nil
})
if err != nil {
return ucloudcommon.Halt(state, err, fmt.Sprintf("Error on waiting for image %q to become available", resp.ImageId))
}
imageSet, err := client.DescribeImageById(resp.ImageId)
if err != nil {
return ucloudcommon.Halt(state, err, fmt.Sprintf("Error on reading image when creating %q", resp.ImageId))
}
s.image = imageSet
state.Put("image_id", imageSet.ImageId)
images := []ucloudcommon.ImageInfo{
{
ImageId: imageSet.ImageId,
ProjectId: config.ProjectId,
Region: config.Region,
},
}
state.Put("ucloud_images", ucloudcommon.NewImageInfoSet(images))
ui.Message(fmt.Sprintf("Creating image %q complete", imageSet.ImageId))
return multistep.ActionContinue
}
func (s *stepCreateImage) 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").(*ucloudcommon.UCloudClient)
conn := client.UHostConn
ui := state.Get("ui").(packersdk.Ui)
ui.Say("Deleting image because of cancellation or error...")
req := conn.NewTerminateCustomImageRequest()
req.ImageId = ucloud.String(s.image.ImageId)
_, err := conn.TerminateCustomImage(req)
if err != nil {
ui.Error(fmt.Sprintf("Error on deleting image %q", s.image.ImageId))
}
ui.Message(fmt.Sprintf("Deleting image %q complete", s.image.ImageId))
}

View File

@ -1,338 +0,0 @@
package uhost
import (
"context"
"encoding/base64"
"fmt"
"io/ioutil"
"math/rand"
"strings"
"time"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/retry"
ucloudcommon "github.com/hashicorp/packer/builder/ucloud/common"
"github.com/ucloud/ucloud-sdk-go/services/uhost"
"github.com/ucloud/ucloud-sdk-go/ucloud"
)
type stepCreateInstance struct {
Region string
Zone string
InstanceType string
InstanceName string
BootDiskType string
SourceImageId string
UsePrivateIp bool
EipBandwidth int
EipChargeMode string
UserData string
UserDataFile string
MinCpuPlatform string
instanceId string
}
func (s *stepCreateInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*ucloudcommon.UCloudClient)
conn := client.UHostConn
ui := state.Get("ui").(packersdk.Ui)
ui.Say("Creating Instance...")
req, err := s.buildCreateInstanceRequest(state)
if err != nil {
return ucloudcommon.Halt(state, err, "Error on build instance request")
}
resp, err := conn.CreateUHostInstance(req)
if err != nil {
return ucloudcommon.Halt(state, err, "Error on creating instance")
}
instanceId := resp.UHostIds[0]
err = retry.Config{
Tries: 100,
ShouldRetry: func(err error) bool {
return ucloudcommon.IsExpectedStateError(err)
},
RetryDelay: (&retry.Backoff{InitialBackoff: 2 * time.Second, MaxBackoff: 6 * time.Second, Multiplier: 2}).Linear,
}.Run(ctx, func(ctx context.Context) error {
inst, err := client.DescribeUHostById(instanceId)
if err != nil {
return err
}
if inst.State == "ResizeFail" {
return fmt.Errorf("resizing instance failed")
}
if inst.State == "Install Fail" {
return fmt.Errorf("install failed")
}
if inst == nil || inst.State != ucloudcommon.InstanceStateRunning {
return ucloudcommon.NewExpectedStateError("instance", instanceId)
}
return nil
})
if err != nil {
return ucloudcommon.Halt(state, err, fmt.Sprintf("Error on waiting for instance %q to become available", instanceId))
}
ui.Message(fmt.Sprintf("Creating instance %q complete", instanceId))
instance, err := client.DescribeUHostById(instanceId)
if err != nil {
return ucloudcommon.Halt(state, err, fmt.Sprintf("Error on reading instance when creating %q", instanceId))
}
s.instanceId = instanceId
state.Put("instance", instance)
// instance_id is the generic term used so that users can have access to the
// instance id inside of the provisioners, used in step_provision.
state.Put("instance_id", instance)
if instance.BootDiskState != ucloudcommon.BootDiskStateNormal {
ui.Say("Waiting for boot disk of instance initialized")
if s.BootDiskType == "local_normal" || s.BootDiskType == "local_ssd" {
ui.Message(fmt.Sprintf("Warning: It takes around 10 mins for boot disk initialization when `boot_disk_type` is %q", s.BootDiskType))
}
err = retry.Config{
Tries: 200,
ShouldRetry: func(err error) bool {
return ucloudcommon.IsExpectedStateError(err)
},
RetryDelay: (&retry.Backoff{InitialBackoff: 2 * time.Second, MaxBackoff: 12 * time.Second, Multiplier: 2}).Linear,
}.Run(ctx, func(ctx context.Context) error {
inst, err := client.DescribeUHostById(instanceId)
if err != nil {
return err
}
if inst.BootDiskState != ucloudcommon.BootDiskStateNormal {
return ucloudcommon.NewExpectedStateError("boot_disk of instance", instanceId)
}
return nil
})
if err != nil {
return ucloudcommon.Halt(state, err, fmt.Sprintf("Error on waiting for boot disk of instance %q initialized", instanceId))
}
ui.Message(fmt.Sprintf("Waiting for boot disk of instance %q initialized complete", instanceId))
}
return multistep.ActionContinue
}
func (s *stepCreateInstance) Cleanup(state multistep.StateBag) {
if s.instanceId == "" {
return
}
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
ui := state.Get("ui").(packersdk.Ui)
ctx := context.TODO()
if cancelled || halted {
ui.Say("Deleting instance because of cancellation or error...")
} else {
ui.Say("Deleting instance...")
}
client := state.Get("client").(*ucloudcommon.UCloudClient)
conn := client.UHostConn
instance, err := client.DescribeUHostById(s.instanceId)
if err != nil {
if ucloudcommon.IsNotFoundError(err) {
return
}
ui.Error(fmt.Sprintf("Error on reading instance when deleting %q, %s",
s.instanceId, err.Error()))
return
}
if instance.State != ucloudcommon.InstanceStateStopped {
stopReq := conn.NewStopUHostInstanceRequest()
stopReq.UHostId = ucloud.String(s.instanceId)
if _, err = conn.StopUHostInstance(stopReq); err != nil {
ui.Error(fmt.Sprintf("Error on stopping instance when deleting %q, %s",
s.instanceId, err.Error()))
return
}
err = retry.Config{
Tries: 100,
ShouldRetry: func(err error) bool {
return ucloudcommon.IsExpectedStateError(err)
},
RetryDelay: (&retry.Backoff{InitialBackoff: 2 * time.Second, MaxBackoff: 6 * time.Second, Multiplier: 2}).Linear,
}.Run(ctx, func(ctx context.Context) error {
instance, err := client.DescribeUHostById(s.instanceId)
if err != nil {
return err
}
if instance.State != ucloudcommon.InstanceStateStopped {
return ucloudcommon.NewExpectedStateError("instance", s.instanceId)
}
return nil
})
if err != nil {
ui.Error(fmt.Sprintf("Error on waiting for stopping instance when deleting %q, %s",
s.instanceId, err.Error()))
return
}
}
deleteReq := conn.NewTerminateUHostInstanceRequest()
deleteReq.UHostId = ucloud.String(s.instanceId)
deleteReq.ReleaseUDisk = ucloud.Bool(true)
deleteReq.ReleaseEIP = ucloud.Bool(true)
if _, err = conn.TerminateUHostInstance(deleteReq); err != nil {
ui.Error(fmt.Sprintf("Error on deleting instance %q, %s",
s.instanceId, err.Error()))
return
}
err = retry.Config{
Tries: 50,
ShouldRetry: func(err error) bool { return !ucloudcommon.IsNotFoundError(err) },
RetryDelay: (&retry.Backoff{InitialBackoff: 2 * time.Second, MaxBackoff: 6 * time.Second, Multiplier: 2}).Linear,
}.Run(ctx, func(ctx context.Context) error {
_, err := client.DescribeUHostById(s.instanceId)
return err
})
if err != nil {
ui.Error(fmt.Sprintf("Error on waiting for instance %q to be deleted: %s",
s.instanceId, err.Error()))
return
}
ui.Message(fmt.Sprintf("Deleting instance %q complete", s.instanceId))
}
func (s *stepCreateInstance) buildCreateInstanceRequest(state multistep.StateBag) (*uhost.CreateUHostInstanceRequest, error) {
client := state.Get("client").(*ucloudcommon.UCloudClient)
conn := client.UHostConn
srcImage := state.Get("source_image").(*uhost.UHostImageSet)
config := state.Get("config").(*Config)
connectConfig := &config.RunConfig.Comm
var password string
if srcImage.OsType == "Linux" {
password = config.Comm.SSHPassword
}
if password == "" {
password = fmt.Sprintf("%s%s%s",
s.randStringFromCharSet(5, ucloudcommon.DefaultPasswordStr),
s.randStringFromCharSet(1, ucloudcommon.DefaultPasswordSpe),
s.randStringFromCharSet(5, ucloudcommon.DefaultPasswordNum))
if srcImage.OsType == "Linux" {
connectConfig.SSHPassword = password
}
}
req := conn.NewCreateUHostInstanceRequest()
t, _ := ucloudcommon.ParseInstanceType(s.InstanceType)
req.CPU = ucloud.Int(t.CPU)
req.Memory = ucloud.Int(t.Memory)
req.Name = ucloud.String(s.InstanceName)
req.LoginMode = ucloud.String("Password")
req.Zone = ucloud.String(s.Zone)
req.ImageId = ucloud.String(s.SourceImageId)
req.ChargeType = ucloud.String("Dynamic")
req.Password = ucloud.String(password)
req.MinimalCpuPlatform = ucloud.String(s.MinCpuPlatform)
req.MachineType = ucloud.String(strings.ToUpper(t.HostType))
if v, ok := state.GetOk("security_group_id"); ok {
req.SecurityGroupId = ucloud.String(v.(string))
}
if v, ok := state.GetOk("vpc_id"); ok {
req.VPCId = ucloud.String(v.(string))
}
if v, ok := state.GetOk("subnet_id"); ok {
req.SubnetId = ucloud.String(v.(string))
}
userData, err := s.getUserData(state)
if err != nil {
return nil, err
}
if userData != "" {
req.UserData = ucloud.String(userData)
}
bootDisk := uhost.UHostDisk{}
bootDisk.IsBoot = ucloud.String("true")
bootDisk.Size = ucloud.Int(srcImage.ImageSize)
bootDisk.Type = ucloud.String(ucloudcommon.BootDiskTypeMap.Convert(s.BootDiskType))
req.Disks = append(req.Disks, bootDisk)
if v, ok := state.GetOk("user_data"); ok {
req.UserData = ucloud.String(base64.StdEncoding.EncodeToString([]byte(v.(string))))
}
if !s.UsePrivateIp {
operatorName := ucloud.String("International")
if strings.HasPrefix(s.Region, "cn-") {
operatorName = ucloud.String("Bgp")
}
networkInterface := uhost.CreateUHostInstanceParamNetworkInterface{
EIP: &uhost.CreateUHostInstanceParamNetworkInterfaceEIP{
Bandwidth: ucloud.Int(s.EipBandwidth),
PayMode: ucloud.String(ucloudcommon.ChargeModeMap.Convert(s.EipChargeMode)),
OperatorName: operatorName,
},
}
req.NetworkInterface = append(req.NetworkInterface, networkInterface)
}
return req, nil
}
func (s *stepCreateInstance) randStringFromCharSet(strlen int, charSet string) string {
rand.Seed(time.Now().UTC().UnixNano())
result := make([]byte, strlen)
for i := 0; i < strlen; i++ {
result[i] = charSet[rand.Intn(len(charSet))]
}
return string(result)
}
func (s *stepCreateInstance) getUserData(state multistep.StateBag) (string, error) {
userData := s.UserData
if s.UserDataFile != "" {
data, err := ioutil.ReadFile(s.UserDataFile)
if err != nil {
return "", fmt.Errorf("error on reading user_data_file, %s", err)
}
userData = string(data)
}
if userData != "" {
userData = base64.StdEncoding.EncodeToString([]byte(userData))
}
return userData, nil
}

View File

@ -1,99 +0,0 @@
package uhost
import (
"context"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
ucloudcommon "github.com/hashicorp/packer/builder/ucloud/common"
)
type stepPreValidate struct {
ProjectId string
Region string
Zone string
ImageDestinations []ucloudcommon.ImageDestination
}
func (s *stepPreValidate) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
if err := s.validateProjectIds(state); err != nil {
return ucloudcommon.Halt(state, err, "")
}
if err := s.validateRegions(state); err != nil {
return ucloudcommon.Halt(state, err, "")
}
if err := s.validateZones(state); err != nil {
return ucloudcommon.Halt(state, err, "")
}
return multistep.ActionContinue
}
func (s *stepPreValidate) validateProjectIds(state multistep.StateBag) error {
ui := state.Get("ui").(packersdk.Ui)
config := state.Get("config").(*Config)
ui.Say("Validating project_id and copied project_ids...")
var errs *packersdk.MultiError
if err := config.ValidateProjectId(s.ProjectId); err != nil {
errs = packersdk.MultiErrorAppend(errs, err)
}
for _, imageDestination := range s.ImageDestinations {
if err := config.ValidateProjectId(imageDestination.ProjectId); err != nil {
errs = packersdk.MultiErrorAppend(errs, err)
}
}
if errs != nil && len(errs.Errors) > 0 {
return errs
}
return nil
}
func (s *stepPreValidate) validateRegions(state multistep.StateBag) error {
ui := state.Get("ui").(packersdk.Ui)
config := state.Get("config").(*Config)
ui.Say("Validating region and copied regions...")
var errs *packersdk.MultiError
if err := config.ValidateRegion(s.Region); err != nil {
errs = packersdk.MultiErrorAppend(errs, err)
}
for _, imageDestination := range s.ImageDestinations {
if err := config.ValidateRegion(imageDestination.Region); err != nil {
errs = packersdk.MultiErrorAppend(errs, err)
}
}
if errs != nil && len(errs.Errors) > 0 {
return errs
}
return nil
}
func (s *stepPreValidate) validateZones(state multistep.StateBag) error {
ui := state.Get("ui").(packersdk.Ui)
config := state.Get("config").(*Config)
ui.Say("Validating availability_zone...")
var errs *packersdk.MultiError
if err := config.ValidateZone(s.Region, s.Zone); err != nil {
errs = packersdk.MultiErrorAppend(errs, err)
}
if errs != nil && len(errs.Errors) > 0 {
return errs
}
return nil
}
func (s *stepPreValidate) Cleanup(multistep.StateBag) {}

View File

@ -1,82 +0,0 @@
package uhost
import (
"context"
"fmt"
"time"
"github.com/hashicorp/packer-plugin-sdk/retry"
ucloudcommon "github.com/hashicorp/packer/builder/ucloud/common"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/ucloud/ucloud-sdk-go/services/uhost"
"github.com/ucloud/ucloud-sdk-go/ucloud"
)
type stepStopInstance struct {
}
func (s *stepStopInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*ucloudcommon.UCloudClient)
conn := client.UHostConn
instance := state.Get("instance").(*uhost.UHostInstanceSet)
ui := state.Get("ui").(packersdk.Ui)
instance, err := client.DescribeUHostById(instance.UHostId)
if err != nil {
return ucloudcommon.Halt(state, err, fmt.Sprintf("Error on reading instance when stopping %q", instance.UHostId))
}
if instance.State != ucloudcommon.InstanceStateStopped {
stopReq := conn.NewStopUHostInstanceRequest()
stopReq.UHostId = ucloud.String(instance.UHostId)
ui.Say(fmt.Sprintf("Stopping instance %q", instance.UHostId))
err = retry.Config{
Tries: 5,
ShouldRetry: func(err error) bool {
return err != nil
},
RetryDelay: (&retry.Backoff{InitialBackoff: 2 * time.Second, MaxBackoff: 6 * time.Second, Multiplier: 2}).Linear,
}.Run(ctx, func(ctx context.Context) error {
if _, err = conn.StopUHostInstance(stopReq); err != nil {
return err
}
return nil
})
if err != nil {
return ucloudcommon.Halt(state, err, fmt.Sprintf("Error on stopping instance %q", instance.UHostId))
}
err = retry.Config{
Tries: 100,
ShouldRetry: func(err error) bool {
return ucloudcommon.IsExpectedStateError(err)
},
RetryDelay: (&retry.Backoff{InitialBackoff: 2 * time.Second, MaxBackoff: 6 * time.Second, Multiplier: 2}).Linear,
}.Run(ctx, func(ctx context.Context) error {
instance, err := client.DescribeUHostById(instance.UHostId)
if err != nil {
return err
}
if instance.State != ucloudcommon.InstanceStateStopped {
return ucloudcommon.NewExpectedStateError("instance", instance.UHostId)
}
return nil
})
if err != nil {
return ucloudcommon.Halt(state, err, fmt.Sprintf("Error on waiting for stopping instance when stopping %q", instance.UHostId))
}
ui.Message(fmt.Sprintf("Stopping instance %q complete", instance.UHostId))
}
return multistep.ActionContinue
}
func (s *stepStopInstance) Cleanup(multistep.StateBag) {
}

View File

@ -1,13 +0,0 @@
package version
import (
"github.com/hashicorp/packer-plugin-sdk/version"
packerVersion "github.com/hashicorp/packer/version"
)
var UcloudPluginVersion *version.PluginVersion
func init() {
UcloudPluginVersion = version.InitializePluginVersion(
packerVersion.Version, packerVersion.VersionPrerelease)
}

View File

@ -28,7 +28,6 @@ import (
profitbricksbuilder "github.com/hashicorp/packer/builder/profitbricks" profitbricksbuilder "github.com/hashicorp/packer/builder/profitbricks"
tencentcloudcvmbuilder "github.com/hashicorp/packer/builder/tencentcloud/cvm" tencentcloudcvmbuilder "github.com/hashicorp/packer/builder/tencentcloud/cvm"
tritonbuilder "github.com/hashicorp/packer/builder/triton" tritonbuilder "github.com/hashicorp/packer/builder/triton"
uclouduhostbuilder "github.com/hashicorp/packer/builder/ucloud/uhost"
vagrantbuilder "github.com/hashicorp/packer/builder/vagrant" vagrantbuilder "github.com/hashicorp/packer/builder/vagrant"
yandexbuilder "github.com/hashicorp/packer/builder/yandex" yandexbuilder "github.com/hashicorp/packer/builder/yandex"
artificepostprocessor "github.com/hashicorp/packer/post-processor/artifice" artificepostprocessor "github.com/hashicorp/packer/post-processor/artifice"
@ -37,7 +36,6 @@ import (
digitaloceanimportpostprocessor "github.com/hashicorp/packer/post-processor/digitalocean-import" digitaloceanimportpostprocessor "github.com/hashicorp/packer/post-processor/digitalocean-import"
manifestpostprocessor "github.com/hashicorp/packer/post-processor/manifest" manifestpostprocessor "github.com/hashicorp/packer/post-processor/manifest"
shelllocalpostprocessor "github.com/hashicorp/packer/post-processor/shell-local" shelllocalpostprocessor "github.com/hashicorp/packer/post-processor/shell-local"
ucloudimportpostprocessor "github.com/hashicorp/packer/post-processor/ucloud-import"
vagrantpostprocessor "github.com/hashicorp/packer/post-processor/vagrant" vagrantpostprocessor "github.com/hashicorp/packer/post-processor/vagrant"
vagrantcloudpostprocessor "github.com/hashicorp/packer/post-processor/vagrant-cloud" vagrantcloudpostprocessor "github.com/hashicorp/packer/post-processor/vagrant-cloud"
yandexexportpostprocessor "github.com/hashicorp/packer/post-processor/yandex-export" yandexexportpostprocessor "github.com/hashicorp/packer/post-processor/yandex-export"
@ -76,7 +74,6 @@ var Builders = map[string]packersdk.Builder{
"profitbricks": new(profitbricksbuilder.Builder), "profitbricks": new(profitbricksbuilder.Builder),
"tencentcloud-cvm": new(tencentcloudcvmbuilder.Builder), "tencentcloud-cvm": new(tencentcloudcvmbuilder.Builder),
"triton": new(tritonbuilder.Builder), "triton": new(tritonbuilder.Builder),
"ucloud-uhost": new(uclouduhostbuilder.Builder),
"vagrant": new(vagrantbuilder.Builder), "vagrant": new(vagrantbuilder.Builder),
"yandex": new(yandexbuilder.Builder), "yandex": new(yandexbuilder.Builder),
} }
@ -103,7 +100,6 @@ var PostProcessors = map[string]packersdk.PostProcessor{
"digitalocean-import": new(digitaloceanimportpostprocessor.PostProcessor), "digitalocean-import": new(digitaloceanimportpostprocessor.PostProcessor),
"manifest": new(manifestpostprocessor.PostProcessor), "manifest": new(manifestpostprocessor.PostProcessor),
"shell-local": new(shelllocalpostprocessor.PostProcessor), "shell-local": new(shelllocalpostprocessor.PostProcessor),
"ucloud-import": new(ucloudimportpostprocessor.PostProcessor),
"vagrant": new(vagrantpostprocessor.PostProcessor), "vagrant": new(vagrantpostprocessor.PostProcessor),
"vagrant-cloud": new(vagrantcloudpostprocessor.PostProcessor), "vagrant-cloud": new(vagrantcloudpostprocessor.PostProcessor),
"yandex-export": new(yandexexportpostprocessor.PostProcessor), "yandex-export": new(yandexexportpostprocessor.PostProcessor),

View File

@ -49,6 +49,8 @@ import (
puppetserverprovisioner "github.com/hashicorp/packer-plugin-puppet/provisioner/puppet-server" puppetserverprovisioner "github.com/hashicorp/packer-plugin-puppet/provisioner/puppet-server"
qemubuilder "github.com/hashicorp/packer-plugin-qemu/builder/qemu" qemubuilder "github.com/hashicorp/packer-plugin-qemu/builder/qemu"
scalewaybuilder "github.com/hashicorp/packer-plugin-scaleway/builder/scaleway" scalewaybuilder "github.com/hashicorp/packer-plugin-scaleway/builder/scaleway"
uclouduhostbuilder "github.com/hashicorp/packer-plugin-ucloud/builder/ucloud/uhost"
ucloudimportpostprocessor "github.com/hashicorp/packer-plugin-ucloud/post-processor/ucloud-import"
virtualboxisobuilder "github.com/hashicorp/packer-plugin-virtualbox/builder/virtualbox/iso" virtualboxisobuilder "github.com/hashicorp/packer-plugin-virtualbox/builder/virtualbox/iso"
virtualboxovfbuilder "github.com/hashicorp/packer-plugin-virtualbox/builder/virtualbox/ovf" virtualboxovfbuilder "github.com/hashicorp/packer-plugin-virtualbox/builder/virtualbox/ovf"
virtualboxvmbuilder "github.com/hashicorp/packer-plugin-virtualbox/builder/virtualbox/vm" virtualboxvmbuilder "github.com/hashicorp/packer-plugin-virtualbox/builder/virtualbox/vm"
@ -93,6 +95,7 @@ var VendoredBuilders = map[string]packersdk.Builder{
"parallels-pvm": new(parallelspvmbuilder.Builder), "parallels-pvm": new(parallelspvmbuilder.Builder),
"qemu": new(qemubuilder.Builder), "qemu": new(qemubuilder.Builder),
"scaleway": new(scalewaybuilder.Builder), "scaleway": new(scalewaybuilder.Builder),
"ucloud-uhost": new(uclouduhostbuilder.Builder),
"vsphere-clone": new(vsphereclonebuilder.Builder), "vsphere-clone": new(vsphereclonebuilder.Builder),
"vsphere-iso": new(vsphereisobuilder.Builder), "vsphere-iso": new(vsphereisobuilder.Builder),
"virtualbox-iso": new(virtualboxisobuilder.Builder), "virtualbox-iso": new(virtualboxisobuilder.Builder),
@ -129,6 +132,7 @@ var VendoredPostProcessors = map[string]packersdk.PostProcessor{
"exoscale-import": new(exoscaleimportpostprocessor.PostProcessor), "exoscale-import": new(exoscaleimportpostprocessor.PostProcessor),
"googlecompute-export": new(googlecomputeexportpostprocessor.PostProcessor), "googlecompute-export": new(googlecomputeexportpostprocessor.PostProcessor),
"googlecompute-import": new(googlecomputeimportpostprocessor.PostProcessor), "googlecompute-import": new(googlecomputeimportpostprocessor.PostProcessor),
"ucloud-import": new(ucloudimportpostprocessor.PostProcessor),
"vsphere-template": new(vspheretemplatepostprocessor.PostProcessor), "vsphere-template": new(vspheretemplatepostprocessor.PostProcessor),
"vsphere": new(vspherepostprocessor.PostProcessor), "vsphere": new(vspherepostprocessor.PostProcessor),
} }

3
go.mod
View File

@ -57,6 +57,7 @@ require (
github.com/hashicorp/packer-plugin-qemu v0.0.1 github.com/hashicorp/packer-plugin-qemu v0.0.1
github.com/hashicorp/packer-plugin-scaleway v0.0.1 github.com/hashicorp/packer-plugin-scaleway v0.0.1
github.com/hashicorp/packer-plugin-sdk v0.2.0 github.com/hashicorp/packer-plugin-sdk v0.2.0
github.com/hashicorp/packer-plugin-ucloud v0.0.1
github.com/hashicorp/packer-plugin-virtualbox v0.0.1 github.com/hashicorp/packer-plugin-virtualbox v0.0.1
github.com/hashicorp/packer-plugin-vmware v0.0.1 github.com/hashicorp/packer-plugin-vmware v0.0.1
github.com/hashicorp/packer-plugin-vsphere v0.0.1 github.com/hashicorp/packer-plugin-vsphere v0.0.1
@ -79,8 +80,6 @@ require (
github.com/shirou/gopsutil v3.21.1+incompatible github.com/shirou/gopsutil v3.21.1+incompatible
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.0
github.com/tencentcloud/tencentcloud-sdk-go v3.0.222+incompatible github.com/tencentcloud/tencentcloud-sdk-go v3.0.222+incompatible
github.com/ucloud/ucloud-sdk-go v0.16.3
github.com/ufilesdk-dev/ufile-gosdk v0.0.0-20190830075812-b4dbc4ef43a6
github.com/ulikunitz/xz v0.5.6 github.com/ulikunitz/xz v0.5.6
github.com/yandex-cloud/go-genproto v0.0.0-20200915125933-33de72a328bd github.com/yandex-cloud/go-genproto v0.0.0-20200915125933-33de72a328bd
github.com/yandex-cloud/go-sdk v0.0.0-20200921111412-ef15ded2014c github.com/yandex-cloud/go-sdk v0.0.0-20200921111412-ef15ded2014c

8
go.sum
View File

@ -543,6 +543,8 @@ github.com/hashicorp/packer-plugin-sdk v0.1.3/go.mod h1:xePpgQgQYv/bamiypx3hH9uk
github.com/hashicorp/packer-plugin-sdk v0.1.4/go.mod h1:xePpgQgQYv/bamiypx3hH9ukidxDdcN8q0R0wLi8IEQ= github.com/hashicorp/packer-plugin-sdk v0.1.4/go.mod h1:xePpgQgQYv/bamiypx3hH9ukidxDdcN8q0R0wLi8IEQ=
github.com/hashicorp/packer-plugin-sdk v0.2.0 h1:A4Dq7p4y1vscY4gMzp7GQaXyDJYYhP4ukp4fapPSOY4= github.com/hashicorp/packer-plugin-sdk v0.2.0 h1:A4Dq7p4y1vscY4gMzp7GQaXyDJYYhP4ukp4fapPSOY4=
github.com/hashicorp/packer-plugin-sdk v0.2.0/go.mod h1:0DiOMEBldmB0HEhp0npFSSygC8bIvW43pphEgWkp2WU= github.com/hashicorp/packer-plugin-sdk v0.2.0/go.mod h1:0DiOMEBldmB0HEhp0npFSSygC8bIvW43pphEgWkp2WU=
github.com/hashicorp/packer-plugin-ucloud v0.0.1 h1:SC2F1BuXb6dKhY6fRdmAqTkuc17jlBIu/Ut0URJy8TU=
github.com/hashicorp/packer-plugin-ucloud v0.0.1/go.mod h1:xyMMmi/UPqFV3GT4eeX7wIqdoncNyrNuvdylnEQl1RU=
github.com/hashicorp/packer-plugin-virtualbox v0.0.1 h1:vTfy7a10RUVMdNnDLo0EQrCVbAG4rGWkaDTMC7MVBi4= github.com/hashicorp/packer-plugin-virtualbox v0.0.1 h1:vTfy7a10RUVMdNnDLo0EQrCVbAG4rGWkaDTMC7MVBi4=
github.com/hashicorp/packer-plugin-virtualbox v0.0.1/go.mod h1:OOGNMK8Y8zjsYngesZH5kCbH0Fj8PKvhqPp8w1ejM3Y= github.com/hashicorp/packer-plugin-virtualbox v0.0.1/go.mod h1:OOGNMK8Y8zjsYngesZH5kCbH0Fj8PKvhqPp8w1ejM3Y=
github.com/hashicorp/packer-plugin-vmware v0.0.1 h1:jRQAdjHwg3zeCBb52KoZsuxugrHcQhjgQln72o9eGgM= github.com/hashicorp/packer-plugin-vmware v0.0.1 h1:jRQAdjHwg3zeCBb52KoZsuxugrHcQhjgQln72o9eGgM=
@ -800,10 +802,12 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/tencentcloud/tencentcloud-sdk-go v3.0.222+incompatible h1:bs+0lcG4RELNbE8PsBC9oaPP0/qExr0DuEGnZyocm84= github.com/tencentcloud/tencentcloud-sdk-go v3.0.222+incompatible h1:bs+0lcG4RELNbE8PsBC9oaPP0/qExr0DuEGnZyocm84=
github.com/tencentcloud/tencentcloud-sdk-go v3.0.222+incompatible/go.mod h1:0PfYow01SHPMhKY31xa+EFz2RStxIqj6JFAJS+IkCi4= github.com/tencentcloud/tencentcloud-sdk-go v3.0.222+incompatible/go.mod h1:0PfYow01SHPMhKY31xa+EFz2RStxIqj6JFAJS+IkCi4=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/ucloud/ucloud-sdk-go v0.16.3 h1:DCh4A5vSxFr3EvtvJL+g0Ehy4hSlEkMpQmEvxEQhYdo=
github.com/ucloud/ucloud-sdk-go v0.16.3/go.mod h1:dyLmFHmUfgb4RZKYQP9IArlvQ2pxzFthfhwxRzOEPIw= github.com/ucloud/ucloud-sdk-go v0.16.3/go.mod h1:dyLmFHmUfgb4RZKYQP9IArlvQ2pxzFthfhwxRzOEPIw=
github.com/ufilesdk-dev/ufile-gosdk v0.0.0-20190830075812-b4dbc4ef43a6 h1:FAWNiqocJ04wC4Znj7Ax4PGWstZijayO6ifuHHvb+vI= github.com/ucloud/ucloud-sdk-go v0.20.2 h1:ZNB38C7oZ2imCHEC79mVFvVT+ASkbryriOkZ+q2E0XI=
github.com/ucloud/ucloud-sdk-go v0.20.2/go.mod h1:dyLmFHmUfgb4RZKYQP9IArlvQ2pxzFthfhwxRzOEPIw=
github.com/ufilesdk-dev/ufile-gosdk v0.0.0-20190830075812-b4dbc4ef43a6/go.mod h1:R5FMQxkQ+QK/9Vz+jfnJP4rZIktYrRcWmuAnbOSkROI= github.com/ufilesdk-dev/ufile-gosdk v0.0.0-20190830075812-b4dbc4ef43a6/go.mod h1:R5FMQxkQ+QK/9Vz+jfnJP4rZIktYrRcWmuAnbOSkROI=
github.com/ufilesdk-dev/ufile-gosdk v1.0.1 h1:TNtFN3vO8ghuxLKBwQqSELllHYP4rggvXCox8JtVV0Y=
github.com/ufilesdk-dev/ufile-gosdk v1.0.1/go.mod h1:R5FMQxkQ+QK/9Vz+jfnJP4rZIktYrRcWmuAnbOSkROI=
github.com/ugorji/go v0.0.0-20151218193438-646ae4a518c1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= github.com/ugorji/go v0.0.0-20151218193438-646ae4a518c1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
github.com/ugorji/go v1.2.4 h1:cTciPbZ/VSOzCLKclmssnfQ/jyoVyOcJ3aoJyUV1Urc= github.com/ugorji/go v1.2.4 h1:cTciPbZ/VSOzCLKclmssnfQ/jyoVyOcJ3aoJyUV1Urc=
github.com/ugorji/go v1.2.4/go.mod h1:EuaSCk8iZMdIspsu6HXH7X2UGKw1ezO4wCfGszGmmo4= github.com/ugorji/go v1.2.4/go.mod h1:EuaSCk8iZMdIspsu6HXH7X2UGKw1ezO4wCfGszGmmo4=

View File

@ -1,362 +0,0 @@
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
//go:generate packer-sdc struct-markdown
package ucloudimport
import (
"context"
"fmt"
"log"
"net/url"
"strings"
"time"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer-plugin-sdk/common"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/retry"
"github.com/hashicorp/packer-plugin-sdk/template/config"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
ucloudcommon "github.com/hashicorp/packer/builder/ucloud/common"
"github.com/ucloud/ucloud-sdk-go/services/ufile"
"github.com/ucloud/ucloud-sdk-go/services/uhost"
"github.com/ucloud/ucloud-sdk-go/ucloud"
ufsdk "github.com/ufilesdk-dev/ufile-gosdk"
)
const (
BuilderId = "packer.post-processor.ucloud-import"
ImageFileFormatRAW = "raw"
ImageFileFormatVHD = "vhd"
ImageFileFormatVMDK = "vmdk"
ImageFileFormatQCOW2 = "qcow2"
)
var imageFormatMap = ucloudcommon.NewStringConverter(map[string]string{
"raw": "RAW",
"vhd": "VHD",
"vmdk": "VMDK",
})
// Configuration of this post processor
type Config struct {
common.PackerConfig `mapstructure:",squash"`
ucloudcommon.AccessConfig `mapstructure:",squash"`
// The name of the UFile bucket where the RAW, VHD, VMDK, or qcow2 file will be copied to for import.
// This bucket must exist when the post-processor is run.
UFileBucket string `mapstructure:"ufile_bucket_name" required:"true"`
// The name of the object key in
// `ufile_bucket_name` where the RAW, VHD, VMDK, or qcow2 file will be copied
// to import. This is a [template engine](/docs/templates/legacy_json_templates/engine).
// Therefore, you may use user variables and template functions in this field.
UFileKey string `mapstructure:"ufile_key_name" required:"false"`
// Whether we should skip removing the RAW, VHD, VMDK, or qcow2 file uploaded to
// UFile after the import process has completed. Possible values are: `true` to
// leave it in the UFile bucket, `false` to remove it. (Default: `false`).
SkipClean bool `mapstructure:"skip_clean" required:"false"`
// The name of the user-defined image, which contains 1-63 characters and only
// supports Chinese, English, numbers, '-\_,.:[]'.
ImageName string `mapstructure:"image_name" required:"true"`
// The description of the image.
ImageDescription string `mapstructure:"image_description" required:"false"`
// Type of the OS. Possible values are: `CentOS`, `Ubuntu`, `Windows`, `RedHat`, `Debian`, `Other`.
// You may refer to [ucloud_api_docs](https://docs.ucloud.cn/api/uhost-api/import_custom_image) for detail.
OSType string `mapstructure:"image_os_type" required:"true"`
// The name of OS. Such as: `CentOS 7.2 64位`, set `Other` When `image_os_type` is `Other`.
// You may refer to [ucloud_api_docs](https://docs.ucloud.cn/api/uhost-api/import_custom_image) for detail.
OSName string `mapstructure:"image_os_name" required:"true"`
// The format of the import image , Possible values are: `raw`, `vhd`, `vmdk`, or `qcow2`.
Format string `mapstructure:"format" required:"true"`
// Timeout of importing image. The default timeout is 3600 seconds if this option is not set or is set.
WaitImageReadyTimeout int `mapstructure:"wait_image_ready_timeout" required:"false"`
ctx interpolate.Context
}
type PostProcessor struct {
config Config
}
func (p *PostProcessor) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() }
func (p *PostProcessor) Configure(raws ...interface{}) error {
err := config.Decode(&p.config, &config.DecodeOpts{
PluginType: BuilderId,
Interpolate: true,
InterpolateContext: &p.config.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"ufile_key_name",
},
},
}, raws...)
if err != nil {
return err
}
// Set defaults
if p.config.UFileKey == "" {
p.config.UFileKey = "packer-import-{{timestamp}}." + p.config.Format
}
if p.config.WaitImageReadyTimeout <= 0 {
p.config.WaitImageReadyTimeout = ucloudcommon.DefaultCreateImageTimeout
}
errs := new(packersdk.MultiError)
// Check and render ufile_key_name
if err = interpolate.Validate(p.config.UFileKey, &p.config.ctx); err != nil {
errs = packersdk.MultiErrorAppend(
errs, fmt.Errorf("Error parsing ufile_key_name template: %s", err))
}
// Check we have ucloud access variables defined somewhere
errs = packersdk.MultiErrorAppend(errs, p.config.AccessConfig.Prepare(&p.config.ctx)...)
// define all our required parameters
templates := map[string]*string{
"ufile_bucket_name": &p.config.UFileBucket,
"image_name": &p.config.ImageName,
"image_os_type": &p.config.OSType,
"image_os_name": &p.config.OSName,
"format": &p.config.Format,
}
// Check out required params are defined
for key, ptr := range templates {
if *ptr == "" {
errs = packersdk.MultiErrorAppend(
errs, fmt.Errorf("%s must be set", key))
}
}
imageName := p.config.ImageName
if !ucloudcommon.ImageNamePattern.MatchString(imageName) {
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("expected %q to be 1-63 characters and only support chinese, english, numbers, '-_,.:[]', got %q", "image_name", imageName))
}
switch p.config.Format {
case ImageFileFormatVHD, ImageFileFormatRAW, ImageFileFormatVMDK, ImageFileFormatQCOW2:
default:
errs = packersdk.MultiErrorAppend(
errs, fmt.Errorf("expected %q only be one of 'raw', 'vhd', 'vmdk', or 'qcow2', got %q", "format", p.config.Format))
}
// Anything which flagged return back up the stack
if len(errs.Errors) > 0 {
return errs
}
packersdk.LogSecretFilter.Set(p.config.PublicKey, p.config.PrivateKey)
log.Println(p.config)
return nil
}
func (p *PostProcessor) PostProcess(ctx context.Context, ui packersdk.Ui, artifact packersdk.Artifact) (packersdk.Artifact, bool, bool, error) {
var err error
generatedData := artifact.State("generated_data")
if generatedData == nil {
// Make sure it's not a nil map so we can assign to it later.
generatedData = make(map[string]interface{})
}
p.config.ctx.Data = generatedData
client, err := p.config.Client()
if err != nil {
return nil, false, false, fmt.Errorf("Failed to connect ucloud client %s", err)
}
uhostconn := client.UHostConn
ufileconn := client.UFileConn
// Render this key since we didn't in the configure phase
p.config.UFileKey, err = interpolate.Render(p.config.UFileKey, &p.config.ctx)
if err != nil {
return nil, false, false, fmt.Errorf("Error rendering ufile_key_name template: %s", err)
}
ui.Message(fmt.Sprintf("Rendered ufile_key_name as %s", p.config.UFileKey))
ui.Message("Looking for image in artifact")
// Locate the files output from the builder
var source string
for _, path := range artifact.Files() {
if strings.HasSuffix(path, "."+p.config.Format) {
source = path
break
}
}
// Hope we found something useful
if source == "" {
return nil, false, false, fmt.Errorf("No %s image file found in artifact from builder", p.config.Format)
}
keyName := p.config.UFileKey
bucketName := p.config.UFileBucket
// query bucket
domain, err := queryBucket(ufileconn, bucketName)
if err != nil {
return nil, false, false, fmt.Errorf("Failed to query bucket, %s", err)
}
var bucketHost string
if p.config.BaseUrl != "" {
// skip error because it has been validated by prepare
urlObj, _ := url.Parse(p.config.BaseUrl)
bucketHost = urlObj.Host
} else {
bucketHost = "api.ucloud.cn"
}
fileHost := strings.SplitN(domain, ".", 2)[1]
config := &ufsdk.Config{
PublicKey: p.config.PublicKey,
PrivateKey: p.config.PrivateKey,
BucketName: bucketName,
FileHost: fileHost,
BucketHost: bucketHost,
}
ui.Say(fmt.Sprintf("Waiting for uploading image file %s to UFile: %s/%s...", source, bucketName, keyName))
// upload file to bucket
ufileUrl, err := uploadFile(ufileconn, config, keyName, source)
if err != nil {
return nil, false, false, fmt.Errorf("Failed to Upload image file, %s", err)
}
ui.Say(fmt.Sprintf("Image file %s has been uploaded to UFile: %s/%s", source, bucketName, keyName))
importImageRequest := p.buildImportImageRequest(uhostconn, ufileUrl)
importImageResponse, err := uhostconn.ImportCustomImage(importImageRequest)
if err != nil {
return nil, false, false, fmt.Errorf("Failed to import image from UFile: %s/%s, %s", bucketName, keyName, err)
}
ui.Say(fmt.Sprintf("Waiting for importing image from UFile: %s/%s ...", bucketName, keyName))
imageId := importImageResponse.ImageId
err = retry.Config{
StartTimeout: time.Duration(p.config.WaitImageReadyTimeout) * time.Second,
ShouldRetry: func(err error) bool {
return ucloudcommon.IsExpectedStateError(err)
},
RetryDelay: (&retry.Backoff{InitialBackoff: 2 * time.Second, MaxBackoff: 12 * time.Second, Multiplier: 2}).Linear,
}.Run(ctx, func(ctx context.Context) error {
image, err := client.DescribeImageById(imageId)
if err != nil {
return err
}
if image.State == ucloudcommon.ImageStateUnavailable {
return fmt.Errorf("Unavailable importing image %q", imageId)
}
if image.State != ucloudcommon.ImageStateAvailable {
return ucloudcommon.NewExpectedStateError("image", imageId)
}
return nil
})
if err != nil {
return nil, false, false, fmt.Errorf("Error on waiting for importing image %q from UFile: %s/%s, %s",
imageId, bucketName, keyName, err)
}
// Add the reported UCloud image ID to the artifact list
ui.Say(fmt.Sprintf("Importing created ucloud image %q in region %q Complete.", imageId, p.config.Region))
images := []ucloudcommon.ImageInfo{
{
ImageId: imageId,
ProjectId: p.config.ProjectId,
Region: p.config.Region,
},
}
artifact = &ucloudcommon.Artifact{
UCloudImages: ucloudcommon.NewImageInfoSet(images),
BuilderIdValue: BuilderId,
Client: client,
}
if !p.config.SkipClean {
ui.Message(fmt.Sprintf("Deleting import source UFile: %s/%s", p.config.UFileBucket, p.config.UFileKey))
if err = deleteFile(config, p.config.UFileKey); err != nil {
return nil, false, false, fmt.Errorf("Failed to delete UFile: %s/%s, %s", p.config.UFileBucket, p.config.UFileKey, err)
}
}
return artifact, false, false, nil
}
func (p *PostProcessor) buildImportImageRequest(conn *uhost.UHostClient, privateUrl string) *uhost.ImportCustomImageRequest {
req := conn.NewImportCustomImageRequest()
req.ImageName = ucloud.String(p.config.ImageName)
req.ImageDescription = ucloud.String(p.config.ImageDescription)
req.UFileUrl = ucloud.String(privateUrl)
req.OsType = ucloud.String(p.config.OSType)
req.OsName = ucloud.String(p.config.OSName)
req.Format = ucloud.String(imageFormatMap.Convert(p.config.Format))
req.Auth = ucloud.Bool(true)
return req
}
func queryBucket(conn *ufile.UFileClient, bucketName string) (string, error) {
req := conn.NewDescribeBucketRequest()
req.BucketName = ucloud.String(bucketName)
resp, err := conn.DescribeBucket(req)
if err != nil {
return "", fmt.Errorf("error on reading bucket %q when create bucket, %s", bucketName, err)
}
if len(resp.DataSet) < 1 {
return "", fmt.Errorf("the bucket %s is not exit", bucketName)
}
return resp.DataSet[0].Domain.Src[0], nil
}
func uploadFile(conn *ufile.UFileClient, config *ufsdk.Config, keyName, source string) (string, error) {
reqFile, err := ufsdk.NewFileRequest(config, nil)
if err != nil {
return "", fmt.Errorf("error on building upload file request, %s", err)
}
// upload file in segments
err = reqFile.AsyncMPut(source, keyName, "")
if err != nil {
return "", fmt.Errorf("error on upload file, %s, details: %s", err, reqFile.DumpResponse(true))
}
reqBucket := conn.NewDescribeBucketRequest()
reqBucket.BucketName = ucloud.String(config.BucketName)
resp, err := conn.DescribeBucket(reqBucket)
if err != nil {
return "", fmt.Errorf("error on reading bucket list when upload file, %s", err)
}
if resp.DataSet[0].Type == "private" {
return reqFile.GetPrivateURL(keyName, time.Duration(24*60*60)*time.Second), nil
}
return reqFile.GetPublicURL(keyName), nil
}
func deleteFile(config *ufsdk.Config, keyName string) error {
req, err := ufsdk.NewFileRequest(config, nil)
if err != nil {
return fmt.Errorf("error on new deleting file, %s", err)
}
req.DeleteFile(keyName)
if err != nil {
return fmt.Errorf("error on deleting file, %s", err)
}
return nil
}

View File

@ -1,77 +0,0 @@
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
package ucloudimport
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/zclconf/go-cty/cty"
)
// FlatConfig is an auto-generated flat version of Config.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatConfig struct {
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"`
PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"`
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
PublicKey *string `mapstructure:"public_key" required:"true" cty:"public_key" hcl:"public_key"`
PrivateKey *string `mapstructure:"private_key" required:"true" cty:"private_key" hcl:"private_key"`
Region *string `mapstructure:"region" required:"true" cty:"region" hcl:"region"`
ProjectId *string `mapstructure:"project_id" required:"true" cty:"project_id" hcl:"project_id"`
BaseUrl *string `mapstructure:"base_url" required:"false" cty:"base_url" hcl:"base_url"`
Profile *string `mapstructure:"profile" required:"false" cty:"profile" hcl:"profile"`
SharedCredentialsFile *string `mapstructure:"shared_credentials_file" required:"false" cty:"shared_credentials_file" hcl:"shared_credentials_file"`
UFileBucket *string `mapstructure:"ufile_bucket_name" required:"true" cty:"ufile_bucket_name" hcl:"ufile_bucket_name"`
UFileKey *string `mapstructure:"ufile_key_name" required:"false" cty:"ufile_key_name" hcl:"ufile_key_name"`
SkipClean *bool `mapstructure:"skip_clean" required:"false" cty:"skip_clean" hcl:"skip_clean"`
ImageName *string `mapstructure:"image_name" required:"true" cty:"image_name" hcl:"image_name"`
ImageDescription *string `mapstructure:"image_description" required:"false" cty:"image_description" hcl:"image_description"`
OSType *string `mapstructure:"image_os_type" required:"true" cty:"image_os_type" hcl:"image_os_type"`
OSName *string `mapstructure:"image_os_name" required:"true" cty:"image_os_name" hcl:"image_os_name"`
Format *string `mapstructure:"format" required:"true" cty:"format" hcl:"format"`
WaitImageReadyTimeout *int `mapstructure:"wait_image_ready_timeout" required:"false" cty:"wait_image_ready_timeout" hcl:"wait_image_ready_timeout"`
}
// FlatMapstructure returns a new FlatConfig.
// FlatConfig is an auto-generated flat version of Config.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatConfig)
}
// HCL2Spec returns the hcl spec of a Config.
// This spec is used by HCL to read the fields of Config.
// The decoded values from this spec will then be applied to a FlatConfig.
func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
"packer_core_version": &hcldec.AttrSpec{Name: "packer_core_version", Type: cty.String, Required: false},
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"public_key": &hcldec.AttrSpec{Name: "public_key", Type: cty.String, Required: false},
"private_key": &hcldec.AttrSpec{Name: "private_key", Type: cty.String, Required: false},
"region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false},
"project_id": &hcldec.AttrSpec{Name: "project_id", Type: cty.String, Required: false},
"base_url": &hcldec.AttrSpec{Name: "base_url", Type: cty.String, Required: false},
"profile": &hcldec.AttrSpec{Name: "profile", Type: cty.String, Required: false},
"shared_credentials_file": &hcldec.AttrSpec{Name: "shared_credentials_file", Type: cty.String, Required: false},
"ufile_bucket_name": &hcldec.AttrSpec{Name: "ufile_bucket_name", Type: cty.String, Required: false},
"ufile_key_name": &hcldec.AttrSpec{Name: "ufile_key_name", Type: cty.String, Required: false},
"skip_clean": &hcldec.AttrSpec{Name: "skip_clean", Type: cty.Bool, Required: false},
"image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false},
"image_description": &hcldec.AttrSpec{Name: "image_description", Type: cty.String, Required: false},
"image_os_type": &hcldec.AttrSpec{Name: "image_os_type", Type: cty.String, Required: false},
"image_os_name": &hcldec.AttrSpec{Name: "image_os_name", Type: cty.String, Required: false},
"format": &hcldec.AttrSpec{Name: "format", Type: cty.String, Required: false},
"wait_image_ready_timeout": &hcldec.AttrSpec{Name: "wait_image_ready_timeout", Type: cty.Number, Required: false},
}
return s
}

View File

@ -1,13 +0,0 @@
package version
import (
"github.com/hashicorp/packer-plugin-sdk/version"
packerVersion "github.com/hashicorp/packer/version"
)
var UCloudImportPluginVersion *version.PluginVersion
func init() {
UCloudImportPluginVersion = version.InitializePluginVersion(
packerVersion.Version, packerVersion.VersionPrerelease)
}

View File

@ -1,214 +0,0 @@
---
description: |
The `ucloud-uhost` Packer builder plugin provides the capability to build
customized images based on an existing base image for use in UHost Instance.
page_title: UCloud Image Builder
---
# UCloud Image Builder
Type: `ucloud-uhost`
Artifact BuilderId: `ucloud.uhost`
The `ucloud-uhost` Packer builder plugin provides the capability to build
customized images based on an existing base image for use in UHost Instance.
This builder builds an UCloud image by launching an UHost instance from a source image,
provisioning that running machine, and then creating an image from that machine.
## Configuration Reference
The following configuration options are available for building UCloud images. They are
segmented below into two categories: required and optional parameters.
In addition to the options listed here, a
[communicator](/docs/templates/legacy_json_templates/communicator) can be configured for this
builder.
~> **Note:** The builder doesn't support Windows images for now and only supports CentOS and Ubuntu images via SSH authentication with `ssh_username` (Required) and `ssh_password` (Optional). The `ssh_username` must be `root` for CentOS images and `ubuntu` for Ubuntu images. The `ssh_password` may contain 8-30 characters, and must consist of at least 2 items out of the capital letters, lower case letters, numbers and special characters. The special characters include `()~!@#\$%^&\*-+=\_|{}\[]:;'<>,.?/`.
### Required:
@include 'builder/ucloud/common/AccessConfig-required.mdx'
@include 'builder/ucloud/common/RunConfig-required.mdx'
@include 'builder/ucloud/common/ImageConfig-required.mdx'
### Optional:
@include 'builder/ucloud/common/AccessConfig-not-required.mdx'
@include 'builder/ucloud/common/RunConfig-not-required.mdx'
@include 'builder/ucloud/common/ImageConfig-not-required.mdx'
## Examples
Here is a basic example for build UCloud CentOS image:
<Tabs>
<Tab heading="JSON">
```json
{
"variables": {
"ucloud_public_key": "{{env `UCLOUD_PUBLIC_KEY`}}",
"ucloud_private_key": "{{env `UCLOUD_PRIVATE_KEY`}}",
"ucloud_project_id": "{{env `UCLOUD_PROJECT_ID`}}"
},
"builders": [
{
"type": "ucloud-uhost",
"public_key": "{{user `ucloud_public_key`}}",
"private_key": "{{user `ucloud_private_key`}}",
"project_id": "{{user `ucloud_project_id`}}",
"region": "cn-bj2",
"availability_zone": "cn-bj2-02",
"instance_type": "n-basic-2",
"source_image_id": "uimage-f1chxn",
"ssh_username": "root",
"image_name": "packer-test{{timestamp}}"
}
]
}
```
</Tab>
<Tab heading="HCL2">
```hcl
// .pkr.hcl file
variable "ucloud_public_key" {
type = string
default = "xxx"
}
variable "ucloud_private_key" {
type = string
default = "xxx"
}
variable "ucloud_project_id" {
type = string
default = "xxx"
}
source "ucloud-uhost" "basic-example" {
public_key = var.ucloud_public_key
private_key = var.ucloud_private_key
project_id = var.ucloud_project_id
region = "cn-bj2"
availability_zone = "cn-bj2-02"
instance_type = "n-basic-2"
source_image_id = "uimage-f1chxn"
ssh_username = "root"
}
build {
source "sources.ucloud-uhost.basic-example" {
image_name = "packer-test-${timestamp()}"
}
}
```
</Tab>
</Tabs>
Here is a example for build UCloud Ubuntu image:
<Tabs>
<Tab heading="JSON">
```json
{
"variables": {
"ucloud_public_key": "{{env `UCLOUD_PUBLIC_KEY`}}",
"ucloud_private_key": "{{env `UCLOUD_PRIVATE_KEY`}}",
"ucloud_project_id": "{{env `UCLOUD_PROJECT_ID`}}",
"password": "ucloud_2020"
},
"builders": [
{
"type": "ucloud-uhost",
"public_key": "{{user `ucloud_public_key`}}",
"private_key": "{{user `ucloud_private_key`}}",
"project_id": "{{user `ucloud_project_id`}}",
"region": "cn-bj2",
"availability_zone": "cn-bj2-02",
"instance_type": "n-basic-2",
"source_image_id": "uimage-irofn4",
"ssh_password": "{{user `password`}}",
"ssh_username": "ubuntu",
"image_name": "packer-test-ubuntu{{timestamp}}"
}
],
"provisioners": [
{
"type": "shell",
"execute_command": "echo '{{user `password`}}' | sudo -S '{{.Path}}'",
"inline": ["sleep 30", "sudo apt update", "sudo apt install nginx -y"]
}
]
}
```
</Tab>
<Tab heading="HCL2">
```hcl
// .pkr.hcl file
variable "ucloud_public_key" {
type = string
default = "xxx"
}
variable "ucloud_private_key" {
type = string
default = "xxx"
}
variable "ucloud_project_id" {
type = string
default = "xxx"
}
variable "password" {
type = string
default = "ucloud_2020"
}
source "ucloud-uhost" "basic-example" {
public_key = var.ucloud_public_key
private_key = var.ucloud_private_key
project_id = var.ucloud_project_id
region = "cn-bj2"
availability_zone = "cn-bj2-02"
instance_type = "n-basic-2"
ssh_password = var.password
source_image_id = "uimage-irofn4"
ssh_username = "ubuntu"
}
build {
source "sources.ucloud-uhost.basic-example" {
image_name = "packer-test-ubuntu-${timestamp()}"
}
provisioner "shell" {
execute_command = "echo '${var.password}' | sudo -S '{{.Path}}'"
inline = ["sleep 30", "sudo apt update", "sudo apt install nginx -y"]
}
}
```
</Tab>
</Tabs>
-> **Note:** Packer can also read the public key and private key from
environmental variables. See the configuration reference in the section above
for more information on what environmental variables Packer will look for.
~> **Note:** Source image may be deprecated after a while, you can use the tools like [UCloud CLI](https://docs.ucloud.cn/cli/intro) to run `ucloud image list` to find one that exists.

View File

@ -1,58 +0,0 @@
---
description: >
The Packer UCloud Import post-processor takes the RAW, VHD, VMDK, or qcow2
artifact from various builders and imports it to UCloud customized image list
for UHost Instance.
page_title: UCloud Import Post-Processors
---
# UCloud Import Post-Processor
Type: `ucloud-import`
Artifact BuilderId: `packer.post-processor.ucloud-import`
The Packer UCloud Import post-processor takes the RAW, VHD, VMDK, or qcow2 artifact from various builders and imports it to UCloud customized image list for UHost Instance.
~> **Note** Some regions don't support image import. You may refer to [ucloud console](https://console.ucloud.cn/uhost/uimage) for more detail. If you want to import to unsupported regions, please import the image in `cn-bj2` first, and then copy the image to the target region.
## How Does it Work?
The import process operates by making a temporary copy of the RAW, VHD, VMDK, or qcow2 to an UFile bucket, and calling an import task in UHost on the RAW, VHD, VMDK, or qcow2 file. Once completed, an UCloud UHost Image is returned. The temporary RAW, VHD, VMDK, or qcow2 copy in UFile can be discarded after the import is complete.
## Configuration
There are some configuration options available for the post-processor. There
are two categories: required and optional parameters.
### Required:
@include 'builder/ucloud/common/AccessConfig-required.mdx'
@include 'post-processor/ucloud-import/Config-required.mdx'
### Optional:
@include 'builder/ucloud/common/AccessConfig-not-required.mdx'
@include 'post-processor/ucloud-import/Config-not-required.mdx'
## Basic Example
Here is a basic example. This assumes that the builder has produced a RAW artifact for us to work with. This will take the RAW image generated by a builder and upload it to UFile. Once uploaded, the import process will start, creating an UCloud UHost image to the region `cn-bj2`.
```json
"post-processors":[
{
"type":"ucloud-import",
"public_key": "{{user `ucloud_public_key`}}",
"private_key": "{{user `ucloud_private_key`}}",
"project_id": "{{user `ucloud_project_id`}}",
"region":"cn-bj2",
"ufile_bucket_name": "packer-import",
"image_name": "packer_import",
"image_os_type": "CentOS",
"image_os_name": "CentOS 6.10 64位",
"format": "raw"
}
]
```

View File

@ -1,12 +0,0 @@
<!-- Code generated from the comments of the AccessConfig struct in builder/ucloud/common/access_config.go; DO NOT EDIT MANUALLY -->
- `base_url` (string) - This is the base url. (Default: `https://api.ucloud.cn`).
- `profile` (string) - This is the UCloud profile name as set in the shared credentials file, it can
also be sourced from the `UCLOUD_PROFILE` environment variables.
- `shared_credentials_file` (string) - This is the path to the shared credentials file, it can also be sourced from
the `UCLOUD_SHARED_CREDENTIAL_FILE` environment variables. If this is not set
and a profile is specified, `~/.ucloud/credential.json` will be used.
<!-- End of code generated from the comments of the AccessConfig struct in builder/ucloud/common/access_config.go; -->

View File

@ -1,15 +0,0 @@
<!-- Code generated from the comments of the AccessConfig struct in builder/ucloud/common/access_config.go; DO NOT EDIT MANUALLY -->
- `public_key` (string) - This is the UCloud public key. It must be provided unless `profile` is set,
but it can also be sourced from the `UCLOUD_PUBLIC_KEY` environment variable.
- `private_key` (string) - This is the UCloud private key. It must be provided unless `profile` is set,
but it can also be sourced from the `UCLOUD_PRIVATE_KEY` environment variable.
- `region` (string) - This is the UCloud region. It must be provided, but it can also be sourced from
the `UCLOUD_REGION` environment variables.
- `project_id` (string) - This is the UCloud project id. It must be provided, but it can also be sourced
from the `UCLOUD_PROJECT_ID` environment variables.
<!-- End of code generated from the comments of the AccessConfig struct in builder/ucloud/common/access_config.go; -->

View File

@ -1,31 +0,0 @@
<!-- Code generated from the comments of the ImageConfig struct in builder/ucloud/common/image_config.go; DO NOT EDIT MANUALLY -->
- `image_description` (string) - The description of the image.
- `image_copy_to_mappings` ([]ImageDestination) - The array of mappings regarding the copied images to the destination regions and projects.
- `project_id` (string) - The destination project id, where copying image in.
- `region` (string) - The destination region, where copying image in.
- `name` (string) - The copied image name. If not defined, builder will use `image_name` as default name.
- `description` (string) - The copied image description.
```json
{
"image_copy_to_mappings": [
{
"project_id": "{{user `ucloud_project_id`}}",
"region": "cn-sh2",
"description": "test",
"name": "packer-test-basic-sh"
}
]
}
```
- `wait_image_ready_timeout` (int) - Timeout of creating image or copying image. The default timeout is 3600 seconds if this option
is not set or is set to 0.
<!-- End of code generated from the comments of the ImageConfig struct in builder/ucloud/common/image_config.go; -->

View File

@ -1,6 +0,0 @@
<!-- Code generated from the comments of the ImageConfig struct in builder/ucloud/common/image_config.go; DO NOT EDIT MANUALLY -->
- `image_name` (string) - The name of the user-defined image, which contains 1-63 characters and only
support Chinese, English, numbers, '-\_,.:[]'.
<!-- End of code generated from the comments of the ImageConfig struct in builder/ucloud/common/image_config.go; -->

View File

@ -1,11 +0,0 @@
<!-- Code generated from the comments of the ImageDestination struct in builder/ucloud/common/image_config.go; DO NOT EDIT MANUALLY -->
- `project_id` (string) - The destination project id, where copying image in.
- `region` (string) - The destination region, where copying image in.
- `name` (string) - The copied image name. If not defined, builder will use `image_name` as default name.
- `description` (string) - The copied image description.
<!-- End of code generated from the comments of the ImageDestination struct in builder/ucloud/common/image_config.go; -->

View File

@ -1,56 +0,0 @@
<!-- Code generated from the comments of the RunConfig struct in builder/ucloud/common/run_config.go; DO NOT EDIT MANUALLY -->
- `instance_name` (string) - The name of instance, which contains 1-63 characters and only support Chinese,
English, numbers, '-', '\_', '.'.
- `boot_disk_type` (string) - The type of boot disk associated to UHost instance.
Possible values are: `cloud_ssd` and `cloud_rssd` for cloud boot disk, `local_normal` and `local_ssd`
for local boot disk. (Default: `cloud_ssd`). The `cloud_ssd` and `local_ssd` are not fully supported
by all regions as boot disk type, please proceed to UCloud console for more details.
~> **Note:** It takes around 10 mins for boot disk initialization when `boot_disk_type` is `local_normal` or `local_ssd`.
- `vpc_id` (string) - The ID of VPC linked to the UHost instance. If not defined `vpc_id`, the instance will use the default VPC in the current region.
- `subnet_id` (string) - The ID of subnet under the VPC. If `vpc_id` is defined, the `subnet_id` is mandatory required.
If `vpc_id` and `subnet_id` are not defined, the instance will use the default subnet in the current region.
- `security_group_id` (string) - The ID of the fire wall associated to UHost instance. If `security_group_id` is not defined,
the instance will use the non-recommended web fire wall, and open port include 22, 3389 by default.
It is supported by ICMP fire wall protocols.
You may refer to [security group_id](https://docs.ucloud.cn/network/firewall/firewall).
- `eip_bandwidth` (int) - Maximum bandwidth to the elastic public network, measured in Mbps (Mega bit per second). (Default: `10`).
- `eip_charge_mode` (string) - Elastic IP charge mode. Possible values are: `traffic` as pay by traffic, `bandwidth` as pay by bandwidth,
`post_accurate_bandwidth` as post pay mode. (Default: `traffic`).
Note currently default `traffic` eip charge mode not not fully support by all `availability_zone`
in the `region`, please proceed to [UCloud console](https://console.ucloud.cn/unet/eip/create) for more details.
You may refer to [eip introduction](https://docs.ucloud.cn/unet/eip/introduction).
- `user_data` (string) - User data to apply when launching the instance.
Note that you need to be careful about escaping characters due to the templates
being JSON. It is often more convenient to use user_data_file, instead.
Packer will not automatically wait for a user script to finish before
shutting down the instance this must be handled in a provisioner.
You may refer to [user_data_document](https://docs.ucloud.cn/uhost/guide/metadata/userdata)
- `user_data_file` (string) - Path to a file that will be used for the user data when launching the instance.
- `min_cpu_platform` (string) - Specifies a minimum CPU platform for the the VM instance. (Default: `Intel/Auto`).
You may refer to [min_cpu_platform](https://docs.ucloud.cn/uhost/introduction/uhost/type_new)
- The Intel CPU platform:
- `Intel/Auto` as the Intel CPU platform version will be selected randomly by system;
- `Intel/IvyBridge` as Intel V2, the version of Intel CPU platform selected by system will be `Intel/IvyBridge` and above;
- `Intel/Haswell` as Intel V3, the version of Intel CPU platform selected by system will be `Intel/Haswell` and above;
- `Intel/Broadwell` as Intel V4, the version of Intel CPU platform selected by system will be `Intel/Broadwell` and above;
- `Intel/Skylake` as Intel V5, the version of Intel CPU platform selected by system will be `Intel/Skylake` and above;
- `Intel/Cascadelake` as Intel V6, the version of Intel CPU platform selected by system will be `Intel/Cascadelake`;
- The AMD CPU platform:
- `Amd/Auto` as the Amd CPU platform version will be selected randomly by system;
- `Amd/Epyc2` as the version of Amd CPU platform selected by system will be `Amd/Epyc2` and above;
- `use_ssh_private_ip` (bool) - If this value is true, packer will connect to the created UHost instance via a private ip
instead of allocating an EIP (elastic public ip).(Default: `false`).
<!-- End of code generated from the comments of the RunConfig struct in builder/ucloud/common/run_config.go; -->

View File

@ -1,11 +0,0 @@
<!-- Code generated from the comments of the RunConfig struct in builder/ucloud/common/run_config.go; DO NOT EDIT MANUALLY -->
- `availability_zone` (string) - This is the UCloud availability zone where UHost instance is located. such as: `cn-bj2-02`.
You may refer to [list of availability_zone](https://docs.ucloud.cn/api/summary/regionlist)
- `source_image_id` (string) - This is the ID of base image which you want to create your customized images with.
- `instance_type` (string) - The type of UHost instance.
You may refer to [list of instance type](https://docs.ucloud.cn/compute/terraform/specification/instance)
<!-- End of code generated from the comments of the RunConfig struct in builder/ucloud/common/run_config.go; -->

View File

@ -1,68 +0,0 @@
There are a set of special keys available. If these are in your boot
command, they will be replaced by the proper key:
- `<bs>` - Backspace
- `<del>` - Delete
- `<enter> <return>` - Simulates an actual "enter" or "return" keypress.
- `<esc>` - Simulates pressing the escape key.
- `<tab>` - Simulates pressing the tab key.
- `<f1> - <f12>` - Simulates pressing a function key.
- `<up> <down> <left> <right>` - Simulates pressing an arrow key.
- `<spacebar>` - Simulates pressing the spacebar.
- `<insert>` - Simulates pressing the insert key.
- `<home> <end>` - Simulates pressing the home and end keys.
- `<pageUp> <pageDown>` - Simulates pressing the page up and page down keys.
- `<menu>` - Simulates pressing the Menu key.
- `<leftAlt> <rightAlt>` - Simulates pressing the alt key.
- `<leftCtrl> <rightCtrl>` - Simulates pressing the ctrl key.
- `<leftShift> <rightShift>` - Simulates pressing the shift key.
- `<leftSuper> <rightSuper>` - Simulates pressing the ⌘ or Windows key.
- `<wait> <wait5> <wait10>` - Adds a 1, 5 or 10 second pause before
sending any additional keys. This is useful if you have to generally wait
for the UI to update before typing more.
- `<waitXX>` - Add an arbitrary pause before sending any additional keys. The
format of `XX` is a sequence of positive decimal numbers, each with
optional fraction and a unit suffix, such as `300ms`, `1.5h` or `2h45m`.
Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example
`<wait10m>` or `<wait1m20s>`
### On/Off variants
Any printable keyboard character, and of these "special" expressions, with the
exception of the `<wait>` types, can also be toggled on or off. For example, to
simulate ctrl+c, use `<leftCtrlOn>c<leftCtrlOff>`. Be sure to release them,
otherwise they will be held down until the machine reboots.
To hold the `c` key down, you would use `<cOn>`. Likewise, `<cOff>` to release.
### Templates inside boot command
In addition to the special keys, each command to type is treated as a
[template engine](/docs/templates/legacy_json_templates/engine). The
available variables are:
- `HTTPIP` and `HTTPPort` - The IP and port, respectively of an HTTP server
that is started serving the directory specified by the `http_directory`
configuration parameter or the content specified in the `http_content` map. If
`http_directory` or `http_content` isn't specified, these will be blank!
- `Name` - The name of the VM.
For more examples of various boot commands, see the sample projects from our
[community templates page](/community-tools#templates).

View File

@ -1,16 +0,0 @@
<!-- Code generated from the comments of the Config struct in post-processor/ucloud-import/post-processor.go; DO NOT EDIT MANUALLY -->
- `ufile_key_name` (string) - The name of the object key in
`ufile_bucket_name` where the RAW, VHD, VMDK, or qcow2 file will be copied
to import. This is a [template engine](/docs/templates/legacy_json_templates/engine).
Therefore, you may use user variables and template functions in this field.
- `skip_clean` (bool) - Whether we should skip removing the RAW, VHD, VMDK, or qcow2 file uploaded to
UFile after the import process has completed. Possible values are: `true` to
leave it in the UFile bucket, `false` to remove it. (Default: `false`).
- `image_description` (string) - The description of the image.
- `wait_image_ready_timeout` (int) - Timeout of importing image. The default timeout is 3600 seconds if this option is not set or is set.
<!-- End of code generated from the comments of the Config struct in post-processor/ucloud-import/post-processor.go; -->

View File

@ -1,17 +0,0 @@
<!-- Code generated from the comments of the Config struct in post-processor/ucloud-import/post-processor.go; DO NOT EDIT MANUALLY -->
- `ufile_bucket_name` (string) - The name of the UFile bucket where the RAW, VHD, VMDK, or qcow2 file will be copied to for import.
This bucket must exist when the post-processor is run.
- `image_name` (string) - The name of the user-defined image, which contains 1-63 characters and only
supports Chinese, English, numbers, '-\_,.:[]'.
- `image_os_type` (string) - Type of the OS. Possible values are: `CentOS`, `Ubuntu`, `Windows`, `RedHat`, `Debian`, `Other`.
You may refer to [ucloud_api_docs](https://docs.ucloud.cn/api/uhost-api/import_custom_image) for detail.
- `image_os_name` (string) - The name of OS. Such as: `CentOS 7.2 64位`, set `Other` When `image_os_type` is `Other`.
You may refer to [ucloud_api_docs](https://docs.ucloud.cn/api/uhost-api/import_custom_image) for detail.
- `format` (string) - The format of the import image , Possible values are: `raw`, `vhd`, `vmdk`, or `qcow2`.
<!-- End of code generated from the comments of the Config struct in post-processor/ucloud-import/post-processor.go; -->

View File

@ -1,5 +0,0 @@
<!-- Code generated from the comments of the Config struct in post-processor/ucloud-import/post-processor.go; DO NOT EDIT MANUALLY -->
Configuration of this post processor
<!-- End of code generated from the comments of the Config struct in post-processor/ucloud-import/post-processor.go; -->

View File

@ -753,10 +753,6 @@
"title": "Triton", "title": "Triton",
"path": "builders/triton" "path": "builders/triton"
}, },
{
"title": "UCloud",
"path": "builders/ucloud-uhost"
},
{ {
"title": "Vagrant", "title": "Vagrant",
"path": "builders/vagrant" "path": "builders/vagrant"
@ -872,10 +868,6 @@
"title": "Shell (Local)", "title": "Shell (Local)",
"path": "post-processors/shell-local" "path": "post-processors/shell-local"
}, },
{
"title": "UCloud Import",
"path": "post-processors/ucloud-import"
},
{ {
"title": "Vagrant", "title": "Vagrant",
"path": "post-processors/vagrant" "path": "post-processors/vagrant"

View File

@ -127,6 +127,13 @@
"pluginTier": "community", "pluginTier": "community",
"version": "latest" "version": "latest"
}, },
{
"title": "UCloud",
"path": "ucloud",
"repo": "hashicorp/packer-plugin-ucloud",
"version": "latest",
"pluginTier": "community"
},
{ {
"title": "VirtualBox", "title": "VirtualBox",
"path": "virtualbox", "path": "virtualbox",