Skip to content

Commit 92ed920

Browse files
new resource torque_ado_server_repository_space_association (#135)
* new resource torque_ado_server_repository_space_association * removes vido log
1 parent cf7eb6b commit 92ed920

File tree

6 files changed

+360
-43
lines changed

6 files changed

+360
-43
lines changed

client/models.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,17 @@ type GitlabEnterpriseRepoSpaceAssociation struct {
104104
AutoRegisterEac bool `json:"eac_auto_registration"`
105105
}
106106

107+
type AdoServerRepoSpaceAssociation struct {
108+
Name string `json:"repository_name"`
109+
URL string `json:"repository_url"`
110+
Token *string `json:"token"`
111+
Branch string `json:"branch"`
112+
CredentialName string `json:"credential_name"`
113+
UseAllAgents bool `json:"use_all_agents"`
114+
Agents []string `json:"agents"`
115+
AutoRegisterEac bool `json:"eac_auto_registration"`
116+
}
117+
107118
type CodeCommitRepoSpaceAssociation struct {
108119
URL string `json:"repository_url"`
109120
RoleArn string `json:"role_arn"`

client/repository.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,39 @@ func (c *Client) OnboardGitlabEnterpriseRepoToSpace(space_name string, repositor
7878
return nil
7979
}
8080

81+
func (c *Client) OnboardAdoServerRepoToSpace(space_name string, repository_name string, repository_url string, token *string, branch string, credential_name string, agents []string, use_all_agents bool, auto_register_eac bool) error {
82+
data := AdoServerRepoSpaceAssociation{
83+
Token: token,
84+
Name: repository_name,
85+
URL: repository_url,
86+
Branch: branch,
87+
CredentialName: credential_name,
88+
Agents: agents,
89+
UseAllAgents: use_all_agents,
90+
AutoRegisterEac: auto_register_eac,
91+
}
92+
93+
payload, err := json.Marshal(data)
94+
95+
if err != nil {
96+
log.Fatalf("impossible to marshall agent association: %s", err)
97+
}
98+
req, err := http.NewRequest("POST", fmt.Sprintf("%sapi/spaces/%s/repositories/azureEnterprise", c.HostURL, space_name), bytes.NewReader(payload))
99+
if err != nil {
100+
return err
101+
}
102+
103+
req.Header.Add("Content-Type", "application/json")
104+
req.Header.Add("Accept", "application/json")
105+
106+
_, err = c.doRequest(req, &c.Token)
107+
if err != nil {
108+
return err
109+
}
110+
111+
return nil
112+
}
113+
81114
func (c *Client) OnboardRepoToSpace(space_name string, repo_name string, repo_type string, repo_url string, repo_token *string, repo_branch string, credential_name *string) error {
82115
var data interface{}
83116
var url string

internal/provider/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ func (p *torqueProvider) Resources(ctx context.Context) []func() resource.Resour
245245
resources.NewTorqueSpaceGenericWebhookNotificationResource,
246246
resources.NewTorqueAuditResource,
247247
resources.NewTorqueElasticsearchAuditResource,
248+
resources.NewTorqueSpaceAdoServerRepositoryResource,
248249
}
249250
}
250251

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
package resources
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
"time"
8+
9+
"github.com/hashicorp/terraform-plugin-framework/path"
10+
"github.com/hashicorp/terraform-plugin-framework/resource"
11+
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
12+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
13+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int32default"
14+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
15+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
16+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
17+
"github.com/hashicorp/terraform-plugin-framework/types"
18+
"github.com/qualitorque/terraform-provider-torque/client"
19+
"github.com/qualitorque/terraform-provider-torque/internal/validators"
20+
)
21+
22+
// Ensure provider defined types fully satisfy framework interfaces.
23+
var _ resource.Resource = &TorqueSpaceAdoServerRepositoryResource{}
24+
var _ resource.ResourceWithImportState = &TorqueSpaceAdoServerRepositoryResource{}
25+
26+
func NewTorqueSpaceAdoServerRepositoryResource() resource.Resource {
27+
return &TorqueSpaceAdoServerRepositoryResource{}
28+
}
29+
30+
// TorqueAgentSpaceAssociationResource defines the resource implementation.
31+
type TorqueSpaceAdoServerRepositoryResource struct {
32+
client *client.Client
33+
}
34+
35+
type TorqueSpaceAdoServerRepositoryResourceModel struct {
36+
SpaceName types.String `tfsdk:"space_name"`
37+
RepositoryName types.String `tfsdk:"repository_name"`
38+
RepositoryUrl types.String `tfsdk:"repository_url"`
39+
Token types.String `tfsdk:"token"`
40+
Branch types.String `tfsdk:"branch"`
41+
CredentialName types.String `tfsdk:"credential_name"`
42+
UseAllAgents types.Bool `tfsdk:"use_all_agents"`
43+
Agents types.List `tfsdk:"agents"`
44+
TimeOut types.Int32 `tfsdk:"timeout"`
45+
AutoRegisterEac types.Bool `tfsdk:"auto_register_eac"`
46+
}
47+
48+
func (r *TorqueSpaceAdoServerRepositoryResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
49+
resp.TypeName = "torque_ado_server_repository_space_association"
50+
}
51+
52+
func (r *TorqueSpaceAdoServerRepositoryResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
53+
resp.Schema = schema.Schema{
54+
MarkdownDescription: "Onboard a new Ado Server repository into an existing space",
55+
56+
Attributes: map[string]schema.Attribute{
57+
"space_name": schema.StringAttribute{
58+
MarkdownDescription: "Existing Torque Space name",
59+
Required: true,
60+
PlanModifiers: []planmodifier.String{
61+
stringplanmodifier.RequiresReplace(),
62+
},
63+
},
64+
"repository_name": schema.StringAttribute{
65+
Description: "The name of the Ado Server repository to onboard. In this example, repo_name",
66+
Required: true,
67+
PlanModifiers: []planmodifier.String{
68+
stringplanmodifier.RequiresReplace(),
69+
},
70+
},
71+
"repository_url": schema.StringAttribute{
72+
Description: "The url of the specific Ado Server repository/project to onboard. For example: https://ado-on-prem.example.com/repo_name",
73+
Required: true,
74+
PlanModifiers: []planmodifier.String{
75+
stringplanmodifier.RequiresReplace(),
76+
},
77+
},
78+
"token": schema.StringAttribute{
79+
Description: "Authentication Token to the project/repository. If omitted, existing credentials provided in the credential_name field will be used for authentication. If provided, a new credentials object will be created.",
80+
Optional: true,
81+
PlanModifiers: []planmodifier.String{
82+
stringplanmodifier.RequiresReplace(),
83+
},
84+
DeprecationMessage: "The token attribute is deprecated and will be removed in a future release. Use the torque_git_credentials resource to store the token and reference it in this resource using the credential_name attribute instead.",
85+
},
86+
"branch": schema.StringAttribute{
87+
Description: "Repository branch to use for blueprints and automation assets",
88+
Required: true,
89+
PlanModifiers: []planmodifier.String{
90+
stringplanmodifier.RequiresReplace(),
91+
},
92+
},
93+
"credential_name": schema.StringAttribute{
94+
Description: "The name of the Credentials to use/create. Must be unique in the space.",
95+
Required: true,
96+
},
97+
"use_all_agents": schema.BoolAttribute{
98+
Description: "Whether all associated agents can be used to onboard and sync this repository. Must be set to false if agents attribute is used.",
99+
Default: booldefault.StaticBool(true),
100+
Optional: true,
101+
Computed: true,
102+
Validators: []validator.Bool{validators.UseAllAgentsValidator{}},
103+
},
104+
"agents": schema.ListAttribute{
105+
Description: "List of specific agents to use to onboard and sync this repository. Cannot be specified when use_all_agents is true.",
106+
Required: false,
107+
Optional: true,
108+
ElementType: types.StringType,
109+
},
110+
"timeout": schema.Int32Attribute{
111+
Description: "Time in minutes to wait for Torque to sync the repository during the onboarding. Default is 1 minute.",
112+
Required: false,
113+
Optional: true,
114+
Computed: true,
115+
Default: int32default.StaticInt32(1),
116+
},
117+
"auto_register_eac": schema.BoolAttribute{
118+
Description: "Auto register environment files",
119+
Default: booldefault.StaticBool(false),
120+
Required: false,
121+
Optional: true,
122+
Computed: true,
123+
},
124+
},
125+
}
126+
}
127+
128+
func (r *TorqueSpaceAdoServerRepositoryResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
129+
// Prevent panic if the provider has not been configured.
130+
if req.ProviderData == nil {
131+
return
132+
}
133+
134+
client, ok := req.ProviderData.(*client.Client)
135+
136+
if !ok {
137+
resp.Diagnostics.AddError(
138+
"Unexpected Resource Configure Type",
139+
fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
140+
)
141+
142+
return
143+
}
144+
145+
r.client = client
146+
}
147+
148+
func (r *TorqueSpaceAdoServerRepositoryResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
149+
var data TorqueSpaceAdoServerRepositoryResourceModel
150+
const (
151+
StatusSyncing = "Syncing"
152+
StatusConnected = "Connected"
153+
Interval = 4 * time.Second
154+
)
155+
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
156+
157+
if resp.Diagnostics.HasError() {
158+
return
159+
}
160+
agents := []string{}
161+
if !data.Agents.IsNull() {
162+
for _, agent := range data.Agents.Elements() {
163+
agents = append(agents, strings.Trim(agent.String(), "\""))
164+
}
165+
}
166+
start := time.Now()
167+
err := r.client.OnboardAdoServerRepoToSpace(data.SpaceName.ValueString(), data.RepositoryName.ValueString(),
168+
data.RepositoryUrl.ValueString(), data.Token.ValueStringPointer(), data.Branch.ValueString(), data.CredentialName.ValueString(), agents, data.UseAllAgents.ValueBool(), data.AutoRegisterEac.ValueBool())
169+
if err != nil {
170+
repo, err := r.client.GetRepoDetails(data.SpaceName.ValueString(), data.RepositoryName.ValueString())
171+
if repo == nil {
172+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to onboard repository to space, got error: %s", err))
173+
return
174+
}
175+
if repo.Status == StatusSyncing {
176+
timeout := time.Duration(data.TimeOut.ValueInt32()) * time.Minute
177+
for time.Since(start) < timeout {
178+
repo, err := r.client.GetRepoDetails(data.SpaceName.ValueString(), data.RepositoryName.ValueString())
179+
if err != nil {
180+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Error while polling repository status: %s", err))
181+
return
182+
}
183+
if repo.Status == StatusConnected {
184+
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
185+
return
186+
}
187+
time.Sleep(Interval)
188+
}
189+
resp.Diagnostics.AddError("Sync Timeout", "Timed out while syncing repository")
190+
return
191+
}
192+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to onboard repository to space, got error: %s", err))
193+
return
194+
}
195+
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
196+
}
197+
198+
func (r *TorqueSpaceAdoServerRepositoryResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
199+
var data TorqueSpaceAdoServerRepositoryResourceModel
200+
201+
// Read Terraform prior state data into the model.
202+
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
203+
204+
if resp.Diagnostics.HasError() {
205+
return
206+
}
207+
208+
// If applicable, this is a great opportunity to initialize any necessary
209+
// provider client data and make a call using it.
210+
211+
// Save updated data into Terraform state.
212+
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
213+
}
214+
215+
func (r *TorqueSpaceAdoServerRepositoryResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
216+
var data TorqueSpaceAdoServerRepositoryResourceModel
217+
218+
// Read Terraform plan data into the model
219+
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
220+
221+
if resp.Diagnostics.HasError() {
222+
return
223+
}
224+
agents := []string{}
225+
if !data.Agents.IsNull() {
226+
for _, agent := range data.Agents.Elements() {
227+
agents = append(agents, strings.Trim(agent.String(), "\""))
228+
}
229+
}
230+
err := r.client.UpdateRepoConfiguration(data.SpaceName.ValueString(), data.RepositoryName.ValueString(),
231+
data.CredentialName.ValueString(), agents, data.UseAllAgents.ValueBool())
232+
if err != nil {
233+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update repository configuration, got error: %s", err))
234+
return
235+
}
236+
237+
// Save updated data into Terraform state
238+
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
239+
}
240+
241+
func (r *TorqueSpaceAdoServerRepositoryResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
242+
var data TorqueSpaceAdoServerRepositoryResourceModel
243+
244+
// Read Terraform prior state data into the model.
245+
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
246+
247+
if resp.Diagnostics.HasError() {
248+
return
249+
}
250+
251+
// Remove repo from space.
252+
err := r.client.RemoveRepoFromSpace(data.SpaceName.ValueString(), data.RepositoryName.ValueString())
253+
if err != nil {
254+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to remove repository from space, got error: %s", err))
255+
return
256+
}
257+
258+
}
259+
260+
func (r *TorqueSpaceAdoServerRepositoryResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
261+
resource.ImportStatePassthroughID(ctx, path.Root("name"), req, resp)
262+
}

