extract and vendor ucloud (#10953)
This commit is contained in:
parent
bc35a737c0
commit
4be2c350bf
|
@ -49,10 +49,6 @@
|
|||
/builder/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
|
||||
/website/pages/docs/builders/yandex* @GennadySpb @alexanderKhaustov @seukyaso
|
||||
|
||||
|
@ -74,4 +70,3 @@
|
|||
/post-processor/yandex-export/ @GennadySpb
|
||||
/post-processor/yandex-import/ @GennadySpb
|
||||
/post-processor/vsphere-template/ nelson@bennu.cl
|
||||
/post-processor/ucloud-import/ @shawnmssu
|
||||
|
|
|
@ -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")
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
||||
}
|
|
@ -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",
|
||||
})
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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"
|
||||
]
|
||||
}]
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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) {}
|
|
@ -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) {
|
||||
}
|
|
@ -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) {
|
||||
}
|
|
@ -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) {
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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
|
||||
|
||||
}
|
|
@ -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) {}
|
|
@ -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) {
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -28,7 +28,6 @@ import (
|
|||
profitbricksbuilder "github.com/hashicorp/packer/builder/profitbricks"
|
||||
tencentcloudcvmbuilder "github.com/hashicorp/packer/builder/tencentcloud/cvm"
|
||||
tritonbuilder "github.com/hashicorp/packer/builder/triton"
|
||||
uclouduhostbuilder "github.com/hashicorp/packer/builder/ucloud/uhost"
|
||||
vagrantbuilder "github.com/hashicorp/packer/builder/vagrant"
|
||||
yandexbuilder "github.com/hashicorp/packer/builder/yandex"
|
||||
artificepostprocessor "github.com/hashicorp/packer/post-processor/artifice"
|
||||
|
@ -37,7 +36,6 @@ import (
|
|||
digitaloceanimportpostprocessor "github.com/hashicorp/packer/post-processor/digitalocean-import"
|
||||
manifestpostprocessor "github.com/hashicorp/packer/post-processor/manifest"
|
||||
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"
|
||||
vagrantcloudpostprocessor "github.com/hashicorp/packer/post-processor/vagrant-cloud"
|
||||
yandexexportpostprocessor "github.com/hashicorp/packer/post-processor/yandex-export"
|
||||
|
@ -76,7 +74,6 @@ var Builders = map[string]packersdk.Builder{
|
|||
"profitbricks": new(profitbricksbuilder.Builder),
|
||||
"tencentcloud-cvm": new(tencentcloudcvmbuilder.Builder),
|
||||
"triton": new(tritonbuilder.Builder),
|
||||
"ucloud-uhost": new(uclouduhostbuilder.Builder),
|
||||
"vagrant": new(vagrantbuilder.Builder),
|
||||
"yandex": new(yandexbuilder.Builder),
|
||||
}
|
||||
|
@ -103,7 +100,6 @@ var PostProcessors = map[string]packersdk.PostProcessor{
|
|||
"digitalocean-import": new(digitaloceanimportpostprocessor.PostProcessor),
|
||||
"manifest": new(manifestpostprocessor.PostProcessor),
|
||||
"shell-local": new(shelllocalpostprocessor.PostProcessor),
|
||||
"ucloud-import": new(ucloudimportpostprocessor.PostProcessor),
|
||||
"vagrant": new(vagrantpostprocessor.PostProcessor),
|
||||
"vagrant-cloud": new(vagrantcloudpostprocessor.PostProcessor),
|
||||
"yandex-export": new(yandexexportpostprocessor.PostProcessor),
|
||||
|
|
|
@ -49,6 +49,8 @@ import (
|
|||
puppetserverprovisioner "github.com/hashicorp/packer-plugin-puppet/provisioner/puppet-server"
|
||||
qemubuilder "github.com/hashicorp/packer-plugin-qemu/builder/qemu"
|
||||
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"
|
||||
virtualboxovfbuilder "github.com/hashicorp/packer-plugin-virtualbox/builder/virtualbox/ovf"
|
||||
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),
|
||||
"qemu": new(qemubuilder.Builder),
|
||||
"scaleway": new(scalewaybuilder.Builder),
|
||||
"ucloud-uhost": new(uclouduhostbuilder.Builder),
|
||||
"vsphere-clone": new(vsphereclonebuilder.Builder),
|
||||
"vsphere-iso": new(vsphereisobuilder.Builder),
|
||||
"virtualbox-iso": new(virtualboxisobuilder.Builder),
|
||||
|
@ -129,6 +132,7 @@ var VendoredPostProcessors = map[string]packersdk.PostProcessor{
|
|||
"exoscale-import": new(exoscaleimportpostprocessor.PostProcessor),
|
||||
"googlecompute-export": new(googlecomputeexportpostprocessor.PostProcessor),
|
||||
"googlecompute-import": new(googlecomputeimportpostprocessor.PostProcessor),
|
||||
"ucloud-import": new(ucloudimportpostprocessor.PostProcessor),
|
||||
"vsphere-template": new(vspheretemplatepostprocessor.PostProcessor),
|
||||
"vsphere": new(vspherepostprocessor.PostProcessor),
|
||||
}
|
||||
|
|
3
go.mod
3
go.mod
|
@ -57,6 +57,7 @@ require (
|
|||
github.com/hashicorp/packer-plugin-qemu 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-ucloud 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-vsphere v0.0.1
|
||||
|
@ -79,8 +80,6 @@ require (
|
|||
github.com/shirou/gopsutil v3.21.1+incompatible
|
||||
github.com/stretchr/testify v1.7.0
|
||||
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/yandex-cloud/go-genproto v0.0.0-20200915125933-33de72a328bd
|
||||
github.com/yandex-cloud/go-sdk v0.0.0-20200921111412-ef15ded2014c
|
||||
|
|
8
go.sum
8
go.sum
|
@ -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.2.0 h1:A4Dq7p4y1vscY4gMzp7GQaXyDJYYhP4ukp4fapPSOY4=
|
||||
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/go.mod h1:OOGNMK8Y8zjsYngesZH5kCbH0Fj8PKvhqPp8w1ejM3Y=
|
||||
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/go.mod h1:0PfYow01SHPMhKY31xa+EFz2RStxIqj6JFAJS+IkCi4=
|
||||
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/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 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 v1.2.4 h1:cTciPbZ/VSOzCLKclmssnfQ/jyoVyOcJ3aoJyUV1Urc=
|
||||
github.com/ugorji/go v1.2.4/go.mod h1:EuaSCk8iZMdIspsu6HXH7X2UGKw1ezO4wCfGszGmmo4=
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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.
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
```
|
|
@ -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; -->
|
|
@ -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; -->
|
|
@ -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; -->
|
|
@ -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; -->
|
|
@ -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; -->
|
|
@ -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; -->
|
|
@ -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; -->
|
|
@ -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).
|
|
@ -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; -->
|
|
@ -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; -->
|
|
@ -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; -->
|
|
@ -753,10 +753,6 @@
|
|||
"title": "Triton",
|
||||
"path": "builders/triton"
|
||||
},
|
||||
{
|
||||
"title": "UCloud",
|
||||
"path": "builders/ucloud-uhost"
|
||||
},
|
||||
{
|
||||
"title": "Vagrant",
|
||||
"path": "builders/vagrant"
|
||||
|
@ -872,10 +868,6 @@
|
|||
"title": "Shell (Local)",
|
||||
"path": "post-processors/shell-local"
|
||||
},
|
||||
{
|
||||
"title": "UCloud Import",
|
||||
"path": "post-processors/ucloud-import"
|
||||
},
|
||||
{
|
||||
"title": "Vagrant",
|
||||
"path": "post-processors/vagrant"
|
||||
|
|
|
@ -127,6 +127,13 @@
|
|||
"pluginTier": "community",
|
||||
"version": "latest"
|
||||
},
|
||||
{
|
||||
"title": "UCloud",
|
||||
"path": "ucloud",
|
||||
"repo": "hashicorp/packer-plugin-ucloud",
|
||||
"version": "latest",
|
||||
"pluginTier": "community"
|
||||
},
|
||||
{
|
||||
"title": "VirtualBox",
|
||||
"path": "virtualbox",
|
||||
|
|
Loading…
Reference in New Issue