diff --git a/internal/provider/append_resource.go b/internal/provider/append_resource.go index 190f2ef..a3cc178 100644 --- a/internal/provider/append_resource.go +++ b/internal/provider/append_resource.go @@ -25,8 +25,10 @@ import ( "github.com/hashicorp/terraform-plugin-log/tflog" ) -var _ resource.Resource = &AppendResource{} -var _ resource.ResourceWithImportState = &AppendResource{} +var ( + _ resource.Resource = &AppendResource{} + _ resource.ResourceWithImportState = &AppendResource{} +) func NewAppendResource() resource.Resource { return &AppendResource{} @@ -198,7 +200,10 @@ func (r *AppendResource) doAppend(ctx context.Context, data *AppendResourceModel if err != nil { return nil, []diag.Diagnostic{diag.NewErrorDiagnostic("Unable to parse base image", fmt.Sprintf("Unable to parse base image %q, got error: %s", data.BaseImage.ValueString(), err))} } - img, err := remote.Image(baseref, r.popts.withContext(ctx)...) + + ropts := r.popts.withContext(ctx) + + desc, err := remote.Get(baseref, ropts...) if err != nil { return nil, []diag.Diagnostic{diag.NewErrorDiagnostic("Unable to fetch base image", fmt.Sprintf("Unable to fetch base image %q, got error: %s", data.BaseImage.ValueString(), err))} } @@ -248,19 +253,64 @@ func (r *AppendResource) doAppend(ctx context.Context, data *AppendResourceModel }) } - img, err = mutate.Append(img, adds...) - if err != nil { - return nil, []diag.Diagnostic{diag.NewErrorDiagnostic("Unable to append layers", fmt.Sprintf("Unable to append layers, got error: %s", err))} - } + var d name.Digest - dig, err := img.Digest() - if err != nil { - return nil, []diag.Diagnostic{diag.NewErrorDiagnostic("Unable to get image digest", fmt.Sprintf("Unable to get image digest, got error: %s", err))} - } + if desc.MediaType.IsIndex() { + baseidx, err := desc.ImageIndex() + if err != nil { + return nil, []diag.Diagnostic{diag.NewErrorDiagnostic("Unable to read image index", fmt.Sprintf("Unable to read image index for ref %q, got error: %s", data.BaseImage.ValueString(), err))} + } + + baseimf, err := baseidx.IndexManifest() + if err != nil { + return nil, []diag.Diagnostic{diag.NewErrorDiagnostic("Unable to read image index manifest", fmt.Sprintf("Unable to read image index manifest for ref %q, got error: %s", data.BaseImage.ValueString(), err))} + } + + // get the image for each platform + for _, p := range baseimf.Manifests { + baseimg, err := baseidx.Image(p.Digest) + if err != nil { + return nil, []diag.Diagnostic{diag.NewErrorDiagnostic("Unable to load image", fmt.Sprintf("Unable to load image for ref %q, got error: %s", data.BaseImage.ValueString(), err))} + } + + img, err := mutate.Append(baseimg, adds...) + if err != nil { + return nil, []diag.Diagnostic{diag.NewErrorDiagnostic("Unable to append layers", fmt.Sprintf("Unable to append layers, got error: %s", err))} + } - d := baseref.Context().Digest(dig.String()) - if err := remote.Write(d, img, r.popts.withContext(ctx)...); err != nil { - return nil, []diag.Diagnostic{diag.NewErrorDiagnostic("Unable to push image", fmt.Sprintf("Unable to push image, got error: %s", err))} + baseidx = mutate.AppendManifests(baseidx, mutate.IndexAddendum{Add: img, Descriptor: p}) + + dig, err := baseidx.Digest() + if err != nil { + return nil, []diag.Diagnostic{diag.NewErrorDiagnostic("Unable to get image digest", fmt.Sprintf("Unable to get image digest, got error: %s", err))} + } + + d = baseref.Context().Digest(dig.String()) + if err := remote.WriteIndex(d, baseidx, r.popts.withContext(ctx)...); err != nil { + return nil, []diag.Diagnostic{diag.NewErrorDiagnostic("Unable to push image", fmt.Sprintf("Unable to push image, got error: %s", err))} + } + } + + } else if desc.MediaType.IsImage() { + baseimg, err := remote.Image(baseref, ropts...) + if err != nil { + return nil, []diag.Diagnostic{diag.NewErrorDiagnostic("Unable to fetch base image", fmt.Sprintf("Unable to fetch base image %q, got error: %s", data.BaseImage.ValueString(), err))} + } + + img, err := mutate.Append(baseimg, adds...) + if err != nil { + return nil, []diag.Diagnostic{diag.NewErrorDiagnostic("Unable to append layers", fmt.Sprintf("Unable to append layers, got error: %s", err))} + } + + dig, err := img.Digest() + if err != nil { + return nil, []diag.Diagnostic{diag.NewErrorDiagnostic("Unable to get image digest", fmt.Sprintf("Unable to get image digest, got error: %s", err))} + } + + d = baseref.Context().Digest(dig.String()) + if err := remote.Write(d, img, r.popts.withContext(ctx)...); err != nil { + return nil, []diag.Diagnostic{diag.NewErrorDiagnostic("Unable to push image", fmt.Sprintf("Unable to push image, got error: %s", err))} + } } // Write logs using the tflog package diff --git a/internal/provider/append_resource_test.go b/internal/provider/append_resource_test.go index 1e832aa..83f9a12 100644 --- a/internal/provider/append_resource_test.go +++ b/internal/provider/append_resource_test.go @@ -7,6 +7,7 @@ import ( ocitesting "github.com/chainguard-dev/terraform-provider-oci/testing" "github.com/google/go-containerregistry/pkg/crane" + "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/random" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/go-containerregistry/pkg/v1/validate" @@ -90,4 +91,51 @@ func TestAccAppendResource(t *testing.T) { // Delete testing automatically occurs in TestCase }, }) + + // Push an index to the local registry. + ref3 := repo.Tag("3") + idx1, err := random.Index(3, 1, 3) + if err != nil { + t.Fatalf("failed to create index: %v", err) + } + if err := remote.WriteIndex(ref3, idx1); err != nil { + t.Fatalf("failed to write index: %v", err) + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: fmt.Sprintf(`resource "oci_append" "test" { + base_image = %q + layers = [{ + files = { + "/usr/local/test.txt" = { contents = "hello world" } + } + }] + }`, ref3), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("oci_append.test", "base_image", ref3.String()), + resource.TestMatchResourceAttr("oci_append.test", "id", regexp.MustCompile(`/test@sha256:[0-9a-f]{64}$`)), + resource.TestCheckFunc(func(s *terraform.State) error { + rs := s.RootModule().Resources["oci_append.test"] + ref, err := name.ParseReference(rs.Primary.Attributes["image_ref"]) + if err != nil { + return fmt.Errorf("failed to parse reference: %v", err) + } + idx, err := remote.Index(ref) + if err != nil { + return fmt.Errorf("failed to pull image: %v", err) + } + if err := validate.Index(idx); err != nil { + return fmt.Errorf("failed to validate image: %v", err) + } + return nil + }), + ), + }, + }, + }) }