| // Copyright IBM Corp. 2014, 2026 | |
| // SPDX-License-Identifier: BUSL-1.1 | |
| package providercache | |
| import ( | |
| "archive/zip" | |
| "bytes" | |
| "crypto/sha256" | |
| "fmt" | |
| "io" | |
| "os" | |
| "path/filepath" | |
| "strings" | |
| "testing" | |
| "github.com/ProtonMail/go-crypto/openpgp" | |
| "github.com/ProtonMail/go-crypto/openpgp/armor" | |
| ) | |
| type providerPkg struct { | |
| zipContent []byte | |
| shaSumContent []byte | |
| sigContent []byte | |
| key *bytes.Buffer | |
| keyID string | |
| filePrefix string // <provider_name>_<version> | |
| } | |
| // createTestProvider creates a test provider package with required verification files | |
| func createTestProvider(t *testing.T, providerName, version string) *providerPkg { | |
| entity, pub := generateGPGKey(t) | |
| dir := t.TempDir() | |
| fileName := fmt.Sprintf("%s_%s", providerName, version) | |
| // Create a zip file for the provider | |
| zipFile := filepath.Join(dir, fileName) | |
| zipContent := createProviderZip(t, zipFile, fileName) | |
| // Generate SHA256SUMS file | |
| sumFile, sumContent, err := createSHASums(dir, fileName) | |
| if err != nil { | |
| t.Fatalf("failed to create SHA sums: %s", err) | |
| } | |
| // Sign the SHA256SUMS file | |
| sigContent, err := createDetachedSignature(sumFile, entity) | |
| if err != nil { | |
| t.Fatalf("failed to create signature: %s", err) | |
| } | |
| return &providerPkg{ | |
| zipContent: zipContent, | |
| shaSumContent: sumContent, | |
| sigContent: sigContent, | |
| key: pub, | |
| keyID: entity.PrimaryKey.KeyIdString(), | |
| filePrefix: fileName, | |
| } | |
| } | |
| func generateGPGKey(t *testing.T) (*openpgp.Entity, *bytes.Buffer) { | |
| entity, err := openpgp.NewEntity("Terraform Test", "test", "terraform@example.com", nil) | |
| if err != nil { | |
| t.Fatalf("failed to create entity: %s", err) | |
| } | |
| // Export the public key in armored format | |
| pubBuf := bytes.NewBuffer(nil) | |
| w, err := armor.Encode(pubBuf, openpgp.PublicKeyType, nil) | |
| if err != nil { | |
| t.Fatalf("failed to create armor writer: %s", err) | |
| } | |
| if err := entity.Serialize(w); err != nil { | |
| t.Fatalf("failed to serialize key: %s", err) | |
| } | |
| if err := w.Close(); err != nil { | |
| t.Fatalf("failed to finalize armor: %s", err) | |
| } | |
| return entity, pubBuf | |
| } | |
| func createProviderZip(t *testing.T, zipPath, filename string) []byte { | |
| zipBuffer := new(bytes.Buffer) | |
| zipWriter := zip.NewWriter(zipBuffer) | |
| // Add file to the archive | |
| _, err := zipWriter.Create(fmt.Sprintf("terraform-provider-%s", filename)) | |
| if err != nil { | |
| t.Fatalf("failed to create zip entry: %s", err) | |
| } | |
| if err := zipWriter.Close(); err != nil { | |
| t.Fatalf("failed to close zip writer: %s", err) | |
| } | |
| // Write the zip to a file | |
| zipContent := zipBuffer.Bytes() | |
| if err := os.WriteFile(zipPath+".zip", zipContent, 0666); err != nil { | |
| t.Fatalf("failed to write zip file: %s", err) | |
| } | |
| return zipContent | |
| } | |
| // createDetachedSignature creates a PGP detached signature for the given file | |
| func createDetachedSignature(filePath string, entity *openpgp.Entity) ([]byte, error) { | |
| r, err := os.Open(filePath) | |
| if err != nil { | |
| return nil, err | |
| } | |
| defer safeClose(r) | |
| sigPath := filePath + ".sig" | |
| w, err := os.Create(sigPath) | |
| if err != nil { | |
| return nil, err | |
| } | |
| defer safeClose(w) | |
| if err := openpgp.DetachSign(w, entity, r, nil); err != nil { | |
| return nil, err | |
| } | |
| // Read the signature back | |
| return os.ReadFile(sigPath) | |
| } | |
| // createSHASums creates SHA256 sums for all files in the directory | |
| func createSHASums(dir, name string) (string, []byte, error) { | |
| var sums []string | |
| files, err := os.ReadDir(dir) | |
| if err != nil { | |
| return "", nil, err | |
| } | |
| for _, file := range files { | |
| if file.IsDir() { | |
| continue | |
| } | |
| filePath := filepath.Join(dir, file.Name()) | |
| sum, err := calculateFileHash(filePath) | |
| if err != nil { | |
| return "", nil, err | |
| } | |
| sums = append(sums, fmt.Sprintf("%x %s", sum, file.Name())) | |
| } | |
| sumContent := []byte(strings.Join(sums, "\n") + "\n") | |
| sumPath := filepath.Join(dir, fmt.Sprintf("%s_SHA256SUMS", name)) | |
| if err := os.WriteFile(sumPath, sumContent, 0666); err != nil { | |
| return "", nil, err | |
| } | |
| return sumPath, sumContent, nil | |
| } | |
| // calculateFileHash returns the SHA256 hash of a file | |
| func calculateFileHash(path string) ([]byte, error) { | |
| f, err := os.Open(path) | |
| if err != nil { | |
| return nil, err | |
| } | |
| defer safeClose(f) | |
| h := sha256.New() | |
| if _, err := io.Copy(h, f); err != nil { | |
| return nil, err | |
| } | |
| return h.Sum(nil), nil | |
| } | |
| // safeClose safely closes a file, logging any errors | |
| func safeClose(f *os.File) { | |
| if err := f.Close(); err != nil { | |
| fmt.Printf("failed to close file: %s\n", err) | |
| } | |
| } |