Skip to content

CI/CD workflow configuration #29

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 32 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ jobs:
(echo; echo "Unexpected difference in directories after code generation. Run 'make generate' command and commit."; exit 1)

# Run acceptance tests in a matrix with Terraform CLI versions
test:
acceptance-test:
name: Terraform Provider Acceptance Tests
needs: build
runs-on: self-hosted
Expand Down Expand Up @@ -82,7 +82,7 @@ jobs:
# - '1.1.*'
# - '1.2.*'
# - '1.3.*'
- '1.4.*'
- '1.11.*'
steps:
- name: Debug info
run: |
Expand All @@ -107,11 +107,40 @@ jobs:
terraform_version: ${{ matrix.terraform }}
terraform_wrapper: false
- run: go mod download
- name: Check Acceptance Test Environment
run: |
set -o allexport; eval "$(echo "$CI_CONFIG_HC_IP205_dos" | tr -s '\r\n' '\n')"; set +o allexport;
set -a
. ./internal/provider/tests/acceptance/setup/env.txt
go run ./internal/provider/tests/acceptance/setup/acceptance_test_env_prepare.go
- env:
TF_ACC: "1"
run: |
set -o allexport; eval "$(echo "$CI_CONFIG_HC_IP205_dos" | tr -s '\r\n' '\n')"; set +o allexport;
echo cur-shell HC_HOST=$HC_HOST
sh -c 'echo sub-shell HC_HOST=$HC_HOST'
go test -v -cover ./internal/provider/
set -a
. ./internal/provider/tests/acceptance/setup/env.txt
go test -v -cover -coverpkg=github.com/hashicorp/terraform-provider-hypercore/internal/provider ./internal/provider/tests/acceptance/
timeout-minutes: 10
- name: Cleanup Acceptance Test Environment
if: always()
run: |
set -o allexport; eval "$(echo "$CI_CONFIG_HC_IP205_dos" | tr -s '\r\n' '\n')"; set +o allexport;
set -a
. ./internal/provider/tests/acceptance/setup/env.txt
go run ./internal/provider/tests/acceptance/setup/acceptance_test_env_prepare.go "cleanup"

