diff --git a/common/common_volcengine_version.go b/common/common_volcengine_version.go index 1144af1c..1f4be772 100644 --- a/common/common_volcengine_version.go +++ b/common/common_volcengine_version.go @@ -2,5 +2,5 @@ package common const ( TerraformProviderName = "terraform-provider-volcengine" - TerraformProviderVersion = "0.0.105" + TerraformProviderVersion = "0.0.106" ) diff --git a/example/dataEcsCommands/main.tf b/example/dataEcsCommands/main.tf new file mode 100644 index 00000000..6cf7c573 --- /dev/null +++ b/example/dataEcsCommands/main.tf @@ -0,0 +1,3 @@ +data "volcengine_ecs_commands" "default" { + command_id = "cmd-ychkepkhtim0tr3b****" +} \ No newline at end of file diff --git a/example/dataEcsInvocationResults/main.tf b/example/dataEcsInvocationResults/main.tf new file mode 100644 index 00000000..3b191b16 --- /dev/null +++ b/example/dataEcsInvocationResults/main.tf @@ -0,0 +1,4 @@ +data "volcengine_ecs_invocation_results" "default" { + invocation_id = "ivk-ych9y4vujvl8j01c****" + invocation_result_status = ["Success"] +} \ No newline at end of file diff --git a/example/dataEcsInvocations/main.tf b/example/dataEcsInvocations/main.tf new file mode 100644 index 00000000..f9f225ed --- /dev/null +++ b/example/dataEcsInvocations/main.tf @@ -0,0 +1,4 @@ +data "volcengine_ecs_invocations" "default" { + invocation_id = "ivk-ych9y4vujvl8j01c****" + invocation_status = ["Success"] +} \ No newline at end of file diff --git a/example/ecsCommand/main.tf b/example/ecsCommand/main.tf new file mode 100644 index 00000000..6d8756ed --- /dev/null +++ b/example/ecsCommand/main.tf @@ -0,0 +1,8 @@ +resource "volcengine_ecs_command" "foo" { + name = "tf-test" + description = "tf" + working_dir = "/home" + username = "root" + timeout = 100 + command_content = "IyEvYmluL2Jhc2gKCgplY2hvICJvcGVyYXRpb24gc3VjY2VzcyEi" +} \ No newline at end of file diff --git a/example/ecsInvocation/main.tf b/example/ecsInvocation/main.tf new file mode 100644 index 00000000..b35075c1 --- /dev/null +++ b/example/ecsInvocation/main.tf @@ -0,0 +1,13 @@ +resource "volcengine_ecs_invocation" "foo" { + command_id = "cmd-ychkepkhtim0tr3b****" + instance_ids = ["i-ychmz92487l8j00o****"] + invocation_name = "tf-test" + invocation_description = "tf" + username = "root" + timeout = 90 + working_dir = "/home" + repeat_mode = "Rate" + frequency = "5m" + launch_time = "2023-06-20T09:48:00Z" + recurrence_end_time = "2023-06-20T09:59:00Z" +} \ No newline at end of file diff --git a/sweep/resource.go b/sweep/resource.go new file mode 100644 index 00000000..917916a7 --- /dev/null +++ b/sweep/resource.go @@ -0,0 +1,28 @@ +package sweep + +import ( + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + ve "github.com/volcengine/terraform-provider-volcengine/common" +) + +type sweepResource struct { + client *ve.SdkClient + resource *schema.Resource + resourceData *schema.ResourceData +} + +func NewSweepResource(resource *schema.Resource, resourceData *schema.ResourceData, client *ve.SdkClient) *sweepResource { + return &sweepResource{ + client: client, + resource: resource, + resourceData: resourceData, + } +} + +func (s *sweepResource) Delete() error { + return s.resource.Delete(s.resourceData, s.client) +} + +func (s *sweepResource) GetId() string { + return s.resourceData.Id() +} diff --git a/sweep/sweep.go b/sweep/sweep.go new file mode 100644 index 00000000..f386581e --- /dev/null +++ b/sweep/sweep.go @@ -0,0 +1,127 @@ +package sweep + +import ( + "fmt" + "os" + "strconv" + "strings" + "sync" + + ve "github.com/volcengine/terraform-provider-volcengine/common" + "github.com/volcengine/terraform-provider-volcengine/logger" +) + +func SharedClientForRegionWithResourceId(region string) (interface{}, error) { + var ( + accessKey string + secretKey string + endpoint string + disableSSL bool + ) + + if accessKey = os.Getenv("VOLCENGINE_ACCESS_KEY"); accessKey == "" { + return nil, fmt.Errorf("%s can not be empty", "VOLCENGINE_ACCESS_KEY") + } + if secretKey = os.Getenv("VOLCENGINE_SECRET_KEY"); secretKey == "" { + return nil, fmt.Errorf("%s can not be empty", "VOLCENGINE_SECRET_KEY") + } + if endpoint = os.Getenv("VOLCENGINE_ENDPOINT"); endpoint == "" { + return nil, fmt.Errorf("%s can not be empty", "VOLCENGINE_ENDPOINT") + } + disableSSL, _ = strconv.ParseBool(os.Getenv("VOLCENGINE_DISABLE_SSL")) + + config := ve.Config{ + AccessKey: accessKey, + SecretKey: secretKey, + Region: region, + Endpoint: endpoint, + DisableSSL: disableSSL, + SessionToken: os.Getenv("VOLCENGINE_SESSION_TOKEN"), + ProxyUrl: os.Getenv("VOLCENGINE_PROXY_URL"), + CustomerHeaders: map[string]string{}, + CustomerEndpoints: defaultCustomerEndPoints(), + } + + headers := os.Getenv("VOLCENGINE_CUSTOMER_HEADERS") + if headers != "" { + hs1 := strings.Split(headers, ",") + for _, hh := range hs1 { + hs2 := strings.Split(hh, ":") + if len(hs2) == 2 { + config.CustomerHeaders[hs2[0]] = hs2[1] + } + } + } + + endpoints := os.Getenv("VOLCENGINE_CUSTOMER_ENDPOINTS") + if endpoints != "" { + ends := strings.Split(endpoints, ",") + for _, end := range ends { + point := strings.Split(end, ":") + if len(point) == 2 { + config.CustomerEndpoints[point[0]] = point[1] + } + } + } + + client, err := config.Client() + if err != nil { + return nil, err + } + + return client, nil +} + +func defaultCustomerEndPoints() map[string]string { + return map[string]string{ + "veenedge": "veenedge.volcengineapi.com", + } +} + +type SweeperInstance interface { + GetId() string + Delete() error +} + +func SweeperScheduler(sweepInstances []SweeperInstance) error { + var ( + wg sync.WaitGroup + syncMap sync.Map + errorStr string + ) + + if len(sweepInstances) == 0 { + return nil + } + wg.Add(len(sweepInstances)) + + for _, value := range sweepInstances { + sweepInstance := value + go func() { + defer func() { + if err := recover(); err != nil { + logger.DebugInfo(" Sweep Resource Panic, resource: ", sweepInstance.GetId(), "error: ", err) + } + wg.Done() + }() + + err := sweepInstance.Delete() + if err != nil { + syncMap.Store(sweepInstance.GetId(), err) + } + }() + } + + wg.Wait() + for _, sweepInstance := range sweepInstances { + if v, exist := syncMap.Load(sweepInstance.GetId()); exist { + if err, ok := v.(error); ok { + errorStr = errorStr + "Sweep Resource " + sweepInstance.GetId() + " error: " + err.Error() + ";\n" + } + } + } + if len(errorStr) > 0 { + return fmt.Errorf(errorStr) + } + return nil +} diff --git a/sweep/sweep_test.go b/sweep/sweep_test.go new file mode 100644 index 00000000..63a69da0 --- /dev/null +++ b/sweep/sweep_test.go @@ -0,0 +1,13 @@ +package sweep_test + +import ( + "testing" + + _ "github.com/volcengine/terraform-provider-volcengine/volcengine/vpc/vpc" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func TestMain(m *testing.M) { + resource.TestMain(m) +} diff --git a/volcengine/ecs/ecs_command/data_source_volcengine_ecs_commands.go b/volcengine/ecs/ecs_command/data_source_volcengine_ecs_commands.go new file mode 100644 index 00000000..cb216b6f --- /dev/null +++ b/volcengine/ecs/ecs_command/data_source_volcengine_ecs_commands.go @@ -0,0 +1,135 @@ +package ecs_command + +import ( + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + ve "github.com/volcengine/terraform-provider-volcengine/common" +) + +func DataSourceVolcengineEcsCommands() *schema.Resource { + return &schema.Resource{ + Read: dataSourceVolcengineEcsCommandsRead, + Schema: map[string]*schema.Schema{ + "command_provider": { + Type: schema.TypeString, + Optional: true, + Description: "The provider of public command. When this field is not specified, query for custom commands.", + }, + "command_id": { + Type: schema.TypeString, + Optional: true, + Description: "The id of ecs command.", + }, + "name": { + Type: schema.TypeString, + Optional: true, + Description: "The name of ecs command. This field support fuzzy query.", + }, + "type": { + Type: schema.TypeString, + Optional: true, + Description: "The type of ecs command. Valid values: `Shell`.", + }, + "order": { + Type: schema.TypeString, + Optional: true, + Description: "The order of ecs command query result.", + }, + "name_regex": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringIsValidRegExp, + Description: "A Name Regex of Resource.", + }, + "output_file": { + Type: schema.TypeString, + Optional: true, + Description: "File name where to save data source results.", + }, + "total_count": { + Type: schema.TypeInt, + Computed: true, + Description: "The total count of query.", + }, + "commands": { + Description: "The collection of query.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + Description: "The id of the ecs command.", + }, + "command_id": { + Type: schema.TypeString, + Computed: true, + Description: "The id of the ecs command.", + }, + "name": { + Type: schema.TypeString, + Computed: true, + Description: "The name of the ecs command.", + }, + "created_at": { + Type: schema.TypeString, + Computed: true, + Description: "The create time of the ecs command.", + }, + "updated_at": { + Type: schema.TypeString, + Computed: true, + Description: "The update time of the ecs command.", + }, + "type": { + Type: schema.TypeString, + Computed: true, + Description: "The type of the ecs command.", + }, + "timeout": { + Type: schema.TypeInt, + Computed: true, + Description: "The timeout of the ecs command.", + }, + "working_dir": { + Type: schema.TypeString, + Computed: true, + Description: "The working directory of the ecs command.", + }, + "username": { + Type: schema.TypeString, + Computed: true, + Description: "The username of the ecs command.", + }, + "description": { + Type: schema.TypeString, + Computed: true, + Description: "The description of the ecs command.", + }, + "command_provider": { + Type: schema.TypeString, + Computed: true, + Description: "The provider of the public command.", + }, + "command_content": { + Type: schema.TypeString, + Computed: true, + Description: "The base64 encoded content of the ecs command.", + }, + "invocation_times": { + Type: schema.TypeInt, + Computed: true, + Description: "The invocation times of the ecs command. Public commands do not display the invocation times.", + }, + }, + }, + }, + }, + } +} + +func dataSourceVolcengineEcsCommandsRead(d *schema.ResourceData, meta interface{}) error { + service := NewEcsCommandService(meta.(*ve.SdkClient)) + return ve.DefaultDispatcher().Data(service, d, DataSourceVolcengineEcsCommands()) +} diff --git a/volcengine/ecs/ecs_command/resource_volcengine_ecs_command.go b/volcengine/ecs/ecs_command/resource_volcengine_ecs_command.go new file mode 100644 index 00000000..db5f132e --- /dev/null +++ b/volcengine/ecs/ecs_command/resource_volcengine_ecs_command.go @@ -0,0 +1,127 @@ +package ecs_command + +import ( + "fmt" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + ve "github.com/volcengine/terraform-provider-volcengine/common" +) + +/* + +Import +EcsCommand can be imported using the id, e.g. +``` +$ terraform import volcengine_ecs_command.default cmd-ychkepkhtim0tr3bcsw1 +``` + +*/ + +func ResourceVolcengineEcsCommand() *schema.Resource { + resource := &schema.Resource{ + Create: resourceVolcengineEcsCommandCreate, + Read: resourceVolcengineEcsCommandRead, + Update: resourceVolcengineEcsCommandUpdate, + Delete: resourceVolcengineEcsCommandDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: "The name of the ecs command.", + }, + "description": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The description of the ecs command.", + }, + "command_content": { + Type: schema.TypeString, + Required: true, + Description: "The base64 encoded content of the ecs command.", + }, + "working_dir": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The working directory of the ecs command.", + }, + "username": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The username of the ecs command.", + }, + "timeout": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ValidateFunc: validation.IntBetween(10, 600), + Description: "The timeout of the ecs command. Valid value range: 10-600.", + }, + + "invocation_times": { + Type: schema.TypeInt, + Computed: true, + Description: "The invocation times of the ecs command. Public commands do not display the invocation times.", + }, + "created_at": { + Type: schema.TypeString, + Computed: true, + Description: "The create time of the ecs command.", + }, + "updated_at": { + Type: schema.TypeString, + Computed: true, + Description: "The update time of the ecs command.", + }, + }, + } + return resource +} + +func resourceVolcengineEcsCommandCreate(d *schema.ResourceData, meta interface{}) (err error) { + service := NewEcsCommandService(meta.(*ve.SdkClient)) + err = ve.DefaultDispatcher().Create(service, d, ResourceVolcengineEcsCommand()) + if err != nil { + return fmt.Errorf("error on creating ecs command %q, %s", d.Id(), err) + } + return resourceVolcengineEcsCommandRead(d, meta) +} + +func resourceVolcengineEcsCommandRead(d *schema.ResourceData, meta interface{}) (err error) { + service := NewEcsCommandService(meta.(*ve.SdkClient)) + err = ve.DefaultDispatcher().Read(service, d, ResourceVolcengineEcsCommand()) + if err != nil { + return fmt.Errorf("error on reading ecs command %q, %s", d.Id(), err) + } + return err +} + +func resourceVolcengineEcsCommandUpdate(d *schema.ResourceData, meta interface{}) (err error) { + service := NewEcsCommandService(meta.(*ve.SdkClient)) + err = ve.DefaultDispatcher().Update(service, d, ResourceVolcengineEcsCommand()) + if err != nil { + return fmt.Errorf("error on updating ecs command %q, %s", d.Id(), err) + } + return resourceVolcengineEcsCommandRead(d, meta) +} + +func resourceVolcengineEcsCommandDelete(d *schema.ResourceData, meta interface{}) (err error) { + service := NewEcsCommandService(meta.(*ve.SdkClient)) + err = ve.DefaultDispatcher().Delete(service, d, ResourceVolcengineEcsCommand()) + if err != nil { + return fmt.Errorf("error on deleting ecs command %q, %s", d.Id(), err) + } + return err +} diff --git a/volcengine/ecs/ecs_command/service_volcengine_ecs_command.go b/volcengine/ecs/ecs_command/service_volcengine_ecs_command.go new file mode 100644 index 00000000..df6079ae --- /dev/null +++ b/volcengine/ecs/ecs_command/service_volcengine_ecs_command.go @@ -0,0 +1,248 @@ +package ecs_command + +import ( + "encoding/json" + "errors" + "fmt" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + ve "github.com/volcengine/terraform-provider-volcengine/common" + "github.com/volcengine/terraform-provider-volcengine/logger" +) + +type VolcengineEcsCommandService struct { + Client *ve.SdkClient + Dispatcher *ve.Dispatcher +} + +func NewEcsCommandService(c *ve.SdkClient) *VolcengineEcsCommandService { + return &VolcengineEcsCommandService{ + Client: c, + Dispatcher: &ve.Dispatcher{}, + } +} + +func (s *VolcengineEcsCommandService) GetClient() *ve.SdkClient { + return s.Client +} + +func (s *VolcengineEcsCommandService) ReadResources(m map[string]interface{}) (data []interface{}, err error) { + var ( + resp *map[string]interface{} + results interface{} + ok bool + ) + return ve.WithPageNumberQuery(m, "PageSize", "PageNumber", 20, 1, func(condition map[string]interface{}) ([]interface{}, error) { + action := "DescribeCommands" + bytes, _ := json.Marshal(condition) + logger.Debug(logger.ReqFormat, action, string(bytes)) + if condition == nil { + resp, err = s.Client.UniversalClient.DoCall(getUniversalInfo(action), nil) + if err != nil { + return data, err + } + } else { + resp, err = s.Client.UniversalClient.DoCall(getUniversalInfo(action), &condition) + if err != nil { + return data, err + } + } + respBytes, _ := json.Marshal(resp) + logger.Debug(logger.RespFormat, action, condition, string(respBytes)) + results, err = ve.ObtainSdkValue("Result.Commands", *resp) + if err != nil { + return data, err + } + if results == nil { + results = []interface{}{} + } + if data, ok = results.([]interface{}); !ok { + return data, errors.New("Result.Commands is not Slice") + } + return data, err + }) +} + +func (s *VolcengineEcsCommandService) ReadResource(resourceData *schema.ResourceData, id string) (data map[string]interface{}, err error) { + var ( + results []interface{} + ok bool + ) + if id == "" { + id = s.ReadResourceId(resourceData.Id()) + } + req := map[string]interface{}{ + "CommandId": id, + } + results, err = s.ReadResources(req) + if err != nil { + return data, err + } + for _, v := range results { + if data, ok = v.(map[string]interface{}); !ok { + return data, errors.New("Value is not map ") + } + } + if len(data) == 0 { + return data, fmt.Errorf("ecs command %s is not exist ", id) + } + return data, err +} + +func (s *VolcengineEcsCommandService) RefreshResourceState(resourceData *schema.ResourceData, target []string, timeout time.Duration, id string) *resource.StateChangeConf { + return &resource.StateChangeConf{} +} + +func (VolcengineEcsCommandService) WithResourceResponseHandlers(data map[string]interface{}) []ve.ResourceResponseHandler { + handler := func() (map[string]interface{}, map[string]ve.ResponseConvert, error) { + return data, nil, nil + } + return []ve.ResourceResponseHandler{handler} +} + +func (s *VolcengineEcsCommandService) CreateResource(resourceData *schema.ResourceData, resource *schema.Resource) []ve.Callback { + callback := ve.Callback{ + Call: ve.SdkCall{ + Action: "CreateCommand", + ConvertMode: ve.RequestConvertAll, + ContentType: ve.ContentTypeDefault, + BeforeCall: func(d *schema.ResourceData, client *ve.SdkClient, call ve.SdkCall) (bool, error) { + (*call.SdkParam)["Type"] = "Shell" + return true, nil + }, + ExecuteCall: func(d *schema.ResourceData, client *ve.SdkClient, call ve.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + resp, err := s.Client.UniversalClient.DoCall(getUniversalInfo(call.Action), call.SdkParam) + logger.Debug(logger.RespFormat, call.Action, resp, err) + return resp, err + }, + AfterCall: func(d *schema.ResourceData, client *ve.SdkClient, resp *map[string]interface{}, call ve.SdkCall) error { + id, _ := ve.ObtainSdkValue("Result.CommandId", *resp) + d.SetId(id.(string)) + return nil + }, + }, + } + return []ve.Callback{callback} +} + +func (s *VolcengineEcsCommandService) ModifyResource(resourceData *schema.ResourceData, resource *schema.Resource) []ve.Callback { + callback := ve.Callback{ + Call: ve.SdkCall{ + Action: "ModifyCommand", + ConvertMode: ve.RequestConvertInConvert, + ContentType: ve.ContentTypeDefault, + Convert: map[string]ve.RequestConvert{ + "name": { + TargetField: "Name", + }, + "description": { + TargetField: "Description", + }, + "command_content": { + TargetField: "CommandContent", + }, + "working_dir": { + TargetField: "WorkingDir", + }, + "username": { + TargetField: "Username", + }, + "timeout": { + TargetField: "Timeout", + }, + }, + BeforeCall: func(d *schema.ResourceData, client *ve.SdkClient, call ve.SdkCall) (bool, error) { + if len(*call.SdkParam) > 0 { + (*call.SdkParam)["CommandId"] = d.Id() + return true, nil + } + return false, nil + }, + ExecuteCall: func(d *schema.ResourceData, client *ve.SdkClient, call ve.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + resp, err := s.Client.UniversalClient.DoCall(getUniversalInfo(call.Action), call.SdkParam) + logger.Debug(logger.RespFormat, call.Action, resp, err) + return resp, err + }, + }, + } + return []ve.Callback{callback} +} + +func (s *VolcengineEcsCommandService) RemoveResource(resourceData *schema.ResourceData, r *schema.Resource) []ve.Callback { + callback := ve.Callback{ + Call: ve.SdkCall{ + Action: "DeleteCommand", + ConvertMode: ve.RequestConvertIgnore, + ContentType: ve.ContentTypeDefault, + SdkParam: &map[string]interface{}{ + "CommandId": resourceData.Id(), + }, + ExecuteCall: func(d *schema.ResourceData, client *ve.SdkClient, call ve.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + return s.Client.UniversalClient.DoCall(getUniversalInfo(call.Action), call.SdkParam) + }, + AfterCall: func(d *schema.ResourceData, client *ve.SdkClient, resp *map[string]interface{}, call ve.SdkCall) error { + return ve.CheckResourceUtilRemoved(d, s.ReadResource, 5*time.Minute) + }, + CallError: func(d *schema.ResourceData, client *ve.SdkClient, call ve.SdkCall, baseErr error) error { + //出现错误后重试 + return resource.Retry(15*time.Minute, func() *resource.RetryError { + _, callErr := s.ReadResource(d, "") + if callErr != nil { + if ve.ResourceNotFoundError(callErr) { + return nil + } else { + return resource.NonRetryableError(fmt.Errorf("error on reading ecs command on delete %q, %w", d.Id(), callErr)) + } + } + _, callErr = call.ExecuteCall(d, client, call) + if callErr == nil { + return nil + } + return resource.RetryableError(callErr) + }) + }, + }, + } + return []ve.Callback{callback} +} + +func (s *VolcengineEcsCommandService) DatasourceResources(*schema.ResourceData, *schema.Resource) ve.DataSourceInfo { + return ve.DataSourceInfo{ + RequestConverts: map[string]ve.RequestConvert{ + "command_provider": { + TargetField: "Provider", + }, + }, + NameField: "Name", + IdField: "CommandId", + CollectField: "commands", + ResponseConverts: map[string]ve.ResponseConvert{ + "CommandId": { + TargetField: "id", + KeepDefault: true, + }, + "Provider": { + TargetField: "command_provider", + }, + }, + } +} + +func (s *VolcengineEcsCommandService) ReadResourceId(id string) string { + return id +} + +func getUniversalInfo(actionName string) ve.UniversalInfo { + return ve.UniversalInfo{ + ServiceName: "ecs", + Version: "2020-04-01", + HttpMethod: ve.GET, + ContentType: ve.Default, + Action: actionName, + } +} diff --git a/volcengine/ecs/ecs_instance/resource_volcengine_ecs_instance.go b/volcengine/ecs/ecs_instance/resource_volcengine_ecs_instance.go index edc98d89..7580c004 100644 --- a/volcengine/ecs/ecs_instance/resource_volcengine_ecs_instance.go +++ b/volcengine/ecs/ecs_instance/resource_volcengine_ecs_instance.go @@ -368,6 +368,7 @@ func ResourceVolcengineEcsInstance() *schema.Resource { "project_name": { Type: schema.TypeString, Optional: true, + Computed: true, Description: "The ProjectName of the ecs instance.", }, "tags": ve.TagsSchema(), diff --git a/volcengine/ecs/ecs_invocation/data_source_volcengine_ecs_invocations.go b/volcengine/ecs/ecs_invocation/data_source_volcengine_ecs_invocations.go new file mode 100644 index 00000000..f449bc42 --- /dev/null +++ b/volcengine/ecs/ecs_invocation/data_source_volcengine_ecs_invocations.go @@ -0,0 +1,206 @@ +package ecs_invocation + +import ( + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + ve "github.com/volcengine/terraform-provider-volcengine/common" +) + +func DataSourceVolcengineEcsInvocations() *schema.Resource { + return &schema.Resource{ + Read: dataSourceVolcengineEcsInvocationsRead, + Schema: map[string]*schema.Schema{ + "invocation_id": { + Type: schema.TypeString, + Optional: true, + Description: "The id of ecs invocation.", + }, + "invocation_name": { + Type: schema.TypeString, + Optional: true, + Description: "The name of ecs invocation. This field support fuzzy query.", + }, + "command_id": { + Type: schema.TypeString, + Optional: true, + Description: "The id of ecs command.", + }, + "command_name": { + Type: schema.TypeString, + Optional: true, + Description: "The name of ecs command. This field support fuzzy query.", + }, + "command_type": { + Type: schema.TypeString, + Optional: true, + Description: "The type of ecs command. Valid values: `Shell`.", + }, + "repeat_mode": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "Once", + "Rate", + "Fixed", + }, false), + Description: "The repeat mode of ecs invocation. Valid values: `Once`, `Rate`, `Fixed`.", + }, + "invocation_status": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice([]string{ + "Pending", "Scheduled", "Running", "Success", + "Failed", "Stopped", "PartialFailed", "Finished", + }, false), + }, + Set: schema.HashString, + Description: "The list of status of ecs invocation. Valid values: `Pending`, `Scheduled`, `Running`, `Success`, `Failed`, `Stopped`, `PartialFailed`, `Finished`.", + }, + "name_regex": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringIsValidRegExp, + Description: "A Name Regex of Resource.", + }, + "output_file": { + Type: schema.TypeString, + Optional: true, + Description: "File name where to save data source results.", + }, + "total_count": { + Type: schema.TypeInt, + Computed: true, + Description: "The total count of query.", + }, + "invocations": { + Description: "The collection of query.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + Description: "The id of the ecs invocation.", + }, + "invocation_id": { + Type: schema.TypeString, + Computed: true, + Description: "The id of the ecs invocation.", + }, + "invocation_name": { + Type: schema.TypeString, + Computed: true, + Description: "The name of the ecs invocation.", + }, + "invocation_description": { + Type: schema.TypeString, + Computed: true, + Description: "The description of the ecs invocation.", + }, + "command_id": { + Type: schema.TypeString, + Computed: true, + Description: "The id of the ecs command.", + }, + "command_name": { + Type: schema.TypeString, + Computed: true, + Description: "The name of the ecs command.", + }, + "command_description": { + Type: schema.TypeString, + Computed: true, + Description: "The description of the ecs command.", + }, + "command_type": { + Type: schema.TypeString, + Computed: true, + Description: "The type of the ecs command.", + }, + "command_provider": { + Type: schema.TypeString, + Computed: true, + Description: "The provider of the ecs command.", + }, + "invocation_status": { + Type: schema.TypeString, + Computed: true, + Description: "The status of the ecs invocation.", + }, + "command_content": { + Type: schema.TypeString, + Computed: true, + Description: "The base64 encoded content of the ecs command.", + }, + "timeout": { + Type: schema.TypeInt, + Computed: true, + Description: "The timeout of the ecs command.", + }, + "working_dir": { + Type: schema.TypeString, + Computed: true, + Description: "The working directory of the ecs command.", + }, + "username": { + Type: schema.TypeString, + Computed: true, + Description: "The username of the ecs command.", + }, + "start_time": { + Type: schema.TypeString, + Computed: true, + Description: "The start time of the ecs invocation.", + }, + "end_time": { + Type: schema.TypeString, + Computed: true, + Description: "The end time of the ecs invocation.", + }, + "instance_number": { + Type: schema.TypeInt, + Computed: true, + Description: "The instance number of the ecs invocation.", + }, + "instance_ids": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Description: "The list of ECS instance IDs.", + }, + "repeat_mode": { + Type: schema.TypeString, + Computed: true, + Description: "The repeat mode of the ecs invocation.", + }, + "frequency": { + Type: schema.TypeString, + Computed: true, + Description: "The frequency of the ecs invocation.", + }, + "launch_time": { + Type: schema.TypeString, + Computed: true, + Description: "The launch time of the ecs invocation.", + }, + "recurrence_end_time": { + Type: schema.TypeString, + Computed: true, + Description: "The recurrence end time of the ecs invocation.", + }, + }, + }, + }, + }, + } +} + +func dataSourceVolcengineEcsInvocationsRead(d *schema.ResourceData, meta interface{}) error { + service := NewEcsInvocationService(meta.(*ve.SdkClient)) + return ve.DefaultDispatcher().Data(service, d, DataSourceVolcengineEcsInvocations()) +} diff --git a/volcengine/ecs/ecs_invocation/resource_volcengine_ecs_invocation.go b/volcengine/ecs/ecs_invocation/resource_volcengine_ecs_invocation.go new file mode 100644 index 00000000..74c6e8fe --- /dev/null +++ b/volcengine/ecs/ecs_invocation/resource_volcengine_ecs_invocation.go @@ -0,0 +1,178 @@ +package ecs_invocation + +import ( + "fmt" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + ve "github.com/volcengine/terraform-provider-volcengine/common" +) + +/* + +Import +EcsInvocation can be imported using the id, e.g. +``` +$ terraform import volcengine_ecs_invocation.default ivk-ychnxnm45dl8j0mm**** +``` + +*/ + +func ResourceVolcengineEcsInvocation() *schema.Resource { + resource := &schema.Resource{ + Create: resourceVolcengineEcsInvocationCreate, + Read: resourceVolcengineEcsInvocationRead, + Delete: resourceVolcengineEcsInvocationDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + Schema: map[string]*schema.Schema{ + "command_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The command id of the ecs invocation.", + }, + "instance_ids": { + Type: schema.TypeSet, + Required: true, + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Set: schema.HashString, + Description: "The list of ECS instance IDs.", + }, + "invocation_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The name of the ecs invocation.", + }, + "invocation_description": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "The description of the ecs invocation.", + }, + "username": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The username of the ecs command. When this field is not specified, use the value of the field with the same name in ecs command as the default value.", + }, + "working_dir": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + Description: "The working directory of the ecs invocation. When this field is not specified, use the value of the field with the same name in ecs command as the default value.", + }, + "timeout": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: validation.IntBetween(10, 600), + Description: "The timeout of the ecs command. Valid value range: 10-600. When this field is not specified, use the value of the field with the same name in ecs command as the default value.", + }, + "repeat_mode": { + Type: schema.TypeString, + Optional: true, + Default: "Once", + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + "Once", + "Rate", + "Fixed", + }, false), + Description: "The repeat mode of the ecs invocation. Valid values: `Once`, `Rate`, `Fixed`.", + }, + "frequency": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + if d.Get("repeat_mode").(string) == "Rate" { + return false + } + return true + }, + Description: "The frequency of the ecs invocation. This field is valid and required when the value of the repeat_mode field is `Rate`.", + }, + "launch_time": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + if d.Get("repeat_mode").(string) == "Once" { + return true + } + return false + }, + Description: "The launch time of the ecs invocation. RFC3339 format. This field is valid and required when the value of the repeat_mode field is `Rate` or `Fixed`.", + }, + "recurrence_end_time": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + if d.Get("repeat_mode").(string) == "Rate" { + return false + } + return true + }, + Description: "The recurrence end time of the ecs invocation. RFC3339 format. This field is valid and required when the value of the repeat_mode field is `Rate`.", + }, + + "invocation_status": { + Type: schema.TypeString, + Computed: true, + Description: "The status of the ecs invocation.", + }, + "start_time": { + Type: schema.TypeString, + Computed: true, + Description: "The start time of the ecs invocation.", + }, + "end_time": { + Type: schema.TypeString, + Computed: true, + Description: "The end time of the ecs invocation.", + }, + }, + } + return resource +} + +func resourceVolcengineEcsInvocationCreate(d *schema.ResourceData, meta interface{}) (err error) { + service := NewEcsInvocationService(meta.(*ve.SdkClient)) + err = ve.DefaultDispatcher().Create(service, d, ResourceVolcengineEcsInvocation()) + if err != nil { + return fmt.Errorf("error on creating ecs invocation %q, %s", d.Id(), err) + } + return resourceVolcengineEcsInvocationRead(d, meta) +} + +func resourceVolcengineEcsInvocationRead(d *schema.ResourceData, meta interface{}) (err error) { + service := NewEcsInvocationService(meta.(*ve.SdkClient)) + err = ve.DefaultDispatcher().Read(service, d, ResourceVolcengineEcsInvocation()) + if err != nil { + return fmt.Errorf("error on reading ecs invocation %q, %s", d.Id(), err) + } + return err +} + +func resourceVolcengineEcsInvocationDelete(d *schema.ResourceData, meta interface{}) (err error) { + service := NewEcsInvocationService(meta.(*ve.SdkClient)) + err = ve.DefaultDispatcher().Delete(service, d, ResourceVolcengineEcsInvocation()) + if err != nil { + return fmt.Errorf("error on deleting ecs invocation %q, %s", d.Id(), err) + } + return err +} diff --git a/volcengine/ecs/ecs_invocation/service_volcengine_ecs_invocation.go b/volcengine/ecs/ecs_invocation/service_volcengine_ecs_invocation.go new file mode 100644 index 00000000..0f74fafa --- /dev/null +++ b/volcengine/ecs/ecs_invocation/service_volcengine_ecs_invocation.go @@ -0,0 +1,337 @@ +package ecs_invocation + +import ( + "encoding/json" + "errors" + "fmt" + "strings" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + ve "github.com/volcengine/terraform-provider-volcengine/common" + "github.com/volcengine/terraform-provider-volcengine/logger" +) + +type VolcengineEcsInvocationService struct { + Client *ve.SdkClient + Dispatcher *ve.Dispatcher +} + +func NewEcsInvocationService(c *ve.SdkClient) *VolcengineEcsInvocationService { + return &VolcengineEcsInvocationService{ + Client: c, + Dispatcher: &ve.Dispatcher{}, + } +} + +func (s *VolcengineEcsInvocationService) GetClient() *ve.SdkClient { + return s.Client +} + +func (s *VolcengineEcsInvocationService) ReadResources(m map[string]interface{}) (data []interface{}, err error) { + var ( + resp *map[string]interface{} + results interface{} + ok bool + ) + return ve.WithPageNumberQuery(m, "PageSize", "PageNumber", 20, 1, func(condition map[string]interface{}) ([]interface{}, error) { + action := "DescribeInvocations" + bytes, _ := json.Marshal(condition) + logger.Debug(logger.ReqFormat, action, string(bytes)) + if condition == nil { + resp, err = s.Client.UniversalClient.DoCall(getUniversalInfo(action), nil) + if err != nil { + return data, err + } + } else { + resp, err = s.Client.UniversalClient.DoCall(getUniversalInfo(action), &condition) + if err != nil { + return data, err + } + } + respBytes, _ := json.Marshal(resp) + logger.Debug(logger.RespFormat, action, condition, string(respBytes)) + results, err = ve.ObtainSdkValue("Result.Invocations", *resp) + if err != nil { + return data, err + } + if results == nil { + results = []interface{}{} + } + if data, ok = results.([]interface{}); !ok { + return data, errors.New("Result.Invocations is not Slice") + } + + for _, v := range data { + instanceIds := make([]string, 0) + invocation, ok := v.(map[string]interface{}) + if !ok { + return data, fmt.Errorf(" Invocation is not map ") + } + action := "DescribeInvocationInstances" + req := map[string]interface{}{ + "InvocationId": invocation["InvocationId"], + } + logger.Debug(logger.ReqFormat, action, req) + resp, err = s.Client.UniversalClient.DoCall(getUniversalInfo(action), &req) + logger.Debug(logger.RespFormat, action, req, resp) + results, err := ve.ObtainSdkValue("Result.InvocationInstances", *resp) + if err != nil { + return data, err + } + if results == nil { + results = []interface{}{} + } + instances, ok := results.([]interface{}) + if !ok { + return data, errors.New("Result.InvocationInstances is not Slice") + } + if len(instances) == 0 { + return data, fmt.Errorf("invocation %s does not contain any instances", invocation["InvocationId"]) + } + for _, v1 := range instances { + instance, ok := v1.(map[string]interface{}) + if !ok { + return data, fmt.Errorf(" invocation instance is not map ") + } + instanceIds = append(instanceIds, instance["InstanceId"].(string)) + } + invocation["InstanceIds"] = instanceIds + } + + return data, err + }) +} + +func (s *VolcengineEcsInvocationService) ReadResource(resourceData *schema.ResourceData, id string) (data map[string]interface{}, err error) { + var ( + results []interface{} + ok bool + ) + if id == "" { + id = s.ReadResourceId(resourceData.Id()) + } + req := map[string]interface{}{ + "InvocationId": id, + } + results, err = s.ReadResources(req) + if err != nil { + return data, err + } + for _, v := range results { + if data, ok = v.(map[string]interface{}); !ok { + return data, errors.New("Value is not map ") + } + } + if len(data) == 0 { + return data, fmt.Errorf("ecs invocation %s is not exist ", id) + } + + // 处理 launch_time、recurrence_end_time 传参与查询结果不一致的问题 + if mode := resourceData.Get("repeat_mode"); mode.(string) != "Once" { + layout := "2006-01-02T15:04:05Z" + launchTimeExpr, exist1 := resourceData.GetOkExists("launch_time") + endTimeExpr, exist2 := resourceData.GetOkExists("recurrence_end_time") + if exist1 && launchTimeExpr.(string) != "" { + launchTime, err := ParseUTCTime(launchTimeExpr.(string)) + if err != nil { + return data, err + } + lt := launchTime.Format(layout) + if lt == data["LaunchTime"].(string) { + data["LaunchTime"] = launchTimeExpr + } + } + if exist2 && endTimeExpr.(string) != "" { + endTime, err := ParseUTCTime(endTimeExpr.(string)) + if err != nil { + return data, err + } + et := endTime.Format(layout) + if et == data["RecurrenceEndTime"].(string) { + data["RecurrenceEndTime"] = endTimeExpr + } + } + } + + return data, err +} + +func ParseUTCTime(timeExpr string) (time.Time, error) { + timeWithoutSecond, err := ParseUTCTimeWithoutSecond(timeExpr) + if err != nil { + timeWithSecond, err := ParseUTCTimeWithSecond(timeExpr) + if err != nil { + return time.Time{}, err + } else { + return timeWithSecond, nil + } + } else { + return timeWithoutSecond, nil + } +} + +func ParseUTCTimeWithoutSecond(timeExpr string) (time.Time, error) { + t, err := time.Parse("2006-01-02T15:04Z", timeExpr) + if err != nil { + return time.Time{}, fmt.Errorf("parse time failed, error: %v, time expr: %v", err, timeExpr) + } + + return t, nil +} + +func ParseUTCTimeWithSecond(timeExpr string) (time.Time, error) { + t, err := time.Parse("2006-01-02T15:04:05Z", timeExpr) + if err != nil { + return time.Time{}, fmt.Errorf("parse time failed, error: %v, time expr: %v", err, timeExpr) + } + + t = t.Add(time.Duration(t.Second()) * time.Second * -1) + + return t, nil +} + +func (s *VolcengineEcsInvocationService) RefreshResourceState(resourceData *schema.ResourceData, target []string, timeout time.Duration, id string) *resource.StateChangeConf { + return &resource.StateChangeConf{ + Pending: []string{}, + Delay: 1 * time.Second, + MinTimeout: 1 * time.Second, + Target: target, + Timeout: timeout, + Refresh: func() (result interface{}, state string, err error) { + var ( + demo map[string]interface{} + status interface{} + ) + //no failed status. + demo, err = s.ReadResource(resourceData, id) + if err != nil { + return nil, "", err + } + status, err = ve.ObtainSdkValue("InvocationStatus", demo) + if err != nil { + return nil, "", err + } + return demo, status.(string), err + }, + } +} + +func (VolcengineEcsInvocationService) WithResourceResponseHandlers(invocation map[string]interface{}) []ve.ResourceResponseHandler { + handler := func() (map[string]interface{}, map[string]ve.ResponseConvert, error) { + return invocation, nil, nil + } + return []ve.ResourceResponseHandler{handler} +} + +func (s *VolcengineEcsInvocationService) CreateResource(resourceData *schema.ResourceData, resource *schema.Resource) []ve.Callback { + callback := ve.Callback{ + Call: ve.SdkCall{ + Action: "InvokeCommand", + ConvertMode: ve.RequestConvertAll, + ContentType: ve.ContentTypeDefault, + Convert: map[string]ve.RequestConvert{ + "instance_ids": { + TargetField: "InstanceIds", + ConvertType: ve.ConvertWithN, + }, + }, + ExecuteCall: func(d *schema.ResourceData, client *ve.SdkClient, call ve.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + resp, err := s.Client.UniversalClient.DoCall(getUniversalInfo(call.Action), call.SdkParam) + logger.Debug(logger.RespFormat, call.Action, resp, err) + return resp, err + }, + AfterCall: func(d *schema.ResourceData, client *ve.SdkClient, resp *map[string]interface{}, call ve.SdkCall) error { + id, _ := ve.ObtainSdkValue("Result.InvocationId", *resp) + d.SetId(id.(string)) + return nil + }, + }, + } + return []ve.Callback{callback} +} + +func (s *VolcengineEcsInvocationService) ModifyResource(resourceData *schema.ResourceData, resource *schema.Resource) []ve.Callback { + return []ve.Callback{} +} + +func (s *VolcengineEcsInvocationService) RemoveResource(resourceData *schema.ResourceData, r *schema.Resource) []ve.Callback { + callback := ve.Callback{ + Call: ve.SdkCall{ + Action: "StopInvocation", + ConvertMode: ve.RequestConvertIgnore, + ContentType: ve.ContentTypeDefault, + SdkParam: &map[string]interface{}{ + "InvocationId": resourceData.Id(), + }, + BeforeCall: func(d *schema.ResourceData, client *ve.SdkClient, call ve.SdkCall) (bool, error) { + status := d.Get("invocation_status") + mode := d.Get("repeat_mode") + if mode.(string) == "Once" || (status.(string) != "Pending" && status.(string) != "Scheduled") { + return false, nil + } else { + (*call.SdkParam)["InvocationId"] = d.Id() + return true, nil + } + }, + ExecuteCall: func(d *schema.ResourceData, client *ve.SdkClient, call ve.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + return s.Client.UniversalClient.DoCall(getUniversalInfo(call.Action), call.SdkParam) + }, + Refresh: &ve.StateRefresh{ + Target: []string{"Stopped"}, + Timeout: resourceData.Timeout(schema.TimeoutDelete), + }, + }, + } + return []ve.Callback{callback} +} + +func (s *VolcengineEcsInvocationService) DatasourceResources(*schema.ResourceData, *schema.Resource) ve.DataSourceInfo { + return ve.DataSourceInfo{ + RequestConverts: map[string]ve.RequestConvert{ + "invocation_status": { + TargetField: "InvocationStatus", + Convert: func(data *schema.ResourceData, i interface{}) interface{} { + var status string + statusSet, ok := data.GetOk("invocation_status") + if !ok { + return status + } + statusList := statusSet.(*schema.Set).List() + statusArr := make([]string, 0) + for _, value := range statusList { + statusArr = append(statusArr, value.(string)) + } + status = strings.Join(statusArr, ",") + return status + }, + }, + }, + NameField: "InvocationName", + IdField: "InvocationId", + CollectField: "invocations", + ResponseConverts: map[string]ve.ResponseConvert{ + "InvocationId": { + TargetField: "id", + KeepDefault: true, + }, + }, + } +} + +func (s *VolcengineEcsInvocationService) ReadResourceId(id string) string { + return id +} + +func getUniversalInfo(actionName string) ve.UniversalInfo { + return ve.UniversalInfo{ + ServiceName: "ecs", + Version: "2020-04-01", + HttpMethod: ve.GET, + ContentType: ve.Default, + Action: actionName, + } +} diff --git a/volcengine/ecs/ecs_invocation_result/data_source_volcengine_ecs_invocation_results.go b/volcengine/ecs/ecs_invocation_result/data_source_volcengine_ecs_invocation_results.go new file mode 100644 index 00000000..702b1d62 --- /dev/null +++ b/volcengine/ecs/ecs_invocation_result/data_source_volcengine_ecs_invocation_results.go @@ -0,0 +1,131 @@ +package ecs_invocation_result + +import ( + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + ve "github.com/volcengine/terraform-provider-volcengine/common" +) + +func DataSourceVolcengineEcsInvocationResults() *schema.Resource { + return &schema.Resource{ + Read: dataSourceVolcengineEcsInvocationResultsRead, + Schema: map[string]*schema.Schema{ + "invocation_id": { + Type: schema.TypeString, + Required: true, + Description: "The id of ecs invocation.", + }, + "instance_id": { + Type: schema.TypeString, + Optional: true, + Description: "The id of ecs instance.", + }, + "command_id": { + Type: schema.TypeString, + Optional: true, + Description: "The id of ecs command.", + }, + "invocation_result_status": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice([]string{ + "Pending", "Running", "Success", "Failed", "Timeout", + }, false), + }, + Set: schema.HashString, + Description: "The list of status of ecs invocation in a single instance. Valid values: `Pending`, `Running`, `Success`, `Failed`, `Timeout`.", + }, + "output_file": { + Type: schema.TypeString, + Optional: true, + Description: "File name where to save data source results.", + }, + "total_count": { + Type: schema.TypeInt, + Computed: true, + Description: "The total count of query.", + }, + "invocation_results": { + Description: "The collection of query.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + Description: "The id of the ecs invocation result.", + }, + "invocation_result_id": { + Type: schema.TypeString, + Computed: true, + Description: "The id of the ecs invocation result.", + }, + "invocation_id": { + Type: schema.TypeString, + Computed: true, + Description: "The id of the ecs invocation.", + }, + "instance_id": { + Type: schema.TypeString, + Computed: true, + Description: "The id of the ecs instance.", + }, + "command_id": { + Type: schema.TypeString, + Computed: true, + Description: "The id of the ecs command.", + }, + "invocation_result_status": { + Type: schema.TypeString, + Computed: true, + Description: "The status of ecs invocation in a single instance.", + }, + "username": { + Type: schema.TypeString, + Computed: true, + Description: "The username of the ecs command.", + }, + "output": { + Type: schema.TypeString, + Computed: true, + Description: "The base64 encoded output message of the ecs invocation. ", + }, + "exit_code": { + Type: schema.TypeInt, + Computed: true, + Description: "The exit code of the ecs command.", + }, + "error_code": { + Type: schema.TypeString, + Computed: true, + Description: "The error code of the ecs invocation.", + }, + "error_message": { + Type: schema.TypeString, + Computed: true, + Description: "The error message of the ecs invocation.", + }, + "start_time": { + Type: schema.TypeString, + Computed: true, + Description: "The start time of the ecs invocation in the instance.", + }, + "end_time": { + Type: schema.TypeString, + Computed: true, + Description: "The end time of the ecs invocation in the instance.", + }, + }, + }, + }, + }, + } +} + +func dataSourceVolcengineEcsInvocationResultsRead(d *schema.ResourceData, meta interface{}) error { + service := NewEcsInvocationResultService(meta.(*ve.SdkClient)) + return ve.DefaultDispatcher().Data(service, d, DataSourceVolcengineEcsInvocationResults()) +} diff --git a/volcengine/ecs/ecs_invocation_result/service_volcengine_ecs_invocation_result.go b/volcengine/ecs/ecs_invocation_result/service_volcengine_ecs_invocation_result.go new file mode 100644 index 00000000..749071c9 --- /dev/null +++ b/volcengine/ecs/ecs_invocation_result/service_volcengine_ecs_invocation_result.go @@ -0,0 +1,140 @@ +package ecs_invocation_result + +import ( + "encoding/json" + "errors" + "strings" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + ve "github.com/volcengine/terraform-provider-volcengine/common" + "github.com/volcengine/terraform-provider-volcengine/logger" +) + +type VolcengineEcsInvocationResultService struct { + Client *ve.SdkClient + Dispatcher *ve.Dispatcher +} + +func NewEcsInvocationResultService(c *ve.SdkClient) *VolcengineEcsInvocationResultService { + return &VolcengineEcsInvocationResultService{ + Client: c, + Dispatcher: &ve.Dispatcher{}, + } +} + +func (s *VolcengineEcsInvocationResultService) GetClient() *ve.SdkClient { + return s.Client +} + +func (s *VolcengineEcsInvocationResultService) ReadResources(m map[string]interface{}) (data []interface{}, err error) { + var ( + resp *map[string]interface{} + results interface{} + ok bool + ) + return ve.WithPageNumberQuery(m, "PageSize", "PageNumber", 20, 1, func(condition map[string]interface{}) ([]interface{}, error) { + action := "DescribeInvocationResults" + bytes, _ := json.Marshal(condition) + logger.Debug(logger.ReqFormat, action, string(bytes)) + if condition == nil { + resp, err = s.Client.UniversalClient.DoCall(getUniversalInfo(action), nil) + if err != nil { + return data, err + } + } else { + resp, err = s.Client.UniversalClient.DoCall(getUniversalInfo(action), &condition) + if err != nil { + return data, err + } + } + respBytes, _ := json.Marshal(resp) + logger.Debug(logger.RespFormat, action, condition, string(respBytes)) + results, err = ve.ObtainSdkValue("Result.InvocationResults", *resp) + if err != nil { + return data, err + } + if results == nil { + results = []interface{}{} + } + if data, ok = results.([]interface{}); !ok { + return data, errors.New("Result.InvocationResults is not Slice") + } + + return data, err + }) +} + +func (s *VolcengineEcsInvocationResultService) ReadResource(resourceData *schema.ResourceData, id string) (data map[string]interface{}, err error) { + return data, err +} + +func (s *VolcengineEcsInvocationResultService) RefreshResourceState(resourceData *schema.ResourceData, target []string, timeout time.Duration, id string) *resource.StateChangeConf { + return nil +} + +func (VolcengineEcsInvocationResultService) WithResourceResponseHandlers(data map[string]interface{}) []ve.ResourceResponseHandler { + handler := func() (map[string]interface{}, map[string]ve.ResponseConvert, error) { + return data, nil, nil + } + return []ve.ResourceResponseHandler{handler} +} + +func (s *VolcengineEcsInvocationResultService) CreateResource(resourceData *schema.ResourceData, resource *schema.Resource) []ve.Callback { + return []ve.Callback{} +} + +func (s *VolcengineEcsInvocationResultService) ModifyResource(resourceData *schema.ResourceData, resource *schema.Resource) []ve.Callback { + return []ve.Callback{} +} + +func (s *VolcengineEcsInvocationResultService) RemoveResource(resourceData *schema.ResourceData, r *schema.Resource) []ve.Callback { + return []ve.Callback{} +} + +func (s *VolcengineEcsInvocationResultService) DatasourceResources(*schema.ResourceData, *schema.Resource) ve.DataSourceInfo { + return ve.DataSourceInfo{ + RequestConverts: map[string]ve.RequestConvert{ + "invocation_result_status": { + TargetField: "InvocationResultStatus", + Convert: func(data *schema.ResourceData, i interface{}) interface{} { + var status string + statusSet, ok := data.GetOk("invocation_result_status") + if !ok { + return status + } + statusList := statusSet.(*schema.Set).List() + statusArr := make([]string, 0) + for _, value := range statusList { + statusArr = append(statusArr, value.(string)) + } + status = strings.Join(statusArr, ",") + return status + }, + }, + }, + IdField: "InvocationResultId", + CollectField: "invocation_results", + ResponseConverts: map[string]ve.ResponseConvert{ + "InvocationResultId": { + TargetField: "id", + KeepDefault: true, + }, + }, + } +} + +func (s *VolcengineEcsInvocationResultService) ReadResourceId(id string) string { + return id +} + +func getUniversalInfo(actionName string) ve.UniversalInfo { + return ve.UniversalInfo{ + ServiceName: "ecs", + Version: "2020-04-01", + HttpMethod: ve.GET, + ContentType: ve.Default, + Action: actionName, + } +} diff --git a/volcengine/provider.go b/volcengine/provider.go index 81501ab4..9489cb26 100644 --- a/volcengine/provider.go +++ b/volcengine/provider.go @@ -79,10 +79,12 @@ import ( "github.com/volcengine/terraform-provider-volcengine/volcengine/cr/cr_vpc_endpoint" "github.com/volcengine/terraform-provider-volcengine/volcengine/ebs/volume" "github.com/volcengine/terraform-provider-volcengine/volcengine/ebs/volume_attach" + "github.com/volcengine/terraform-provider-volcengine/volcengine/ecs/ecs_command" "github.com/volcengine/terraform-provider-volcengine/volcengine/ecs/ecs_deployment_set" "github.com/volcengine/terraform-provider-volcengine/volcengine/ecs/ecs_deployment_set_associate" "github.com/volcengine/terraform-provider-volcengine/volcengine/ecs/ecs_instance" "github.com/volcengine/terraform-provider-volcengine/volcengine/ecs/ecs_instance_state" + "github.com/volcengine/terraform-provider-volcengine/volcengine/ecs/ecs_invocation" "github.com/volcengine/terraform-provider-volcengine/volcengine/ecs/ecs_key_pair" "github.com/volcengine/terraform-provider-volcengine/volcengine/ecs/ecs_key_pair_associate" "github.com/volcengine/terraform-provider-volcengine/volcengine/ecs/ecs_launch_template" @@ -266,6 +268,8 @@ func Provider() terraform.ResourceProvider { "volcengine_ecs_deployment_sets": ecs_deployment_set.DataSourceVolcengineEcsDeploymentSets(), "volcengine_ecs_key_pairs": ecs_key_pair.DataSourceVolcengineEcsKeyPairs(), "volcengine_ecs_launch_templates": ecs_launch_template.DataSourceVolcengineEcsLaunchTemplates(), + "volcengine_ecs_commands": ecs_command.DataSourceVolcengineEcsCommands(), + "volcengine_ecs_invocations": ecs_invocation.DataSourceVolcengineEcsInvocations(), // ================ NAT ================ "volcengine_snat_entries": snat_entry.DataSourceVolcengineSnatEntries(), @@ -441,6 +445,8 @@ func Provider() terraform.ResourceProvider { "volcengine_ecs_key_pair": ecs_key_pair.ResourceVolcengineEcsKeyPair(), "volcengine_ecs_key_pair_associate": ecs_key_pair_associate.ResourceVolcengineEcsKeyPairAssociate(), "volcengine_ecs_launch_template": ecs_launch_template.ResourceVolcengineEcsLaunchTemplate(), + "volcengine_ecs_command": ecs_command.ResourceVolcengineEcsCommand(), + "volcengine_ecs_invocation": ecs_invocation.ResourceVolcengineEcsInvocation(), // ================ NAT ================ "volcengine_snat_entry": snat_entry.ResourceVolcengineSnatEntry(), diff --git a/volcengine/vpc/vpc/resource_volcengine_vpc.go b/volcengine/vpc/vpc/resource_volcengine_vpc.go index 740ea7bb..6b22bf93 100644 --- a/volcengine/vpc/vpc/resource_volcengine_vpc.go +++ b/volcengine/vpc/vpc/resource_volcengine_vpc.go @@ -81,9 +81,9 @@ func ResourceVolcengineVpc() *schema.Resource { Description: "The IPv6 CIDR block of the VPC.", }, "project_name": { - Type: schema.TypeString, - Optional: true, - //ForceNew: true, + Type: schema.TypeString, + Optional: true, + Computed: true, Description: "The ProjectName of the VPC.", }, "tags": ve.TagsSchema(), diff --git a/volcengine/vpc/vpc/sweep.go b/volcengine/vpc/vpc/sweep.go new file mode 100644 index 00000000..20690c0a --- /dev/null +++ b/volcengine/vpc/vpc/sweep.go @@ -0,0 +1,80 @@ +//go:build sweep +// +build sweep + +package vpc + +import ( + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + ve "github.com/volcengine/terraform-provider-volcengine/common" + "github.com/volcengine/terraform-provider-volcengine/logger" + "github.com/volcengine/terraform-provider-volcengine/sweep" +) + +func init() { + resource.AddTestSweepers("volcengine_vpc", &resource.Sweeper{ + Name: "volcengine_vpc", + F: testSweepVolcengineVpcResource, + }) +} + +func testSweepVolcengineVpcResource(region string) error { + var ( + results []interface{} + err error + skipSweep bool + ) + + prefixes := []string{ + "acc-test", + } + + sharedClient, err := sweep.SharedClientForRegionWithResourceId(region) + if err != nil { + return fmt.Errorf("getting volcengine client error: %s", err.Error()) + } + + client := sharedClient.(*ve.SdkClient) + service := NewVpcService(client) + + sweepResources := make([]sweep.SweeperInstance, 0) + results, err = service.ReadResources(map[string]interface{}{}) + if err != nil { + return fmt.Errorf("vpc ReadResources error: %s", err.Error()) + } + + for _, value := range results { + resource, ok := value.(map[string]interface{}) + if !ok { + return fmt.Errorf("result is not map") + } + resourceId := resource["VpcId"] + resourceName := resource["VpcName"] + skipSweep = true + for _, prefix := range prefixes { + if strings.HasPrefix(resourceName.(string), prefix) { + skipSweep = false + break + } + } + if skipSweep { + logger.DebugInfo(" Skip sweep vpc: %s (%s)", resourceId, resourceName) + continue + } + + r := ResourceVolcengineVpc() + d := r.Data(nil) + d.SetId(resourceId.(string)) + + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) + } + + err = sweep.SweeperScheduler(sweepResources) + if err != nil { + return fmt.Errorf(" Sweep vpc Error, region: %s, err: %s", region, err) + } + + return nil +} diff --git a/website/docs/d/ecs_commands.html.markdown b/website/docs/d/ecs_commands.html.markdown new file mode 100644 index 00000000..eb102684 --- /dev/null +++ b/website/docs/d/ecs_commands.html.markdown @@ -0,0 +1,45 @@ +--- +subcategory: "ECS" +layout: "volcengine" +page_title: "Volcengine: volcengine_ecs_commands" +sidebar_current: "docs-volcengine-datasource-ecs_commands" +description: |- + Use this data source to query detailed information of ecs commands +--- +# volcengine_ecs_commands +Use this data source to query detailed information of ecs commands +## Example Usage +```hcl +data "volcengine_ecs_commands" "default" { + command_id = "cmd-ychkepkhtim0tr3b****" +} +``` +## Argument Reference +The following arguments are supported: +* `command_id` - (Optional) The id of ecs command. +* `command_provider` - (Optional) The provider of public command. When this field is not specified, query for custom commands. +* `name_regex` - (Optional) A Name Regex of Resource. +* `name` - (Optional) The name of ecs command. This field support fuzzy query. +* `order` - (Optional) The order of ecs command query result. +* `output_file` - (Optional) File name where to save data source results. +* `type` - (Optional) The type of ecs command. Valid values: `Shell`. + +## Attributes Reference +In addition to all arguments above, the following attributes are exported: +* `commands` - The collection of query. + * `command_content` - The base64 encoded content of the ecs command. + * `command_id` - The id of the ecs command. + * `command_provider` - The provider of the public command. + * `created_at` - The create time of the ecs command. + * `description` - The description of the ecs command. + * `id` - The id of the ecs command. + * `invocation_times` - The invocation times of the ecs command. Public commands do not display the invocation times. + * `name` - The name of the ecs command. + * `timeout` - The timeout of the ecs command. + * `type` - The type of the ecs command. + * `updated_at` - The update time of the ecs command. + * `username` - The username of the ecs command. + * `working_dir` - The working directory of the ecs command. +* `total_count` - The total count of query. + + diff --git a/website/docs/d/ecs_invocations.html.markdown b/website/docs/d/ecs_invocations.html.markdown new file mode 100644 index 00000000..af40fef5 --- /dev/null +++ b/website/docs/d/ecs_invocations.html.markdown @@ -0,0 +1,57 @@ +--- +subcategory: "ECS" +layout: "volcengine" +page_title: "Volcengine: volcengine_ecs_invocations" +sidebar_current: "docs-volcengine-datasource-ecs_invocations" +description: |- + Use this data source to query detailed information of ecs invocations +--- +# volcengine_ecs_invocations +Use this data source to query detailed information of ecs invocations +## Example Usage +```hcl +data "volcengine_ecs_invocations" "default" { + invocation_id = "ivk-ych9y4vujvl8j01c****" + invocation_status = ["Success"] +} +``` +## Argument Reference +The following arguments are supported: +* `command_id` - (Optional) The id of ecs command. +* `command_name` - (Optional) The name of ecs command. This field support fuzzy query. +* `command_type` - (Optional) The type of ecs command. Valid values: `Shell`. +* `invocation_id` - (Optional) The id of ecs invocation. +* `invocation_name` - (Optional) The name of ecs invocation. This field support fuzzy query. +* `invocation_status` - (Optional) The list of status of ecs invocation. Valid values: `Pending`, `Scheduled`, `Running`, `Success`, `Failed`, `Stopped`, `PartialFailed`, `Finished`. +* `name_regex` - (Optional) A Name Regex of Resource. +* `output_file` - (Optional) File name where to save data source results. +* `repeat_mode` - (Optional) The repeat mode of ecs invocation. Valid values: `Once`, `Rate`, `Fixed`. + +## Attributes Reference +In addition to all arguments above, the following attributes are exported: +* `invocations` - The collection of query. + * `command_content` - The base64 encoded content of the ecs command. + * `command_description` - The description of the ecs command. + * `command_id` - The id of the ecs command. + * `command_name` - The name of the ecs command. + * `command_provider` - The provider of the ecs command. + * `command_type` - The type of the ecs command. + * `end_time` - The end time of the ecs invocation. + * `frequency` - The frequency of the ecs invocation. + * `id` - The id of the ecs invocation. + * `instance_ids` - The list of ECS instance IDs. + * `instance_number` - The instance number of the ecs invocation. + * `invocation_description` - The description of the ecs invocation. + * `invocation_id` - The id of the ecs invocation. + * `invocation_name` - The name of the ecs invocation. + * `invocation_status` - The status of the ecs invocation. + * `launch_time` - The launch time of the ecs invocation. + * `recurrence_end_time` - The recurrence end time of the ecs invocation. + * `repeat_mode` - The repeat mode of the ecs invocation. + * `start_time` - The start time of the ecs invocation. + * `timeout` - The timeout of the ecs command. + * `username` - The username of the ecs command. + * `working_dir` - The working directory of the ecs command. +* `total_count` - The total count of query. + + diff --git a/website/docs/r/ecs_command.html.markdown b/website/docs/r/ecs_command.html.markdown new file mode 100644 index 00000000..280b690d --- /dev/null +++ b/website/docs/r/ecs_command.html.markdown @@ -0,0 +1,44 @@ +--- +subcategory: "ECS" +layout: "volcengine" +page_title: "Volcengine: volcengine_ecs_command" +sidebar_current: "docs-volcengine-resource-ecs_command" +description: |- + Provides a resource to manage ecs command +--- +# volcengine_ecs_command +Provides a resource to manage ecs command +## Example Usage +```hcl +resource "volcengine_ecs_command" "foo" { + name = "tf-test" + description = "tf" + working_dir = "/home" + username = "root" + timeout = 100 + command_content = "IyEvYmluL2Jhc2gKCgplY2hvICJvcGVyYXRpb24gc3VjY2VzcyEi" +} +``` +## Argument Reference +The following arguments are supported: +* `command_content` - (Required) The base64 encoded content of the ecs command. +* `name` - (Required) The name of the ecs command. +* `description` - (Optional) The description of the ecs command. +* `timeout` - (Optional) The timeout of the ecs command. Valid value range: 10-600. +* `username` - (Optional) The username of the ecs command. +* `working_dir` - (Optional) The working directory of the ecs command. + +## Attributes Reference +In addition to all arguments above, the following attributes are exported: +* `id` - ID of the resource. +* `created_at` - The create time of the ecs command. +* `invocation_times` - The invocation times of the ecs command. Public commands do not display the invocation times. +* `updated_at` - The update time of the ecs command. + + +## Import +EcsCommand can be imported using the id, e.g. +``` +$ terraform import volcengine_ecs_command.default cmd-ychkepkhtim0tr3bcsw1 +``` + diff --git a/website/docs/r/ecs_invocation.html.markdown b/website/docs/r/ecs_invocation.html.markdown new file mode 100644 index 00000000..821ea2af --- /dev/null +++ b/website/docs/r/ecs_invocation.html.markdown @@ -0,0 +1,54 @@ +--- +subcategory: "ECS" +layout: "volcengine" +page_title: "Volcengine: volcengine_ecs_invocation" +sidebar_current: "docs-volcengine-resource-ecs_invocation" +description: |- + Provides a resource to manage ecs invocation +--- +# volcengine_ecs_invocation +Provides a resource to manage ecs invocation +## Example Usage +```hcl +resource "volcengine_ecs_invocation" "foo" { + command_id = "cmd-ychkepkhtim0tr3b****" + instance_ids = ["i-ychmz92487l8j00o****"] + invocation_name = "tf-test" + invocation_description = "tf" + username = "root" + timeout = 90 + working_dir = "/home" + repeat_mode = "Rate" + frequency = "5m" + launch_time = "2023-06-20T09:48:00Z" + recurrence_end_time = "2023-06-20T09:59:00Z" +} +``` +## Argument Reference +The following arguments are supported: +* `command_id` - (Required, ForceNew) The command id of the ecs invocation. +* `instance_ids` - (Required, ForceNew) The list of ECS instance IDs. +* `invocation_name` - (Required, ForceNew) The name of the ecs invocation. +* `username` - (Required, ForceNew) The username of the ecs command. When this field is not specified, use the value of the field with the same name in ecs command as the default value. +* `frequency` - (Optional, ForceNew) The frequency of the ecs invocation. This field is valid and required when the value of the repeat_mode field is `Rate`. +* `invocation_description` - (Optional, ForceNew) The description of the ecs invocation. +* `launch_time` - (Optional, ForceNew) The launch time of the ecs invocation. RFC3339 format. This field is valid and required when the value of the repeat_mode field is `Rate` or `Fixed`. +* `recurrence_end_time` - (Optional, ForceNew) The recurrence end time of the ecs invocation. RFC3339 format. This field is valid and required when the value of the repeat_mode field is `Rate`. +* `repeat_mode` - (Optional, ForceNew) The repeat mode of the ecs invocation. Valid values: `Once`, `Rate`, `Fixed`. +* `timeout` - (Optional, ForceNew) The timeout of the ecs command. Valid value range: 10-600. When this field is not specified, use the value of the field with the same name in ecs command as the default value. +* `working_dir` - (Optional, ForceNew) The working directory of the ecs invocation. When this field is not specified, use the value of the field with the same name in ecs command as the default value. + +## Attributes Reference +In addition to all arguments above, the following attributes are exported: +* `id` - ID of the resource. +* `end_time` - The end time of the ecs invocation. +* `invocation_status` - The status of the ecs invocation. +* `start_time` - The start time of the ecs invocation. + + +## Import +EcsInvocation can be imported using the id, e.g. +``` +$ terraform import volcengine_ecs_invocation.default ivk-ychnxnm45dl8j0mm**** +``` + diff --git a/website/volcengine.erb b/website/volcengine.erb index cc8663ae..6b5a7e14 100644 --- a/website/volcengine.erb +++ b/website/volcengine.erb @@ -340,12 +340,18 @@