Skip to content

Commit 1d447c9

Browse files
authored
feat: support container image datasource (#30)
1 parent 8325157 commit 1d447c9

File tree

6 files changed

+395
-0
lines changed

6 files changed

+395
-0
lines changed

docs/data-sources/container_image.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "ec_container_image Data Source - terraform-provider-ec"
4+
subcategory: ""
5+
description: |-
6+
Use this data source to access information about an existing Image.
7+
---
8+
9+
# ec_container_image (Data Source)
10+
11+
Use this data source to access information about an existing Image.
12+
13+
14+
15+
<!-- schema generated by tfplugindocs -->
16+
## Schema
17+
18+
### Optional
19+
20+
- `instance` (String) Name is an instance name configured in the provider.
21+
- `metadata` (Block List, Max: 1) Image object metadata. (see [below for nested schema](#nestedblock--metadata))
22+
- `spec` (Block List, Max: 1) Spec defines the desired image. (see [below for nested schema](#nestedblock--spec))
23+
24+
### Read-Only
25+
26+
- `id` (String) The ID of this resource.
27+
28+
<a id="nestedblock--metadata"></a>
29+
### Nested Schema for `metadata`
30+
31+
Required:
32+
33+
- `branch` (String) Branch defines the branch within which each name must be unique.
34+
35+
Optional:
36+
37+
- `object_meta` (Block List, Max: 1) (see [below for nested schema](#nestedblock--metadata--object_meta))
38+
39+
<a id="nestedblock--metadata--object_meta"></a>
40+
### Nested Schema for `metadata.object_meta`
41+
42+
Optional:
43+
44+
- `annotations` (Map of String) An unstructured map of keys and values stored on an object.
45+
- `environment` (String) The name of the environment the object belongs to.
46+
- `labels` (Map of String) A map of keys and values that can be used to organize and categorize objects.
47+
- `name` (String) The unique object name within its scope.
48+
49+
Read-Only:
50+
51+
- `revision` (String) An opaque resource revision.
52+
- `uid` (String) A unique identifier for each an object.
53+
54+
55+
56+
<a id="nestedblock--spec"></a>
57+
### Nested Schema for `spec`
58+
59+
Required:
60+
61+
- `image` (String) Image is the image name.
62+
63+
Optional:
64+
65+
- `tag` (String) Tag is the image tag.
66+
67+
Read-Only:
68+
69+
- `digest` (String) Digest is the hash of the image.
70+
- `registry` (String) Registry is the registry that contains the image.
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "ec_container_image_v1 Data Source - terraform-provider-ec"
4+
subcategory: ""
5+
description: |-
6+
Use this data source to access information about an existing Image.
7+
---
8+
9+
# ec_container_image_v1 (Data Source)
10+
11+
Use this data source to access information about an existing Image.
12+
13+
14+
15+
<!-- schema generated by tfplugindocs -->
16+
## Schema
17+
18+
### Optional
19+
20+
- `instance` (String) Name is an instance name configured in the provider.
21+
- `metadata` (Block List, Max: 1) Image object metadata. (see [below for nested schema](#nestedblock--metadata))
22+
- `spec` (Block List, Max: 1) Spec defines the desired image. (see [below for nested schema](#nestedblock--spec))
23+
24+
### Read-Only
25+
26+
- `id` (String) The ID of this resource.
27+
28+
<a id="nestedblock--metadata"></a>
29+
### Nested Schema for `metadata`
30+
31+
Required:
32+
33+
- `branch` (String) Branch defines the branch within which each name must be unique.
34+
35+
Optional:
36+
37+
- `object_meta` (Block List, Max: 1) (see [below for nested schema](#nestedblock--metadata--object_meta))
38+
39+
<a id="nestedblock--metadata--object_meta"></a>
40+
### Nested Schema for `metadata.object_meta`
41+
42+
Optional:
43+
44+
- `annotations` (Map of String) An unstructured map of keys and values stored on an object.
45+
- `environment` (String) The name of the environment the object belongs to.
46+
- `labels` (Map of String) A map of keys and values that can be used to organize and categorize objects.
47+
- `name` (String) The unique object name within its scope.
48+
49+
Read-Only:
50+
51+
- `revision` (String) An opaque resource revision.
52+
- `uid` (String) A unique identifier for each an object.
53+
54+
55+
56+
<a id="nestedblock--spec"></a>
57+
### Nested Schema for `spec`
58+
59+
Required:
60+
61+
- `image` (String) Image is the image name.
62+
63+
Optional:
64+
65+
- `tag` (String) Tag is the image tag.
66+
67+
Read-Only:
68+
69+
- `digest` (String) Digest is the hash of the image.
70+
- `registry` (String) Registry is the registry that contains the image.

ec/container/data_source_image.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package container
2+
3+
import (
4+
"context"
5+
"errors"
6+
"slices"
7+
8+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
10+
"github.com/nitrado/terraform-provider-ec/ec"
11+
"github.com/nitrado/terraform-provider-ec/pkg/resource"
12+
apierrors "gitlab.com/nitrado/b2b/ec/apicore/api/errors"
13+
metav1 "gitlab.com/nitrado/b2b/ec/apicore/apis/meta/v1"
14+
containerv1 "gitlab.com/nitrado/b2b/ec/core/pkg/api/container/v1"
15+
)
16+
17+
// DataSourceImage returns the data source resource for an image.
18+
func DataSourceImage() *schema.Resource {
19+
return &schema.Resource{
20+
Description: "Use this data source to access information about an existing Image.",
21+
ReadContext: dataSourceImageRead,
22+
Schema: imageSchema(),
23+
}
24+
}
25+
26+
func dataSourceImageRead(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
27+
inst, _ := d.Get("instance").(string)
28+
clientSet, err := ec.ResolveClientSet(m, inst)
29+
if err != nil {
30+
return diag.FromErr(err)
31+
}
32+
33+
branch := d.Get("metadata.0.branch").(string)
34+
name, hasName := d.GetOk("metadata.0.object_meta.0.name")
35+
image, hasImage := d.GetOk("spec.0.image")
36+
tag, hasTag := d.GetOk("spec.0.tag")
37+
38+
var obj *containerv1.Image
39+
switch {
40+
case hasName:
41+
obj, err = clientSet.ContainerV1().Images(branch).Get(ctx, name.(string), metav1.GetOptions{})
42+
if err != nil {
43+
switch {
44+
case apierrors.IsNotFound(err):
45+
d.SetId("")
46+
return nil
47+
default:
48+
return diag.FromErr(err)
49+
}
50+
}
51+
case hasImage:
52+
fieldSelector := map[string]string{
53+
"spec.image": image.(string),
54+
}
55+
if hasTag {
56+
fieldSelector["spec.tag"] = tag.(string)
57+
}
58+
list, err := clientSet.ContainerV1().Images(branch).List(ctx, metav1.ListOptions{FieldSelector: fieldSelector})
59+
if err != nil {
60+
return diag.FromErr(err)
61+
}
62+
if len(list.Items) == 0 {
63+
d.SetId("")
64+
return nil
65+
}
66+
latestImg := slices.MaxFunc(list.Items, func(a, b containerv1.Image) int {
67+
switch {
68+
case a.CreatedTimestamp.Before(b.CreatedTimestamp):
69+
return -1
70+
case a.CreatedTimestamp.Equal(b.CreatedTimestamp):
71+
return 0
72+
}
73+
return 1
74+
})
75+
obj = &latestImg
76+
default:
77+
return diag.FromErr(errors.New("either metadata.0.name or spec.0.image is required"))
78+
}
79+
80+
d.SetId(obj.Branch + "/" + obj.Name)
81+
82+
data, err := ec.Converter().Flatten(obj, imageSchema())
83+
if err != nil {
84+
return diag.FromErr(err)
85+
}
86+
87+
if err = resource.SetData(d, data); err != nil {
88+
return diag.FromErr(err)
89+
}
90+
return nil
91+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package container_test
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
8+
"github.com/nitrado/terraform-provider-ec/ec/provider/providertest"
9+
metav1 "gitlab.com/nitrado/b2b/ec/apicore/apis/meta/v1"
10+
containerv1 "gitlab.com/nitrado/b2b/ec/core/pkg/api/container/v1"
11+
)
12+
13+
func TestDataSourceImages(t *testing.T) {
14+
img1 := &containerv1.Image{
15+
ImageObjectMeta: containerv1.ImageObjectMeta{
16+
ObjectMeta: metav1.ObjectMeta{
17+
Name: "my-image-1",
18+
CreatedTimestamp: time.Now().Add(-1 * time.Hour),
19+
},
20+
Branch: "my-branch",
21+
},
22+
Spec: containerv1.ImageSpec{
23+
Image: "my-image-name",
24+
Tag: "my-tag1",
25+
},
26+
}
27+
img2 := &containerv1.Image{
28+
ImageObjectMeta: containerv1.ImageObjectMeta{
29+
ObjectMeta: metav1.ObjectMeta{
30+
Name: "my-image-2",
31+
CreatedTimestamp: time.Now(),
32+
},
33+
Branch: "my-branch",
34+
},
35+
Spec: containerv1.ImageSpec{
36+
Image: "my-image-name",
37+
Tag: "my-tag2",
38+
},
39+
}
40+
41+
pf, _ := providertest.SetupProviderFactories(t, img1, img2)
42+
43+
resource.Test(t, resource.TestCase{
44+
IsUnitTest: true,
45+
ProviderFactories: pf,
46+
Steps: []resource.TestStep{
47+
{
48+
Config: testDataSourceImageNameConfigRead(),
49+
Check: resource.ComposeAggregateTestCheckFunc(
50+
resource.TestCheckResourceAttr("data.ec_container_image.by_name", "metadata.0.object_meta.0.name", "my-image-1"),
51+
resource.TestCheckResourceAttr("data.ec_container_image.by_name", "metadata.0.branch", "my-branch"),
52+
resource.TestCheckResourceAttr("data.ec_container_image.by_name", "spec.#", "1"),
53+
resource.TestCheckResourceAttr("data.ec_container_image.by_name", "spec.0.image", "my-image-name"),
54+
resource.TestCheckResourceAttr("data.ec_container_image.by_name", "spec.0.tag", "my-tag1"),
55+
),
56+
},
57+
{
58+
Config: testDataSourceImageSpecConfigRead(),
59+
Check: resource.ComposeAggregateTestCheckFunc(
60+
resource.TestCheckResourceAttr("data.ec_container_image.by_image", "metadata.0.object_meta.0.name", "my-image-2"),
61+
resource.TestCheckResourceAttr("data.ec_container_image.by_image", "metadata.0.branch", "my-branch"),
62+
resource.TestCheckResourceAttr("data.ec_container_image.by_image", "spec.#", "1"),
63+
resource.TestCheckResourceAttr("data.ec_container_image.by_image", "spec.0.image", "my-image-name"),
64+
resource.TestCheckResourceAttr("data.ec_container_image.by_image", "spec.0.tag", "my-tag2"),
65+
),
66+
},
67+
},
68+
})
69+
}
70+
71+
func testDataSourceImageNameConfigRead() string {
72+
return `data "ec_container_image" "by_name" {
73+
metadata {
74+
object_meta {
75+
name = "my-image-1"
76+
}
77+
branch = "my-branch"
78+
}
79+
}
80+
`
81+
}
82+
83+
func testDataSourceImageSpecConfigRead() string {
84+
return `data "ec_container_image" "by_image" {
85+
metadata {
86+
branch = "my-branch"
87+
}
88+
spec {
89+
image = "my-image-name"
90+
}
91+
}
92+
`
93+
}

ec/container/schema_image.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package container
2+
3+
import (
4+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
5+
"github.com/nitrado/terraform-provider-ec/ec/meta"
6+
)
7+
8+
// imageSchema is manually created, due to specific requirements.
9+
func imageSchema() map[string]*schema.Schema {
10+
return map[string]*schema.Schema{
11+
"instance": {
12+
Type: schema.TypeString,
13+
Description: "Name is an instance name configured in the provider.",
14+
Optional: true,
15+
},
16+
"metadata": {
17+
Type: schema.TypeList,
18+
Description: "Image object metadata.",
19+
Optional: true,
20+
MaxItems: 1,
21+
Elem: &schema.Resource{
22+
Schema: map[string]*schema.Schema{
23+
"branch": {
24+
Type: schema.TypeString,
25+
Description: "Branch defines the branch within which each name must be unique.",
26+
Required: true,
27+
},
28+
"object_meta": {
29+
Type: schema.TypeList,
30+
Optional: true,
31+
MaxItems: 1,
32+
Elem: &schema.Resource{Schema: meta.Schema()},
33+
},
34+
},
35+
},
36+
},
37+
"spec": {
38+
Type: schema.TypeList,
39+
Description: "Spec defines the desired image.",
40+
Optional: true,
41+
MaxItems: 1,
42+
Elem: &schema.Resource{
43+
Schema: map[string]*schema.Schema{
44+
"digest": {
45+
Type: schema.TypeString,
46+
Description: "Digest is the hash of the image.",
47+
Computed: true,
48+
},
49+
"image": {
50+
Type: schema.TypeString,
51+
Description: "Image is the image name.",
52+
Required: true,
53+
},
54+
"registry": {
55+
Type: schema.TypeString,
56+
Description: "Registry is the registry that contains the image.",
57+
Computed: true,
58+
},
59+
"tag": {
60+
Type: schema.TypeString,
61+
Description: "Tag is the image tag.",
62+
Optional: true,
63+
Computed: true,
64+
},
65+
},
66+
},
67+
},
68+
}
69+
}

0 commit comments

Comments
 (0)