unit-test:
name: Go Unit Tests
needs: build
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed
with:
go-version-file: 'go.mod'
cache: true
- name: Run Unit Tests
run: go test -v -cover -coverpkg=github.com/hashicorp/terraform-provider-hypercore/internal/provider ./internal/provider/tests/unit/
9 changes: 9 additions & 0 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,12 @@ Add needed variables/secrets to github project:
```

TEMP: create VM named `testtf-src`.

Prior to running acceptance tests we need to setup:
1. Virtual machine
a. has one disk
b. has one nic
c. boot order is configured as [disk, nic]
2. Virtual disk (as standalone not attached to the testing VM)
3. Add names and UUIDs to the env.txt file in /tests/acceptance/setup directory
4. Virtual machine needs to be powered off
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/hashicorp/terraform-plugin-go v0.25.0
github.com/hashicorp/terraform-plugin-log v0.9.0
github.com/hashicorp/terraform-plugin-testing v1.11.0
github.com/stretchr/testify v1.9.0
)

require (
Expand Down Expand Up @@ -67,4 +68,5 @@ require (
google.golang.org/grpc v1.67.1 // indirect
google.golang.org/protobuf v1.35.1 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package provider
package acceptance

import (
"fmt"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package provider
package acceptance

import (
"testing"

"github.com/hashicorp/terraform-plugin-framework/providerserver"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
"github.com/hashicorp/terraform-provider-hypercore/internal/provider"
)

/*
var source_vm_name = os.Getenv("SOURCE_VM_NAME")
var existing_vdisk_uuid = os.Getenv("EXISTING_VDISK_UUID")
var source_nic_uuid = os.Getenv("SOURCE_NIC_UUID")
var source_disk_uuid = os.Getenv("SOURCE_DISK_UUID")
*/

// testAccProtoV6ProviderFactories are used to instantiate a provider during
// acceptance testing. The factory function will be invoked for every Terraform
// CLI command executed to create a provider server to which the CLI can
// reattach.
var testAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServer, error){
"hypercore": providerserver.NewProtocol6WithError(New("test")()),
"hypercore": providerserver.NewProtocol6WithError(provider.New("test")()),
}

func testAccPreCheck(t *testing.T) {
// You can add code here to run prior to any test case execution, for example assertions
// about the appropriate environment variables being set are common to see in a pre-check
// function.
// Prechecks
// Don't use terraform CRUD operations here, this is ran prior to the test and will not cleanup

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package main

import (
"bytes"
"crypto/tls"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"reflect"
"time"
)

type EnvConfig struct {
SourceVmUUID string
ExistingVdiskUUID string
SourceVmName string
SourceDiskUUID string
SourceNicUUID string
}

const (
VirDomainEndpoint = "/rest/v1/VirDomain/"
VirtualDiskEndpoint = "/rest/v1/VirtualDisk/"
VirDomainActionEndpoint = "/rest/v1/VirDomain/action"
)

func LoadEnv() EnvConfig {
return EnvConfig{
SourceVmUUID: os.Getenv("SOURCE_VM_UUID"),
ExistingVdiskUUID: os.Getenv("EXISTING_VDISK_UUID"),
SourceVmName: os.Getenv("SOURCE_VM_NAME"),
SourceDiskUUID: os.Getenv("SOURCE_DISK_UUID"),
SourceNicUUID: os.Getenv("SOURCE_NIC_UUID"),
}
}

func SetHTTPHeader(req *http.Request) *http.Request {
user := os.Getenv("HC_USERNAME")
pass := os.Getenv("HC_PASSWORD")

// Create the Basic Authentication string
auth := user + ":" + pass
authHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))

// Set the Content-Type header
req.Header.Set("Content-Type", "application/json")

// Set the Content-Length header (not required, it's usually set automatically)
// req.Header.Set("Content-Length", fmt.Sprintf("%d", len(data)))

// Set Basic Authentication header
req.Header.Set("Authorization", authHeader)
return req
}
func SetHTTPClient() *http.Client {
// Create a custom HTTP client with insecure transport
tr := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true, // Disable certificate verification
},
}
client := &http.Client{Transport: tr}

return client
}
func SendHTTPRequest(client *http.Client, method string, url string, data []byte) (*http.Response, []byte) {
req := SetHTTPMethod(method, url, data)
req = SetHTTPHeader(req)
resp, err := client.Do(req)
if err != nil {
log.Fatalf("Sending request failed with %v", err)
}
defer resp.Body.Close()

// Read and print the response
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatalf("Reading request response body failed with %v", err)
}

fmt.Println("Response Status:", resp.Status)
fmt.Println("Response Body:", string(body))

return resp, body
}
func SetHTTPMethod(method string, url string, data []byte) *http.Request {
var req *http.Request
var err error

// Set request method and body
if method == "GET" {
req, err = http.NewRequest("GET", url, bytes.NewBuffer(nil))
} else {
req, err = http.NewRequest("POST", url, bytes.NewBuffer(data))
}

// Handle any errors that occur
if err != nil {
log.Fatalf("%s request to url: %s failed with error: %v", method, url, err)
}

return req
}

func AreEnvVariablesLoaded(env EnvConfig) bool {
if env.SourceVmUUID == "" || env.ExistingVdiskUUID == "" || env.SourceVmName == "" || env.SourceDiskUUID == "" || env.SourceNicUUID == "" {
return false
}
return true
}
func DoesTestVMExist(host string, client *http.Client, env EnvConfig) bool {
url := fmt.Sprintf("%s%s%s", host, VirDomainEndpoint, env.SourceVmUUID)

resp, _ := SendHTTPRequest(client, "GET", url, nil)

return resp.StatusCode == http.StatusOK
}
func IsTestVMRunning(host string, client *http.Client, env EnvConfig) bool {
url := fmt.Sprintf("%s%s%s", host, VirDomainEndpoint, env.SourceVmUUID)

_, body := SendHTTPRequest(client, "GET", url, nil)

var result []map[string]interface{}
err := json.Unmarshal(body, &result)
if err != nil {
log.Fatal(err)
}
return result[0]["state"] != "SHUTOFF"
}
func DoesVirtualDiskExist(host string, client *http.Client, env EnvConfig) bool {
url := fmt.Sprintf("%s%s%s", host, VirtualDiskEndpoint, env.ExistingVdiskUUID)

resp, _ := SendHTTPRequest(client, "GET", url, nil)

return resp.StatusCode == http.StatusOK
}
func IsBootOrderCorrect(host string, client *http.Client, env EnvConfig) bool {
expectedBootOrder := []string{env.SourceDiskUUID, env.SourceNicUUID}
url := fmt.Sprintf("%s%s%s", host, VirDomainEndpoint, env.SourceVmUUID)

_, body := SendHTTPRequest(client, "GET", url, nil)

var result []map[string]interface{}
err := json.Unmarshal(body, &result)
if err != nil {
log.Fatal(err)
}
return reflect.DeepEqual(result[0]["bootDevices"], expectedBootOrder)
}
func PrepareEnv(host string, client *http.Client, env EnvConfig) {
// We are doing env prepare here, make sure all the necessary entities are setup and present
if !AreEnvVariablesLoaded(env) {
log.Fatal("Environment variables aren't loaded, check env file in /acceptance/setup directory")
} else {
fmt.Println("Environment variables are loaded correctly")
}
if !DoesTestVMExist(host, client, env) {
log.Fatal("Acceptance test VM is missing in your testing environment")
} else {
fmt.Println("Acceptance test VM is present in the testing environment")
}
if IsTestVMRunning(host, client, env) {
log.Fatal("Acceptance test VM is RUNNING and should be turned off before the testing begins")
} else {
fmt.Println("Acceptance test VM is in the correct SHUTOFF state")
}
if !DoesVirtualDiskExist(host, client, env) {
log.Fatal("Acceptance test Virtual disk is missing in your testing environment")
} else {
fmt.Println("Acceptance test Virtual disk is present in your testing environment")
}
if IsBootOrderCorrect(host, client, env) {
log.Fatal("Acceptance test Boot order is incorrect on the test VM, should be disk followed by network interface")
} else {
fmt.Println("Acceptance test Boot order is in correct order")
}
}

func CleanUpPowerState(host string, client *http.Client, env EnvConfig) {
data := []byte(fmt.Sprintf(`[{"virDomainUUID": "%s", "actionType": "STOP", "cause": "INTERNAL"}]`, env.SourceVmUUID))
url := fmt.Sprintf("%s%s", host, VirDomainActionEndpoint)
SendHTTPRequest(client, "POST", url, data)
// wait 30 seconds for VM to shutdown and then proceed with other cleanup tasks
time.Sleep(30 * time.Second)
}
func CleanUpBootOrder(host string, client *http.Client, env EnvConfig) {
bootOrder := []string{env.SourceDiskUUID, env.SourceNicUUID}
payload := map[string]interface{}{
"bootDevices": bootOrder,
}
data, err := json.Marshal(payload)
if err != nil {
log.Fatalf("Failed to marshal JSON: %v", err)
}
url := fmt.Sprintf("%s%s%s", host, VirDomainEndpoint, env.SourceVmUUID)
SendHTTPRequest(client, "POST", url, data)
}
func CleanupEnv(host string, client *http.Client, env EnvConfig) {
CleanUpPowerState(host, client, env)
CleanUpBootOrder(host, client, env)
}

func main() {
/*
We are running env setup here based on the arguments passed into GO program it's either going to:
1. Prepare environment
2. Cleanup environment
Argument we are looking to pass is "cleanup" see test.yml workflow file for more information
*/
env := LoadEnv()
host := os.Getenv("HC_HOST")
client := SetHTTPClient()
isCleanup := len(os.Args) > 1 && os.Args[1] == "cleanup"
fmt.Println("Are we doing Cleanup:", isCleanup)

if isCleanup {
CleanupEnv(host, client, env)
} else {
PrepareEnv(host, client, env)
}
}
5 changes: 5 additions & 0 deletions internal/provider/tests/acceptance/setup/env.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
SOURCE_VM_UUID="97904009-1878-4881-b6df-83c85ab7dc1a"
EXISTING_VDISK_UUID="33c78baf-c3c6-4600-8432-9c7a2a3008ab"
SOURCE_VM_NAME="integration-test-vm"
SOURCE_NIC_UUID="7a79893f-129f-4d35-8800-35a947ff4a51"
SOURCE_DISK_UUID="46dac49c-9726-4e4f-950f-f2b06fcf2e24"
Loading
Loading