Skip to content

Commit 0ec0886

Browse files
authored
Merge pull request #1016 from Dutchy-/gitlab_project_runner
Implement resource `gitlab_project_runner_enablement` to attach specific runners to projects
2 parents d95ab29 + 3000c9e commit 0ec0886

File tree

5 files changed

+326
-0
lines changed

5 files changed

+326
-0
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "gitlab_project_runner_enablement Resource - terraform-provider-gitlab"
4+
subcategory: ""
5+
description: |-
6+
The gitlab_project_runner_enablement resource allows to enable a runner in a project.
7+
Upstream API: GitLab REST API docs https://docs.gitlab.com/ee/api/runners.html#enable-a-runner-in-project
8+
---
9+
10+
# gitlab_project_runner_enablement (Resource)
11+
12+
The `gitlab_project_runner_enablement` resource allows to enable a runner in a project.
13+
14+
**Upstream API**: [GitLab REST API docs](https://docs.gitlab.com/ee/api/runners.html#enable-a-runner-in-project)
15+
16+
## Example Usage
17+
18+
```terraform
19+
resource "gitlab_project_runner_enablement" "foo" {
20+
project = 5
21+
runner_id = 7
22+
}
23+
```
24+
25+
<!-- schema generated by tfplugindocs -->
26+
## Schema
27+
28+
### Required
29+
30+
- `project` (String) The ID or URL-encoded path of the project owned by the authenticated user.
31+
- `runner_id` (Number) The ID of a runner to enable for the project.
32+
33+
### Optional
34+
35+
- `id` (String) The ID of this resource.
36+
37+
## Import
38+
39+
Import is supported using the following syntax:
40+
41+
```shell
42+
# GitLab project runners can be imported using an id made up of `project:runner_id`, e.g.
43+
terraform import gitlab_project_runner_enablement.foo 5:7
44+
```
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# GitLab project runners can be imported using an id made up of `project:runner_id`, e.g.
2+
terraform import gitlab_project_runner_enablement.foo 5:7
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
resource "gitlab_project_runner_enablement" "foo" {
2+
project = 5
3+
runner_id = 7
4+
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package provider
2+
3+
import (
4+
"context"
5+
"log"
6+
"strconv"
7+
8+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
10+
gitlab "github.com/xanzy/go-gitlab"
11+
)
12+
13+
var _ = registerResource("gitlab_project_runner_enablement", func() *schema.Resource {
14+
return &schema.Resource{
15+
Description: `The ` + "`gitlab_project_runner_enablement`" + ` resource allows to enable a runner in a project.
16+
17+
**Upstream API**: [GitLab REST API docs](https://docs.gitlab.com/ee/api/runners.html#enable-a-runner-in-project)`,
18+
CreateContext: resourceGitlabProjectRunnerEnablementCreate,
19+
ReadContext: resourceGitlabProjectRunnerEnablementRead,
20+
DeleteContext: resourceGitlabProjectRunnerEnablementDelete,
21+
Importer: &schema.ResourceImporter{
22+
StateContext: schema.ImportStatePassthroughContext,
23+
},
24+
25+
Schema: map[string]*schema.Schema{
26+
"project": {
27+
Description: "The ID or URL-encoded path of the project owned by the authenticated user.",
28+
Type: schema.TypeString,
29+
ForceNew: true,
30+
Required: true,
31+
},
32+
"runner_id": {
33+
Description: "The ID of a runner to enable for the project.",
34+
Type: schema.TypeInt,
35+
ForceNew: true,
36+
Required: true,
37+
},
38+
},
39+
}
40+
})
41+
42+
func resourceGitlabProjectRunnerEnablementCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
43+
client := meta.(*gitlab.Client)
44+
projectID := d.Get("project").(string)
45+
runnerID := d.Get("runner_id").(int)
46+
options := &gitlab.EnableProjectRunnerOptions{
47+
RunnerID: runnerID,
48+
}
49+
50+
log.Printf("[DEBUG] create gitlab project runner %v/%v", projectID, runnerID)
51+
52+
_, _, err := client.Runners.EnableProjectRunner(projectID, options, gitlab.WithContext(ctx))
53+
if err != nil {
54+
return diag.FromErr(err)
55+
}
56+
57+
runnerIDString := strconv.Itoa(runnerID)
58+
d.SetId(buildTwoPartID(&projectID, &runnerIDString))
59+
60+
return resourceGitlabProjectRunnerEnablementRead(ctx, d, meta)
61+
}
62+
63+
func resourceGitlabProjectRunnerEnablementRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
64+
client := meta.(*gitlab.Client)
65+
project, runnerID, err := projectAndRunnerFromID(d.Id())
66+
if err != nil {
67+
return diag.FromErr(err)
68+
}
69+
70+
log.Printf("[DEBUG] read gitlab project runner %s/%v", project, runnerID)
71+
72+
// Get the project id from `project`, which can be either the numeric ID or a name
73+
projectDetails, _, err := client.Projects.GetProject(project, &gitlab.GetProjectOptions{}, gitlab.WithContext(ctx))
74+
if err != nil {
75+
return diag.FromErr(err)
76+
}
77+
78+
runnerdetails, _, err := client.Runners.GetRunnerDetails(runnerID, gitlab.WithContext(ctx))
79+
if err != nil {
80+
return diag.FromErr(err)
81+
}
82+
83+
// Check if the project exists in the runner details
84+
found := false
85+
for _, p := range runnerdetails.Projects {
86+
if p.ID == projectDetails.ID {
87+
found = true
88+
break
89+
}
90+
}
91+
92+
if !found {
93+
log.Printf("[WARN] removing project runner: %v from state because it no longer exists in gitlab", runnerID)
94+
d.SetId("")
95+
return nil
96+
}
97+
98+
d.Set("project", project)
99+
d.Set("runner_id", runnerID)
100+
101+
return nil
102+
}
103+
104+
func projectAndRunnerFromID(id string) (string, int, error) {
105+
var runnerID int
106+
projectID, runnerIDString, err := parseTwoPartID(id)
107+
if err != nil {
108+
log.Printf("[WARN] could not get project and runner ids from resource id %v", id)
109+
return projectID, runnerID, err
110+
}
111+
112+
runnerID, err = strconv.Atoi(runnerIDString)
113+
if err != nil {
114+
log.Printf("[WARN] could not convert runner id '%s' to integer", runnerIDString)
115+
return projectID, runnerID, err
116+
}
117+
return projectID, runnerID, nil
118+
119+
}
120+
121+
func resourceGitlabProjectRunnerEnablementDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
122+
client := meta.(*gitlab.Client)
123+
124+
projectID, runnerID, err := projectAndRunnerFromID(d.Id())
125+
if err != nil {
126+
return diag.FromErr(err)
127+
}
128+
129+
log.Printf("[DEBUG] Delete gitlab project runner %s/%v", projectID, runnerID)
130+
131+
_, err = client.Runners.DisableProjectRunner(projectID, runnerID)
132+
if err != nil {
133+
return diag.FromErr(err)
134+
}
135+
136+
return nil
137+
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package provider
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"testing"
7+
8+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
10+
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
11+
gitlab "github.com/xanzy/go-gitlab"
12+
)
13+
14+
func TestAccGitlabProjectRunnerEnablement_basic(t *testing.T) {
15+
testAccCheck(t)
16+
testGroup := testAccCreateGroups(t, 1)[0]
17+
projectA := testAccCreateProjectWithNamespace(t, testGroup.ID)
18+
projectB := testAccCreateProjectWithNamespace(t, testGroup.ID)
19+
projectC := testAccCreateProjectWithNamespace(t, testGroup.ID)
20+
21+
name := fmt.Sprintf("TestAcc Runner %s", acctest.RandString(10))
22+
23+
opts := gitlab.RegisterNewRunnerOptions{
24+
Token: &projectA.RunnersToken,
25+
Description: gitlab.String(name),
26+
}
27+
28+
// Create runner in project A
29+
runner, _, _ := testGitlabClient.Runners.RegisterNewRunner(&opts)
30+
31+
resource.Test(t, resource.TestCase{
32+
PreCheck: func() { testAccPreCheck(t) },
33+
ProviderFactories: providerFactories,
34+
CheckDestroy: testAccCheckGitlabProjectRunnerEnablementDestroy(projectB.ID, runner.ID),
35+
Steps: []resource.TestStep{
36+
// Enable it in projectB
37+
{
38+
Config: fmt.Sprintf(`
39+
resource "gitlab_project_runner_enablement" "foo" {
40+
project = %d
41+
runner_id = %d
42+
}`, projectB.ID, runner.ID),
43+
Check: resource.ComposeTestCheckFunc(
44+
resource.TestCheckResourceAttr("gitlab_project_runner_enablement.foo", "project", fmt.Sprint(projectB.ID)),
45+
resource.TestCheckResourceAttr("gitlab_project_runner_enablement.foo", "runner_id", fmt.Sprint(runner.ID)),
46+
resource.TestCheckResourceAttr("gitlab_project_runner_enablement.foo", "id", fmt.Sprintf("%d:%d", projectB.ID, runner.ID)),
47+
// The runner is enabled in project B
48+
testAccCheckGitlabProjectRunnerEnablementCreate(projectB.ID, runner.ID),
49+
),
50+
},
51+
// Verify foo resource with an import.
52+
{
53+
ResourceName: "gitlab_project_runner_enablement.foo",
54+
ImportState: true,
55+
ImportStateVerify: true,
56+
},
57+
// Enable it in projectC
58+
{
59+
Config: fmt.Sprintf(`
60+
resource "gitlab_project_runner_enablement" "foo" {
61+
project = %d
62+
runner_id = %d
63+
}`, projectC.ID, runner.ID),
64+
Check: resource.ComposeTestCheckFunc(
65+
resource.TestCheckResourceAttr("gitlab_project_runner_enablement.foo", "project", fmt.Sprint(projectC.ID)),
66+
resource.TestCheckResourceAttr("gitlab_project_runner_enablement.foo", "runner_id", fmt.Sprint(runner.ID)),
67+
resource.TestCheckResourceAttr("gitlab_project_runner_enablement.foo", "id", fmt.Sprintf("%d:%d", projectC.ID, runner.ID)),
68+
testAccCheckGitlabProjectRunnerEnablementCreate(projectC.ID, runner.ID),
69+
// The runner is no longer enabled on B
70+
testAccCheckGitlabProjectRunnerEnablementDestroy(projectB.ID, runner.ID),
71+
),
72+
},
73+
// Verify foo resource with an import.
74+
{
75+
ResourceName: "gitlab_project_runner_enablement.foo",
76+
ImportState: true,
77+
ImportStateVerify: true,
78+
},
79+
// Enable it in both projects
80+
{
81+
Config: fmt.Sprintf(`
82+
resource "gitlab_project_runner_enablement" "foo" {
83+
project = %d
84+
runner_id = %d
85+
}
86+
87+
resource "gitlab_project_runner_enablement" "bar" {
88+
project = %d
89+
runner_id = %d
90+
}`, projectC.ID, runner.ID, projectB.ID, runner.ID),
91+
Check: resource.ComposeTestCheckFunc(
92+
resource.TestCheckResourceAttr("gitlab_project_runner_enablement.foo", "project", fmt.Sprint(projectC.ID)),
93+
resource.TestCheckResourceAttr("gitlab_project_runner_enablement.foo", "runner_id", fmt.Sprint(runner.ID)),
94+
resource.TestCheckResourceAttr("gitlab_project_runner_enablement.foo", "id", fmt.Sprintf("%d:%d", projectC.ID, runner.ID)),
95+
resource.TestCheckResourceAttr("gitlab_project_runner_enablement.bar", "project", fmt.Sprint(projectB.ID)),
96+
resource.TestCheckResourceAttr("gitlab_project_runner_enablement.bar", "runner_id", fmt.Sprint(runner.ID)),
97+
resource.TestCheckResourceAttr("gitlab_project_runner_enablement.bar", "id", fmt.Sprintf("%d:%d", projectB.ID, runner.ID)),
98+
testAccCheckGitlabProjectRunnerEnablementCreate(projectB.ID, runner.ID),
99+
testAccCheckGitlabProjectRunnerEnablementCreate(projectC.ID, runner.ID),
100+
),
101+
},
102+
// Verify bar resource with an import.
103+
{
104+
ResourceName: "gitlab_project_runner_enablement.bar",
105+
ImportState: true,
106+
ImportStateVerify: true,
107+
},
108+
},
109+
})
110+
}
111+
112+
func testAccCheckGitlabProjectRunnerEnablementCreate(pid int, rid int) resource.TestCheckFunc {
113+
return func(_ *terraform.State) error {
114+
runnerdetails, _, err := testGitlabClient.Runners.GetRunnerDetails(rid)
115+
if err != nil {
116+
return err
117+
}
118+
119+
for _, p := range runnerdetails.Projects {
120+
if p.ID == pid {
121+
// The runner is enabled in the project - no error
122+
return nil
123+
}
124+
}
125+
126+
return errors.New("Runner is not enabled in the project")
127+
}
128+
}
129+
130+
func testAccCheckGitlabProjectRunnerEnablementDestroy(pid int, rid int) resource.TestCheckFunc {
131+
return func(s *terraform.State) error {
132+
testCreate := testAccCheckGitlabProjectRunnerEnablementCreate(pid, rid)
133+
err := testCreate(s)
134+
if err.Error() != "Runner is not enabled in the project" {
135+
return err
136+
}
137+
return nil
138+
}
139+
}

0 commit comments

Comments
 (0)