internal/provider/datasource_gitlab_project_environments.go (229 lines of code) (raw):

package provider import ( "context" "fmt" "time" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" gitlab "gitlab.com/gitlab-org/api/client-go" "gitlab.com/gitlab-org/terraform-provider-gitlab/internal/provider/utils" ) // Ensure the implementation satisfies the expected interfaces. var ( _ datasource.DataSource = &gitLabProjectEnvironmentsDataSource{} _ datasource.DataSourceWithConfigure = &gitLabProjectEnvironmentsDataSource{} ) func init() { registerDataSource(newGitLabProjectEnvironmentsDataSource) } // NewGitLabRunnersDataSource is a helper function to simplify the provider implementation. func newGitLabProjectEnvironmentsDataSource() datasource.DataSource { return &gitLabProjectEnvironmentsDataSource{} } // gitLabProjectEnvironmentsDataSource is the data source implementation. type gitLabProjectEnvironmentsDataSource struct { client *gitlab.Client } // gitLabProjectEnvironmentsDataSourceModel describes the data source data model. type gitLabProjectEnvironmentsDataSourceModel struct { ID types.String `tfsdk:"id"` Project types.String `tfsdk:"project"` Name types.String `tfsdk:"name"` Search types.String `tfsdk:"search"` States types.String `tfsdk:"states"` Environments []*gitlabEnvironment `tfsdk:"environments"` } type gitlabEnvironment struct { ID types.Int64 `tfsdk:"id"` Name types.String `tfsdk:"name"` Slug types.String `tfsdk:"slug"` Description types.String `tfsdk:"description"` State types.String `tfsdk:"state"` Tier types.String `tfsdk:"tier"` ExternalURL types.String `tfsdk:"external_url"` CreatedAt types.String `tfsdk:"created_at"` UpdatedAt types.String `tfsdk:"updated_at"` ClusterAgentID types.Int64 `tfsdk:"cluster_agent_id"` KubernetesNamespace types.String `tfsdk:"kubernetes_namespace"` FluxResourcePath types.String `tfsdk:"flux_resource_path"` AutoStopAt types.String `tfsdk:"auto_stop_at"` AutoStopSetting types.String `tfsdk:"auto_stop_setting"` } // Metadata returns the data source type name. func (d *gitLabProjectEnvironmentsDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_project_environments" } // Schema defines the schema for the data source. func (d *gitLabProjectEnvironmentsDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { allowedStatesValues := []string{"available", "stopping", "stopped"} environmentTiers := []string{"production", "staging", "testing", "development", "other"} resp.Schema = schema.Schema{ MarkdownDescription: `The ` + "`gitlab_project_environments`" + ` data source retrieves information about all environments of the given project. **Upstream API**: [GitLab REST API docs](https://docs.gitlab.com/api/environments/#list-environments)`, Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ MarkdownDescription: "The ID of this Terraform resource.", Computed: true, }, "project": schema.StringAttribute{ MarkdownDescription: "The ID or full path of the project.", Required: true, }, "name": schema.StringAttribute{ MarkdownDescription: "Return the environment with this name. Mutually exclusive with search.", Optional: true, Validators: []validator.String{ stringvalidator.ConflictsWith(path.MatchRoot("search")), }, }, "search": schema.StringAttribute{ MarkdownDescription: "Return list of environments matching the search criteria. Mutually exclusive with name. Must be at least 3 characters long. ", Optional: true, Validators: []validator.String{ stringvalidator.ConflictsWith(path.MatchRoot("name")), }, }, "states": schema.StringAttribute{ MarkdownDescription: fmt.Sprintf("List all environments that match the specified state. Valid values are %s. Returns all environments if not set.", utils.RenderValueListForDocs(allowedStatesValues)), Optional: true, Validators: []validator.String{stringvalidator.OneOf(allowedStatesValues...)}, }, "environments": schema.ListNestedAttribute{ MarkdownDescription: "The list of environments.", Computed: true, NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ "id": schema.Int64Attribute{ MarkdownDescription: "The ID of the environment.", Computed: true, }, "name": schema.StringAttribute{ MarkdownDescription: "The name of the environment.", Computed: true, }, // see https://docs.gitlab.com/ci/variables/predefined_variables/, CI_ENVIRONMENT_SLUG. API also truncates and adds a randum suffix. "slug": schema.StringAttribute{ MarkdownDescription: "The simplified version of the environment name, suitable for inclusion in DNS, URLs, Kubernetes labels, and so on. The slug is truncated to 24 characters. A random suffix is automatically added to uppercase environment names.", Computed: true, }, "description": schema.StringAttribute{ MarkdownDescription: "The description of the environment.", Computed: true, }, "state": schema.StringAttribute{ MarkdownDescription: fmt.Sprintf("The state of the environment. Value can be one of %s. Returns all environments if not set.", utils.RenderValueListForDocs(allowedStatesValues)), Computed: true, }, "tier": schema.StringAttribute{ MarkdownDescription: fmt.Sprintf("The tier of the environment. Value can be one of %s. Returns all environments if not set.", utils.RenderValueListForDocs(environmentTiers)), Computed: true, }, "external_url": schema.StringAttribute{ MarkdownDescription: "Place to link to for this environment.", Computed: true, }, "created_at": schema.StringAttribute{ MarkdownDescription: "Timestamp of the environment creation, RFC3339 format.", Computed: true, }, "updated_at": schema.StringAttribute{ MarkdownDescription: "Timestamp of the last environment update, RFC3339 format.", Computed: true, }, "cluster_agent_id": schema.Int64Attribute{ MarkdownDescription: "The ID of the environments cluster agent or `null` if none is assigned.", Computed: true, }, "kubernetes_namespace": schema.StringAttribute{ MarkdownDescription: "The Kubernetes namespace to associate with this environment.", Computed: true, }, "flux_resource_path": schema.StringAttribute{ MarkdownDescription: "The Flux resource path to associate with this environment.", Computed: true, }, "auto_stop_at": schema.StringAttribute{ MarkdownDescription: "Timestamp of when the environment is scheduled to stop, RFC3339 format.", Computed: true, }, "auto_stop_setting": schema.StringAttribute{ MarkdownDescription: "The auto stop setting for the environment.", Computed: true, }, }, }, }, }, } } // Configure adds the provider configured client to the data source. func (d *gitLabProjectEnvironmentsDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, _ *datasource.ConfigureResponse) { if req.ProviderData == nil { return } datasource := req.ProviderData.(*GitLabDatasourceData) d.client = datasource.Client } // Read refreshes the Terraform state with the latest data. func (d *gitLabProjectEnvironmentsDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { var config gitLabProjectEnvironmentsDataSourceModel // Read Terraform plan data into the model resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) if resp.Diagnostics.HasError() { return } options := &gitlab.ListEnvironmentsOptions{ ListOptions: gitlab.ListOptions{ PerPage: 20, Page: 1, }, } if !config.Name.IsNull() && !config.Name.IsUnknown() { options.Name = config.Name.ValueStringPointer() } if !config.Search.IsNull() && !config.Search.IsUnknown() { options.Search = config.Search.ValueStringPointer() } if !config.States.IsNull() && !config.States.IsUnknown() { options.States = config.States.ValueStringPointer() } environments, err := d.getAllEnvironments(ctx, config.Project.ValueString(), options) if err != nil { resp.Diagnostics.AddError( "GitLab API error occurred", err.Error(), ) return } for _, environment := range environments { e := &gitlabEnvironment{ ID: types.Int64Value(int64(environment.ID)), Name: types.StringValue(environment.Name), Slug: types.StringValue(environment.Slug), Description: types.StringValue(environment.Description), State: types.StringValue(environment.State), Tier: types.StringValue(environment.Tier), ExternalURL: types.StringValue(environment.ExternalURL), CreatedAt: types.StringValue(environment.CreatedAt.Format(time.RFC3339)), UpdatedAt: types.StringValue(environment.UpdatedAt.Format(time.RFC3339)), KubernetesNamespace: types.StringValue(environment.KubernetesNamespace), FluxResourcePath: types.StringValue(environment.FluxResourcePath), AutoStopSetting: types.StringValue(environment.AutoStopSetting), } if environment.ClusterAgent != nil { e.ClusterAgentID = types.Int64Value(int64(environment.ClusterAgent.ID)) } if environment.AutoStopAt != nil { e.AutoStopAt = types.StringValue(environment.AutoStopAt.Format(time.RFC3339)) } config.Environments = append(config.Environments, e) } diags := resp.State.Set(ctx, &config) resp.Diagnostics.Append(diags...) } func (d *gitLabProjectEnvironmentsDataSource) getAllEnvironments(ctx context.Context, projectID any, options *gitlab.ListEnvironmentsOptions) ([]*gitlab.Environment, error) { var environments []*gitlab.Environment for options.Page != 0 { // Make API call to read environments r, resp, err := d.client.Environments.ListEnvironments(projectID, options, gitlab.WithContext(ctx)) if err != nil { return nil, fmt.Errorf("unable to read environments page %d: %w", options.Page, err) } environments = append(environments, r...) options.Page = resp.NextPage } return environments, nil }