diff --git a/internal/provider/build_resource.go b/internal/provider/build_resource.go index f630995..8c980c8 100644 --- a/internal/provider/build_resource.go +++ b/internal/provider/build_resource.go @@ -92,17 +92,19 @@ func (r *BuildResource) Create(ctx context.Context, req resource.CreateRequest, return } - id, err := r.doBuild(ctx, data) - if err != nil { + if err := r.doBuild(ctx, data); err != nil { resp.Diagnostics.AddError("Client Error", err.Error()) return } - data.Id = types.StringValue(id) - - // Write logs using the tflog package - // Documentation: https://terraform.io/plugin/log - tflog.Trace(ctx, "created a resource") + // ID is the sha256 of the JSON-serialized input config, + // to ensure the resource is updated if the changes. + b, err := json.Marshal(data.Config.String()) + if err != nil { + resp.Diagnostics.AddError("Client Error", err.Error()) + return + } + data.Id = types.StringValue(fmt.Sprintf("%x", sha256.Sum256(b))) // Save data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) @@ -115,6 +117,15 @@ func (r *BuildResource) Read(ctx context.Context, req resource.ReadRequest, resp return } + // ID is the sha256 of the JSON-serialized input config, + // to ensure the resource is updated if the changes. + b, err := json.Marshal(data.Config.String()) + if err != nil { + resp.Diagnostics.AddError("Client Error", err.Error()) + return + } + data.Id = types.StringValue(fmt.Sprintf("%x", sha256.Sum256(b))) + // Save updated data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } @@ -126,6 +137,20 @@ func (r *BuildResource) Update(ctx context.Context, req resource.UpdateRequest, return } + if err := r.doBuild(ctx, data); err != nil { + resp.Diagnostics.AddError("Client Error", err.Error()) + return + } + + // ID is the sha256 of the JSON-serialized input config, + // to ensure the resource is updated if the changes. + b, err := json.Marshal(data.Config.String()) + if err != nil { + resp.Diagnostics.AddError("Client Error", err.Error()) + return + } + data.Id = types.StringValue(fmt.Sprintf("%x", sha256.Sum256(b))) + // Save updated data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } @@ -136,16 +161,17 @@ func (r *BuildResource) Delete(ctx context.Context, req resource.DeleteRequest, if resp.Diagnostics.HasError() { return } + // Nothing to delete. } func (r *BuildResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) } -func (r *BuildResource) doBuild(ctx context.Context, data BuildResourceModel) (string, error) { +func (r *BuildResource) doBuild(ctx context.Context, data BuildResourceModel) error { var cfg Configuration if diags := reflect.AssignValue(data.Config, &cfg); diags.HasError() { - return "", fmt.Errorf("assigning value: %v", diags.Errors()) + return fmt.Errorf("assigning value: %v", diags.Errors()) } // TODO: support force-overwrite, so you don't have to rm the package or bump the epoch while testing locally. @@ -164,20 +190,20 @@ func (r *BuildResource) doBuild(ctx context.Context, data BuildResourceModel) (s sdir := filepath.Join(r.popts.dir, cfg.Package.Name) if _, err := os.Stat(sdir); os.IsNotExist(err) { if err := os.MkdirAll(sdir, os.ModePerm); err != nil { - return "", fmt.Errorf("creating source directory %s: %v", sdir, err) + return fmt.Errorf("creating source directory %s: %v", sdir, err) } } else if err != nil { - return "", fmt.Errorf("creating source directory: %v", err) + return fmt.Errorf("creating source directory: %v", err) } // Write the config to a temp file. // TODO(jason): This is kind of gross, but Melange's build API requires a file path. tmp, err := os.CreateTemp("", fmt.Sprintf("%s-*.yaml", cfg.Package.Name)) if err != nil { - return "", fmt.Errorf("creating temporary file: %v", err) + return fmt.Errorf("creating temporary file: %v", err) } if err := os.WriteFile(tmp.Name(), []byte(data.ConfigContents.ValueString()), 0644); err != nil { - return "", fmt.Errorf("writing config to temporary file: %v", err) + return fmt.Errorf("writing config to temporary file: %v", err) } tflog.Trace(ctx, fmt.Sprintf("will build %s for %s", cfg.Package.Name, arch)) @@ -195,7 +221,7 @@ func (r *BuildResource) doBuild(ctx context.Context, data BuildResourceModel) (s build.WithGenerateIndex(true), ) if err != nil { - return "", fmt.Errorf("building %s for %s: %w", cfg.Package.Name, arch, err) + return fmt.Errorf("building %s for %s: %w", cfg.Package.Name, arch, err) } bcs = append(bcs, bc) } @@ -207,14 +233,7 @@ func (r *BuildResource) doBuild(ctx context.Context, data BuildResourceModel) (s }) } if err := errg.Wait(); err != nil { - return "", err + return err } - - // ID is the sha256 of the JSON-serialized input config, - // to ensure the resource is updated if the changes. - b, err := json.Marshal(data.Config) - if err != nil { - return "", err - } - return fmt.Sprintf("%x", sha256.Sum256(b)), nil + return nil } diff --git a/internal/provider/build_resource_test.go b/internal/provider/build_resource_test.go index 746450b..ae70455 100644 --- a/internal/provider/build_resource_test.go +++ b/internal/provider/build_resource_test.go @@ -49,7 +49,8 @@ resource "melange_build" "build" { }`, Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("melange_build.build", "config.package.name", "minimal"), - //resource.TestCheckResourceAttr("melange_build.build", "id", "44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a"), + resource.TestCheckResourceAttr("melange_build.build", "config.package.epoch", "3"), + resource.TestCheckResourceAttr("melange_build.build", "id", "2966ff23365b2bfb14798021109424102419b989b044778d5484297ade1b201b"), ), }}, }) @@ -90,4 +91,56 @@ resource "melange_build" "build" { t.Errorf("checksum mismatch: %v != %v", idx.Packages[0].Checksum, pkg.Checksum) } // TODO(jason): Check that index is signed with the key. + + // Update the resource to bump the epoch. + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: providerFactories, + Steps: []resource.TestStep{{ + Config: ` +data "melange_config" "minimal" { + config_contents = file("${path.module}/testdata/minimal.yaml") +} + +// Simulate bumping the epoch. +locals { + updated = merge(data.melange_config.minimal.config, { + package = { + name = "minimal" + version = "0.0.1" + epoch = 4 + } + }) +} + +resource "melange_build" "build" { + config = local.updated + config_contents = yamlencode(local.updated) +}`, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("melange_build.build", "config.package.name", "minimal"), + resource.TestCheckResourceAttr("melange_build.build", "config.package.epoch", "4"), + resource.TestCheckResourceAttr("melange_build.build", "id", "1e44061d052789d741503299f4bb0fa37311dfb9df24f373994e6b95c0fe664c"), + ), + }}, + }) + + // Check the new apk. + { + fn := fmt.Sprintf("packages/%s/minimal-0.0.1-r4.apk", arch) + f, err := os.Open(fn) + if err != nil { + t.Fatalf("failed to open apk: %v", err) + } + defer f.Close() + pkg, err := repository.ParsePackage(f) + if err != nil { + t.Fatalf("failed to parse apk: %v", err) + } + if pkg.Name != "minimal" { + t.Errorf("unexpected package name: %v", pkg.Name) + } + if pkg.Version != "0.0.1-r4" { + t.Errorf("unexpected package version: %v", pkg.Version) + } + } }