Skip to content

Commit

Permalink
feat: support container image datasource (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
nrwiersma authored Jun 21, 2024
1 parent 8325157 commit 1d447c9
Show file tree
Hide file tree
Showing 6 changed files with 395 additions and 0 deletions.
70 changes: 70 additions & 0 deletions docs/data-sources/container_image.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "ec_container_image Data Source - terraform-provider-ec"
subcategory: ""
description: |-
Use this data source to access information about an existing Image.
---

# ec_container_image (Data Source)

Use this data source to access information about an existing Image.



<!-- schema generated by tfplugindocs -->
## Schema

### Optional

- `instance` (String) Name is an instance name configured in the provider.
- `metadata` (Block List, Max: 1) Image object metadata. (see [below for nested schema](#nestedblock--metadata))
- `spec` (Block List, Max: 1) Spec defines the desired image. (see [below for nested schema](#nestedblock--spec))

### Read-Only

- `id` (String) The ID of this resource.

<a id="nestedblock--metadata"></a>
### Nested Schema for `metadata`

Required:

- `branch` (String) Branch defines the branch within which each name must be unique.

Optional:

- `object_meta` (Block List, Max: 1) (see [below for nested schema](#nestedblock--metadata--object_meta))

<a id="nestedblock--metadata--object_meta"></a>
### Nested Schema for `metadata.object_meta`

Optional:

- `annotations` (Map of String) An unstructured map of keys and values stored on an object.
- `environment` (String) The name of the environment the object belongs to.
- `labels` (Map of String) A map of keys and values that can be used to organize and categorize objects.
- `name` (String) The unique object name within its scope.

Read-Only:

- `revision` (String) An opaque resource revision.
- `uid` (String) A unique identifier for each an object.



<a id="nestedblock--spec"></a>
### Nested Schema for `spec`

Required:

- `image` (String) Image is the image name.

Optional:

- `tag` (String) Tag is the image tag.

Read-Only:

- `digest` (String) Digest is the hash of the image.
- `registry` (String) Registry is the registry that contains the image.
70 changes: 70 additions & 0 deletions docs/data-sources/container_image_v1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "ec_container_image_v1 Data Source - terraform-provider-ec"
subcategory: ""
description: |-
Use this data source to access information about an existing Image.
---

# ec_container_image_v1 (Data Source)

Use this data source to access information about an existing Image.



<!-- schema generated by tfplugindocs -->
## Schema

### Optional

- `instance` (String) Name is an instance name configured in the provider.
- `metadata` (Block List, Max: 1) Image object metadata. (see [below for nested schema](#nestedblock--metadata))
- `spec` (Block List, Max: 1) Spec defines the desired image. (see [below for nested schema](#nestedblock--spec))

### Read-Only

- `id` (String) The ID of this resource.

<a id="nestedblock--metadata"></a>
### Nested Schema for `metadata`

Required:

- `branch` (String) Branch defines the branch within which each name must be unique.

Optional:

- `object_meta` (Block List, Max: 1) (see [below for nested schema](#nestedblock--metadata--object_meta))

<a id="nestedblock--metadata--object_meta"></a>
### Nested Schema for `metadata.object_meta`

Optional:

- `annotations` (Map of String) An unstructured map of keys and values stored on an object.
- `environment` (String) The name of the environment the object belongs to.
- `labels` (Map of String) A map of keys and values that can be used to organize and categorize objects.
- `name` (String) The unique object name within its scope.

Read-Only:

- `revision` (String) An opaque resource revision.
- `uid` (String) A unique identifier for each an object.



<a id="nestedblock--spec"></a>
### Nested Schema for `spec`

Required:

- `image` (String) Image is the image name.

Optional:

- `tag` (String) Tag is the image tag.

Read-Only:

- `digest` (String) Digest is the hash of the image.
- `registry` (String) Registry is the registry that contains the image.
91 changes: 91 additions & 0 deletions ec/container/data_source_image.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package container

import (
"context"
"errors"
"slices"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/nitrado/terraform-provider-ec/ec"
"github.com/nitrado/terraform-provider-ec/pkg/resource"
apierrors "gitlab.com/nitrado/b2b/ec/apicore/api/errors"
metav1 "gitlab.com/nitrado/b2b/ec/apicore/apis/meta/v1"
containerv1 "gitlab.com/nitrado/b2b/ec/core/pkg/api/container/v1"
)

// DataSourceImage returns the data source resource for an image.
func DataSourceImage() *schema.Resource {
return &schema.Resource{
Description: "Use this data source to access information about an existing Image.",
ReadContext: dataSourceImageRead,
Schema: imageSchema(),
}
}

func dataSourceImageRead(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
inst, _ := d.Get("instance").(string)
clientSet, err := ec.ResolveClientSet(m, inst)
if err != nil {
return diag.FromErr(err)
}

branch := d.Get("metadata.0.branch").(string)
name, hasName := d.GetOk("metadata.0.object_meta.0.name")
image, hasImage := d.GetOk("spec.0.image")
tag, hasTag := d.GetOk("spec.0.tag")

var obj *containerv1.Image
switch {
case hasName:
obj, err = clientSet.ContainerV1().Images(branch).Get(ctx, name.(string), metav1.GetOptions{})
if err != nil {
switch {
case apierrors.IsNotFound(err):
d.SetId("")
return nil
default:
return diag.FromErr(err)
}
}
case hasImage:
fieldSelector := map[string]string{
"spec.image": image.(string),
}
if hasTag {
fieldSelector["spec.tag"] = tag.(string)
}
list, err := clientSet.ContainerV1().Images(branch).List(ctx, metav1.ListOptions{FieldSelector: fieldSelector})
if err != nil {
return diag.FromErr(err)
}
if len(list.Items) == 0 {
d.SetId("")
return nil
}
latestImg := slices.MaxFunc(list.Items, func(a, b containerv1.Image) int {
switch {
case a.CreatedTimestamp.Before(b.CreatedTimestamp):
return -1
case a.CreatedTimestamp.Equal(b.CreatedTimestamp):
return 0
}
return 1
})
obj = &latestImg
default:
return diag.FromErr(errors.New("either metadata.0.name or spec.0.image is required"))
}

d.SetId(obj.Branch + "/" + obj.Name)

data, err := ec.Converter().Flatten(obj, imageSchema())
if err != nil {
return diag.FromErr(err)
}

if err = resource.SetData(d, data); err != nil {
return diag.FromErr(err)
}
return nil
}
93 changes: 93 additions & 0 deletions ec/container/data_source_image_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package container_test

import (
"testing"
"time"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/nitrado/terraform-provider-ec/ec/provider/providertest"
metav1 "gitlab.com/nitrado/b2b/ec/apicore/apis/meta/v1"
containerv1 "gitlab.com/nitrado/b2b/ec/core/pkg/api/container/v1"
)

func TestDataSourceImages(t *testing.T) {
img1 := &containerv1.Image{
ImageObjectMeta: containerv1.ImageObjectMeta{
ObjectMeta: metav1.ObjectMeta{
Name: "my-image-1",
CreatedTimestamp: time.Now().Add(-1 * time.Hour),
},
Branch: "my-branch",
},
Spec: containerv1.ImageSpec{
Image: "my-image-name",
Tag: "my-tag1",
},
}
img2 := &containerv1.Image{
ImageObjectMeta: containerv1.ImageObjectMeta{
ObjectMeta: metav1.ObjectMeta{
Name: "my-image-2",
CreatedTimestamp: time.Now(),
},
Branch: "my-branch",
},
Spec: containerv1.ImageSpec{
Image: "my-image-name",
Tag: "my-tag2",
},
}

pf, _ := providertest.SetupProviderFactories(t, img1, img2)

resource.Test(t, resource.TestCase{
IsUnitTest: true,
ProviderFactories: pf,
Steps: []resource.TestStep{
{
Config: testDataSourceImageNameConfigRead(),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("data.ec_container_image.by_name", "metadata.0.object_meta.0.name", "my-image-1"),
resource.TestCheckResourceAttr("data.ec_container_image.by_name", "metadata.0.branch", "my-branch"),
resource.TestCheckResourceAttr("data.ec_container_image.by_name", "spec.#", "1"),
resource.TestCheckResourceAttr("data.ec_container_image.by_name", "spec.0.image", "my-image-name"),
resource.TestCheckResourceAttr("data.ec_container_image.by_name", "spec.0.tag", "my-tag1"),
),
},
{
Config: testDataSourceImageSpecConfigRead(),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("data.ec_container_image.by_image", "metadata.0.object_meta.0.name", "my-image-2"),
resource.TestCheckResourceAttr("data.ec_container_image.by_image", "metadata.0.branch", "my-branch"),
resource.TestCheckResourceAttr("data.ec_container_image.by_image", "spec.#", "1"),
resource.TestCheckResourceAttr("data.ec_container_image.by_image", "spec.0.image", "my-image-name"),
resource.TestCheckResourceAttr("data.ec_container_image.by_image", "spec.0.tag", "my-tag2"),
),
},
},
})
}

func testDataSourceImageNameConfigRead() string {
return `data "ec_container_image" "by_name" {
metadata {
object_meta {
name = "my-image-1"
}
branch = "my-branch"
}
}
`
}

func testDataSourceImageSpecConfigRead() string {
return `data "ec_container_image" "by_image" {
metadata {
branch = "my-branch"
}
spec {
image = "my-image-name"
}
}
`
}
69 changes: 69 additions & 0 deletions ec/container/schema_image.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package container

import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/nitrado/terraform-provider-ec/ec/meta"
)

// imageSchema is manually created, due to specific requirements.
func imageSchema() map[string]*schema.Schema {
return map[string]*schema.Schema{
"instance": {
Type: schema.TypeString,
Description: "Name is an instance name configured in the provider.",
Optional: true,
},
"metadata": {
Type: schema.TypeList,
Description: "Image object metadata.",
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"branch": {
Type: schema.TypeString,
Description: "Branch defines the branch within which each name must be unique.",
Required: true,
},
"object_meta": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{Schema: meta.Schema()},
},
},
},
},
"spec": {
Type: schema.TypeList,
Description: "Spec defines the desired image.",
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"digest": {
Type: schema.TypeString,
Description: "Digest is the hash of the image.",
Computed: true,
},
"image": {
Type: schema.TypeString,
Description: "Image is the image name.",
Required: true,
},
"registry": {
Type: schema.TypeString,
Description: "Registry is the registry that contains the image.",
Computed: true,
},
"tag": {
Type: schema.TypeString,
Description: "Tag is the image tag.",
Optional: true,
Computed: true,
},
},
},
},
}
}
Loading

0 comments on commit 1d447c9

Please sign in to comment.