internal/provider/resources/space_gitlab_enterprise_repository_resource.go

Lines changed: 2 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
1717
"github.com/hashicorp/terraform-plugin-framework/types"
1818
"github.com/qualitorque/terraform-provider-torque/client"
19+
"github.com/qualitorque/terraform-provider-torque/internal/validators"
1920
)
2021

2122
// Ensure provider defined types fully satisfy framework interfaces.
@@ -98,7 +99,7 @@ func (r *TorqueSpaceGitlabEnterpriseRepositoryResource) Schema(ctx context.Conte
9899
Default: booldefault.StaticBool(true),
99100
Optional: true,
100101
Computed: true,
101-
Validators: []validator.Bool{UseAllAgentsValidator{}},
102+
Validators: []validator.Bool{validators.UseAllAgentsValidator{}},
102103
},
103104
"agents": schema.ListAttribute{
104105
Description: "List of specific agents to use to onboard and sync this repository. Cannot be specified when use_all_agents is true.",
@@ -259,45 +260,3 @@ func (r *TorqueSpaceGitlabEnterpriseRepositoryResource) Delete(ctx context.Conte
259260
func (r *TorqueSpaceGitlabEnterpriseRepositoryResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
260261
resource.ImportStatePassthroughID(ctx, path.Root("name"), req, resp)
261262
}
262-
263-
type UseAllAgentsValidator struct{}
264-
265-
func (v UseAllAgentsValidator) Description(ctx context.Context) string {
266-
return "Ensures use_all_agents is false when agents are provided."
267-
}
268-
269-
func (v UseAllAgentsValidator) MarkdownDescription(ctx context.Context) string {
270-
return v.Description(ctx)
271-
}
272-
273-
func (v UseAllAgentsValidator) ValidateBool(ctx context.Context, req validator.BoolRequest, resp *validator.BoolResponse) {
274-
if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() {
275-
return
276-
}
277-
278-
useAllAgents := req.ConfigValue.ValueBool()
279-
var agents []types.String
280-
281-
// Fetch the agents attribute
282-
if diags := req.Config.GetAttribute(ctx, path.Root("agents"), &agents); diags.HasError() {
283-
resp.Diagnostics.Append(diags...)
284-
return
285-
}
286-
287-
// Check if use_all_agents is true and agents should be empty
288-
if useAllAgents && len(agents) > 0 {
289-
resp.Diagnostics.AddError(
290-
"Invalid Configuration",
291-
"Cannot specify agents when use_all_agents is true.",
292-
)
293-
return
294-
}
295-
296-
// Check if use_all_agents is false and agents list must have at least 1 element
297-
if !useAllAgents && len(agents) == 0 {
298-
resp.Diagnostics.AddError(
299-
"Invalid Configuration",
300-
"Agents list must contain at least one element when use_all_agents is false.",
301-
)
302-
}
303-
}

0 commit comments

Comments
 (0)