Skip to content

Commit cd05af8

Browse files
committed
Initial push of tested gitlab_runner resource.
1 parent fd03393 commit cd05af8

File tree

6 files changed

+485
-0
lines changed

6 files changed

+485
-0
lines changed

docs/resources/runner.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "gitlab_runner Resource - terraform-provider-gitlab"
4+
subcategory: ""
5+
description: |-
6+
The gitlab_runner resource allows registering a runner, either at an instance level
7+
or at a group level. The runner will be registered at a group level if the token used is from a group, or at an
8+
instance level if the token used is for the instance.
9+
Upstream API: GitLab REST API docs https://docs.gitlab.com/ee/api/runners.html#register-a-new-runner
10+
---
11+
12+
# gitlab_runner (Resource)
13+
14+
The `gitlab_runner` resource allows registering a runner, either at an instance level
15+
or at a group level. The runner will be registered at a group level if the token used is from a group, or at an
16+
instance level if the token used is for the instance.
17+
18+
**Upstream API**: [GitLab REST API docs](https://docs.gitlab.com/ee/api/runners.html#register-a-new-runner)
19+
20+
## Example Usage
21+
22+
```terraform
23+
# Basic GitLab Runner
24+
resource "gitlab_runner" "this" {
25+
token = "12345"
26+
}
27+
28+
# GitLab Runner that runs only tagged jobs
29+
resource "gitlab_runner" "tagged_only" {
30+
token = "12345"
31+
description = "I only run tagged jobs"
32+
33+
run_untagged = "false"
34+
tag_list = ["tag_one", "tag_two"]
35+
}
36+
37+
# GitLab Runner that only runs on protected branches
38+
resource "gitlab_runner" "protected" {
39+
token = "12345"
40+
description = "I only run protected jobs"
41+
42+
access_level = "ref_protected"
43+
}
44+
```
45+
46+
<!-- schema generated by tfplugindocs -->
47+
## Schema
48+
49+
### Required
50+
51+
- `token` (String, Sensitive) The registration token used to register the runner
52+
53+
### Optional
54+
55+
- `access_level` (String) The access_level of the runner. Valid values are: `not_protected`, `ref_protected`
56+
- `description` (String) The runner's description'
57+
- `id` (String) The ID of this resource.
58+
- `locked` (Boolean) Whether the runner should be locked for current project
59+
- `maximum_timeout` (Number) Maximum timeout set when this runner handles the job
60+
- `paused` (Boolean) Whether the runner should ignore new jobs
61+
- `run_untagged` (Boolean) Whether the runner should handle untagged jobs
62+
- `tag_list` (List of String) List of runner’s tags
63+
64+
### Read-Only
65+
66+
- `authentication_token` (String, Sensitive) The authentication token used for building a config.toml file. This value is not present when imported.
67+
- `status` (String) The status of runners to show, one of: online and offline. active and paused are also possible values
68+
which were deprecated in GitLab 14.8 and will be removed in GitLab 16.0
69+
70+
## Import
71+
72+
Import is supported using the following syntax:
73+
74+
```shell
75+
# A GitLab Runner can be imported using the runner's ID, eg
76+
terraform import gitlab_runner.this 1
77+
```
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# A GitLab Runner can be imported using the runner's ID, eg
2+
terraform import gitlab_runner.this 1
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Basic GitLab Runner
2+
resource "gitlab_runner" "this" {
3+
token = "12345"
4+
}
5+
6+
# GitLab Runner that runs only tagged jobs
7+
resource "gitlab_runner" "tagged_only" {
8+
token = "12345"
9+
description = "I only run tagged jobs"
10+
11+
run_untagged = "false"
12+
tag_list = ["tag_one", "tag_two"]
13+
}
14+
15+
# GitLab Runner that only runs on protected branches
16+
resource "gitlab_runner" "protected" {
17+
token = "12345"
18+
description = "I only run protected jobs"
19+
20+
access_level = "ref_protected"
21+
}
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
package provider
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
"strconv"
8+
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
10+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
11+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
12+
"github.com/xanzy/go-gitlab"
13+
)
14+
15+
var _ = registerResource("gitlab_runner", func() *schema.Resource {
16+
return &schema.Resource{
17+
Description: `The ` + "`gitlab_runner`" + ` resource allows registering a runner, either at an instance level
18+
or at a group level. The runner will be registered at a group level if the token used is from a group, or at an
19+
instance level if the token used is for the instance.
20+
21+
**Upstream API**: [GitLab REST API docs](https://docs.gitlab.com/ee/api/runners.html#register-a-new-runner)`,
22+
23+
// Since this resource updates an in-place resource, the update method is the same as the create method
24+
CreateContext: resourceGitLabRunnerCreate,
25+
UpdateContext: resourceGitLabRunnerUpdate,
26+
ReadContext: resourceGitLabRunnerRead,
27+
DeleteContext: resourceGitLabRunnerDelete,
28+
Importer: &schema.ResourceImporter{
29+
StateContext: schema.ImportStatePassthroughContext,
30+
},
31+
32+
Schema: map[string]*schema.Schema{
33+
"token": {
34+
Description: `The registration token used to register the runner`,
35+
Type: schema.TypeString,
36+
ForceNew: true,
37+
Required: true,
38+
Sensitive: true,
39+
},
40+
// Even though the output variable is just called 'token', we need to differentiate between the regitration
41+
// and authentication token in terraform.
42+
"authentication_token": {
43+
Description: `The authentication token used for building a config.toml file. This value is not present when imported.`,
44+
Type: schema.TypeString,
45+
Computed: true,
46+
Sensitive: true,
47+
},
48+
"status": {
49+
Description: `The status of runners to show, one of: online and offline. active and paused are also possible values
50+
which were deprecated in GitLab 14.8 and will be removed in GitLab 16.0`,
51+
Type: schema.TypeString,
52+
Computed: true,
53+
},
54+
"description": {
55+
Description: `The runner's description'`,
56+
Type: schema.TypeString,
57+
Optional: true,
58+
},
59+
"paused": {
60+
Description: `Whether the runner should ignore new jobs`,
61+
Type: schema.TypeBool,
62+
Optional: true,
63+
Default: gitlab.Bool(false),
64+
},
65+
"locked": {
66+
Description: `Whether the runner should be locked for current project`,
67+
Type: schema.TypeBool,
68+
Optional: true,
69+
Default: gitlab.Bool(false),
70+
},
71+
"run_untagged": {
72+
Description: `Whether the runner should handle untagged jobs`,
73+
Type: schema.TypeBool,
74+
Optional: true,
75+
Default: gitlab.Bool(true),
76+
},
77+
"tag_list": {
78+
Description: `List of runner’s tags`,
79+
Type: schema.TypeList,
80+
Optional: true,
81+
Elem: &schema.Schema{Type: schema.TypeString},
82+
},
83+
"access_level": {
84+
Description: fmt.Sprintf(`The access_level of the runner. Valid values are: %s`,
85+
renderValueListForDocs(runnerAccessLevelAllowedValues)),
86+
Type: schema.TypeString,
87+
Optional: true,
88+
ValidateFunc: validation.StringInSlice(runnerAccessLevelAllowedValues, true),
89+
Default: "not_protected",
90+
},
91+
"maximum_timeout": {
92+
Description: `Maximum timeout set when this runner handles the job`,
93+
Type: schema.TypeInt,
94+
Optional: true,
95+
},
96+
// While Maintenance Note is available during "create", it is not available during "update", so
97+
// excluding it here for now.
98+
},
99+
}
100+
})
101+
102+
func resourceGitLabRunnerDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
103+
client := meta.(*gitlab.Client)
104+
runnerId, err := strconv.Atoi(d.Id())
105+
if err != nil {
106+
return diag.FromErr(err)
107+
}
108+
109+
log.Printf("[DEBUG] Delete GitLab Runner %s", d.Id())
110+
_, err = client.Runners.DeleteRegisteredRunnerByID(runnerId, gitlab.WithContext(ctx))
111+
if err != nil {
112+
return diag.FromErr(err)
113+
}
114+
115+
return nil
116+
}
117+
118+
func resourceGitLabRunnerRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
119+
client := meta.(*gitlab.Client)
120+
runnerId, err := strconv.Atoi(d.Id())
121+
if err != nil {
122+
return diag.FromErr(err)
123+
}
124+
125+
runner, _, err := client.Runners.GetRunnerDetails(runnerId, gitlab.WithContext(ctx))
126+
if err != nil {
127+
return diag.FromErr(err)
128+
}
129+
130+
d.SetId(strconv.Itoa(runner.ID))
131+
d.Set("description", runner.Description)
132+
d.Set("paused", runner.Paused)
133+
d.Set("locked", runner.Locked)
134+
d.Set("run_untagged", runner.RunUntagged)
135+
d.Set("access_level", runner.AccessLevel)
136+
d.Set("maximum_timeout", runner.MaximumTimeout)
137+
d.Set("status", runner.Status)
138+
139+
if err := d.Set("tag_list", runner.TagList); err != nil {
140+
return diag.FromErr(fmt.Errorf("[DEBUG] error setting tag list for runner: %s", err))
141+
}
142+
143+
return nil
144+
}
145+
146+
func resourceGitLabRunnerUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
147+
client := meta.(*gitlab.Client)
148+
runnerId := d.Id()
149+
150+
options := &gitlab.UpdateRunnerDetailsOptions{}
151+
if v, ok := d.GetOk("description"); ok {
152+
options.Description = gitlab.String(v.(string))
153+
}
154+
155+
//GetOK skips the block if the value is "false", so need to use GetOkExists even though it's deprecated.
156+
// nolint:staticcheck // SA1019 ignore deprecated GetOkExists
157+
// lintignore: XR001 // TODO: replace with alternative for GetOkExists
158+
if v, ok := d.GetOkExists("paused"); ok {
159+
options.Paused = gitlab.Bool(v.(bool))
160+
}
161+
162+
// nolint:staticcheck // SA1019 ignore deprecated GetOkExists
163+
// lintignore: XR001 // TODO: replace with alternative for GetOkExists
164+
if v, ok := d.GetOkExists("locked"); ok {
165+
options.Locked = gitlab.Bool(v.(bool))
166+
}
167+
168+
// nolint:staticcheck // SA1019 ignore deprecated GetOkExists
169+
// lintignore: XR001 // TODO: replace with alternative for GetOkExists
170+
if v, ok := d.GetOkExists("run_untagged"); ok {
171+
options.RunUntagged = gitlab.Bool(v.(bool))
172+
}
173+
174+
if v, ok := d.GetOk("tag_list"); ok {
175+
options.TagList = stringListToStringSlice(v.([]interface{}))
176+
}
177+
178+
if v, ok := d.GetOk("access_level"); ok {
179+
options.AccessLevel = gitlab.String(v.(string))
180+
}
181+
182+
if v, ok := d.GetOk("maximum_timeout"); ok {
183+
options.MaximumTimeout = gitlab.Int(v.(int))
184+
}
185+
186+
log.Printf("[DEBUG] Update GitLab Runner %s", d.Id())
187+
_, _, err := client.Runners.UpdateRunnerDetails(runnerId, options, gitlab.WithContext(ctx))
188+
if err != nil {
189+
return diag.FromErr(err)
190+
}
191+
192+
return resourceGitLabRunnerRead(ctx, d, meta)
193+
194+
}
195+
196+
func resourceGitLabRunnerCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
197+
client := meta.(*gitlab.Client)
198+
199+
options := &gitlab.RegisterNewRunnerOptions{
200+
Token: gitlab.String(d.Get("token").(string)),
201+
}
202+
203+
if v, ok := d.GetOk("description"); ok {
204+
options.Description = gitlab.String(v.(string))
205+
}
206+
207+
//GetOK skips the block if the value is "false", so need to use GetOkExists even though it's deprecated.
208+
// nolint:staticcheck // SA1019 ignore deprecated GetOkExists
209+
// lintignore: XR001 // TODO: replace with alternative for GetOkExists
210+
if v, ok := d.GetOkExists("paused"); ok {
211+
options.Paused = gitlab.Bool(v.(bool))
212+
}
213+
214+
// nolint:staticcheck // SA1019 ignore deprecated GetOkExist
215+
// lintignore: XR001 // TODO: replace with alternative for GetOkExists
216+
if v, ok := d.GetOkExists("locked"); ok {
217+
options.Locked = gitlab.Bool(v.(bool))
218+
}
219+
220+
// nolint:staticcheck // SA1019 ignore deprecated GetOkExists
221+
// lintignore: XR001 // TODO: replace with alternative for GetOkExists
222+
if v, ok := d.GetOkExists("run_untagged"); ok {
223+
options.RunUntagged = gitlab.Bool(v.(bool))
224+
}
225+
226+
if v, ok := d.GetOk("tag_list"); ok {
227+
options.TagList = stringListToStringSlice(v.([]interface{}))
228+
}
229+
230+
if v, ok := d.GetOk("access_level"); ok {
231+
options.AccessLevel = gitlab.String(v.(string))
232+
}
233+
234+
if v, ok := d.GetOk("maximum_timeout"); ok {
235+
options.MaximumTimeout = gitlab.Int(v.(int))
236+
}
237+
238+
// Explicitly not printing the registration token here, even though it may make debugging a bit trickier, since it's a
239+
// secret.
240+
log.Printf("[DEBUG] Update GitLab Runner using registration token in configuration")
241+
runner, _, err := client.Runners.RegisterNewRunner(options, gitlab.WithContext(ctx))
242+
if err != nil {
243+
return diag.FromErr(err)
244+
}
245+
246+
d.SetId(strconv.Itoa(runner.ID))
247+
248+
//The authentication_token will ONLY exist during creation, and will not return during "read", so we need to set it here.
249+
d.Set("authentication_token", runner.Token)
250+
251+
return resourceGitLabRunnerRead(ctx, d, meta)
252+
}
253+
254+
var runnerAccessLevelAllowedValues = []string{
255+
"not_protected",
256+
"ref_protected",
257+
}

0 commit comments

Comments
 (0)