Skip to content

Commit

Permalink
Add upload of tar.gz to Red Hat Connect endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
sebrandon1 committed Dec 11, 2024
1 parent c36ec77 commit ad28178
Show file tree
Hide file tree
Showing 7 changed files with 269 additions and 9 deletions.
8 changes: 8 additions & 0 deletions cmd/certsuite/run/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ func NewCommand() *cobra.Command {
runCmd.PersistentFlags().String("daemonset-mem-req", "100M", "Memory request for the probe daemonset container")
runCmd.PersistentFlags().String("daemonset-mem-lim", "100M", "Memory limit for the probe daemonset container")
runCmd.PersistentFlags().Bool("sanitize-claim", false, "Sanitize the claim.json file before sending it to the collector")
runCmd.PersistentFlags().String("connect-api-key", "", "API Key for Red Hat Connect portal")
runCmd.PersistentFlags().String("connect-project-id", "", "Project ID for Red Hat Connect portal")
runCmd.PersistentFlags().String("connect-api-proxy-url", "", "Proxy URL for Red Hat Connect API")
runCmd.PersistentFlags().String("connect-api-proxy-port", "", "Proxy port for Red Hat Connect API")

return runCmd
}
Expand Down Expand Up @@ -73,6 +77,10 @@ func initTestParamsFromFlags(cmd *cobra.Command) error {
testParams.DaemonsetMemReq, _ = cmd.Flags().GetString("daemonset-mem-req")
testParams.DaemonsetMemLim, _ = cmd.Flags().GetString("daemonset-mem-lim")
testParams.SanitizeClaim, _ = cmd.Flags().GetBool("sanitize-claim")
testParams.ConnectAPIKey, _ = cmd.Flags().GetString("connect-api-key")
testParams.ConnectProjectID, _ = cmd.Flags().GetString("connect-project-id")
testParams.ConnectAPIProxyURL, _ = cmd.Flags().GetString("connect-api-proxy-url")
testParams.ConnectAPIProxyPort, _ = cmd.Flags().GetString("connect-api-proxy-port")
timeoutStr, _ := cmd.Flags().GetString("timeout")

// Check if the output directory exists and, if not, create it
Expand Down
4 changes: 4 additions & 0 deletions config/certsuite_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,7 @@ executedBy: ""
partnerName: ""
collectorAppPassword: ""
collectorAppEndpoint: "http://claims-collector.cnf-certifications.sysdeseng.com"
connectAPIKey: ""
connectProjectID: ""
connectAPIProxyURL: ""
connectAPIProxyPort: ""
201 changes: 195 additions & 6 deletions internal/results/archiver.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ package results

import (
"archive/tar"
"bytes"
"compress/gzip"
"encoding/json"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/url"
"os"
"path/filepath"
"time"
Expand All @@ -16,6 +21,10 @@ const (
// tarGz file prefix layout format: YearMonthDay-HourMinSec
tarGzFileNamePrefixLayout = "20060102-150405"
tarGzFileNameSuffix = "cnf-test-results.tar.gz"

// Connect API Information
certIDURL = "https://access.qa.redhat.com/hydra/cwe/rest/v1.0/projects/certifications"
connectAPIURL = "https://access.qa.redhat.com/hydra/cwe/rest/v1.0/attachments/upload"
)

func generateZipFileName() string {
Expand All @@ -38,14 +47,14 @@ func getFileTarHeader(file string) (*tar.Header, error) {
}

// Creates a zip file in the outputDir containing each file in the filePaths slice.
func CompressResultsArtifacts(outputDir string, filePaths []string) error {
func CompressResultsArtifacts(outputDir string, filePaths []string) (string, error) {
zipFileName := generateZipFileName()
zipFilePath := filepath.Join(outputDir, zipFileName)

log.Info("Compressing results artifacts into %s", zipFilePath)
zipFile, err := os.Create(zipFilePath)
if err != nil {
return fmt.Errorf("failed creating tar.gz file %s in dir %s (filepath=%s): %v",
return "", fmt.Errorf("failed creating tar.gz file %s in dir %s (filepath=%s): %v",
zipFileName, outputDir, zipFilePath, err)
}

Expand All @@ -60,25 +69,205 @@ func CompressResultsArtifacts(outputDir string, filePaths []string) error {

tarHeader, err := getFileTarHeader(file)
if err != nil {
return err
return "", err
}

err = tarWriter.WriteHeader(tarHeader)
if err != nil {
return fmt.Errorf("failed to write tar header for %s: %v", file, err)
return "", fmt.Errorf("failed to write tar header for %s: %v", file, err)
}

f, err := os.Open(file)
if err != nil {
return fmt.Errorf("failed to open file %s: %v", file, err)
return "", fmt.Errorf("failed to open file %s: %v", file, err)
}

if _, err = io.Copy(tarWriter, f); err != nil {
return fmt.Errorf("failed to tar file %s: %v", file, err)
return "", fmt.Errorf("failed to tar file %s: %v", file, err)
}

f.Close()
}

// Return the entire path to the zip file
return zipFilePath, nil
}

func createFormField(w *multipart.Writer, field, value string) error {
fw, err := w.CreateFormField(field)
if err != nil {
return fmt.Errorf("failed to create form field: %v", err)
}

_, err = fw.Write([]byte(value))
if err != nil {
return fmt.Errorf("failed to write field %s: %v", field, err)
}

return nil
}

type CertIDResponse struct {
ID int `json:"id"`
CaseNumber string `json:"caseNumber"`
Status string `json:"status"`
CertificationLevel string `json:"certificationLevel"`
RhcertURL string `json:"rhcertUrl"`
HasStartedByPartner bool `json:"hasStartedByPartner"`
CertificationType struct {
ID int `json:"id"`
Name string `json:"name"`
} `json:"certificationType"`
}

// GetCertIDFromConnectAPI gets the certification ID from the Red Hat Connect API
func GetCertIDFromConnectAPI(apiKey, projectID, proxyURL, proxyPort string) (string, error) {
log.Info("Getting certification ID from Red Hat Connect API")

projectIDJSON := fmt.Sprintf(`{ "projectId": "%q" }`, projectID)

// Convert JSON to bytes
projectIDJSONBytes := []byte(projectIDJSON)

// Create a new request
req, err := http.NewRequest("POST", certIDURL, bytes.NewBuffer(projectIDJSONBytes))
if err != nil {
return "", fmt.Errorf("failed to create new request: %v", err)
}

// Set the content type
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
req.Header.Set("x-api-key", apiKey)

// print the request
log.Debug("Sending request to %s", certIDURL)

client := &http.Client{}
setProxy(client, proxyURL, proxyPort)
res, err := sendRequest(req, client)
if err != nil {
return "", fmt.Errorf("failed to send post request to the endpoint: %v", err)
}
defer res.Body.Close()

// Parse the response
var certIDResponse CertIDResponse
err = json.NewDecoder(res.Body).Decode(&certIDResponse)
if err != nil {
return "", fmt.Errorf("failed to decode response body: %v", err)
}

// Return the certification ID
return fmt.Sprintf("%d", certIDResponse.ID), nil
}

type UploadResult struct {
UUID string `json:"uuid"`
Type string `json:"type"`
Name string `json:"name"`
Size int `json:"size"`
ContentType string `json:"contentType"`
Desc string `json:"desc"`
DownloadURL string `json:"downloadUrl"`
UploadedBy string `json:"uploadedBy"`
UploadedDate time.Time `json:"uploadedDate"`
CertID int `json:"certId"`
}

// SendResultsToConnectAPI sends the results to the Red Hat Connect API
//
//nolint:funlen
func SendResultsToConnectAPI(zipFile, apiKey, certID, proxyURL, proxyPort string) error {
log.Info("Sending results to Red Hat Connect")

var buffer bytes.Buffer

// Create a new multipart writer
w := multipart.NewWriter(&buffer)

fw, err := w.CreateFormFile("attachment", filepath.Base(zipFile))
if err != nil {
return fmt.Errorf("failed to create form file: %v", err)
}

_, err = fw.Write([]byte(zipFile))
if err != nil {
return fmt.Errorf("failed to write file: %v", err)
}

// Create a form field
err = createFormField(w, "type", "RhocpBestPracticeTestResult")
if err != nil {
return err
}

// Create a form field
err = createFormField(w, "certId", certID)
if err != nil {
return err
}

// Create a form field
err = createFormField(w, "description", "CNF Test Results")
if err != nil {
return err
}

w.Close()

// Create a new request
req, err := http.NewRequest("POST", connectAPIURL, &buffer)
if err != nil {
return fmt.Errorf("failed to create new request: %v", err)
}

// Set the content type
req.Header.Set("Content-Type", w.FormDataContentType())
req.Header.Set("x-api-key", apiKey)

// Create a client
client := &http.Client{}
setProxy(client, proxyURL, proxyPort)
response, err := sendRequest(req, client)
if err != nil {
return fmt.Errorf("failed to send post request to the endpoint: %v", err)
}
defer response.Body.Close()

// Parse the result of the request
var uploadResult UploadResult
err = json.NewDecoder(response.Body).Decode(&uploadResult)
if err != nil {
return fmt.Errorf("failed to decode response body: %v", err)
}

log.Info("Download URL: %s", uploadResult.DownloadURL)
log.Info("Upload Date: %s", uploadResult.UploadedDate)
return nil
}

func sendRequest(req *http.Request, client *http.Client) (*http.Response, error) {
res, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to send post request to the endpoint: %v", err)
}

if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to send post request to the endpoint: %v", res.Status)
}

return res, nil
}

func setProxy(client *http.Client, proxyURL, proxyPort string) {
if proxyURL != "" && proxyPort != "" {
log.Debug("Proxy is set. Using proxy %s:%s", proxyURL, proxyPort)
proxyURL := fmt.Sprintf("%s:%s", proxyURL, proxyPort)
parsedURL, err := url.Parse(proxyURL)
if err != nil {
log.Error("Failed to parse proxy URL: %v", err)
}
client.Transport = &http.Transport{Proxy: http.ProxyURL(parsedURL)}
}
}
8 changes: 8 additions & 0 deletions pkg/autodiscover/autodiscover.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ type DiscoveredTestData struct {
PartnerName string
CollectorAppPassword string
CollectorAppEndpoint string
ConnectAPIKey string
ConnectProjectID string
ConnectAPIProxyURL string
ConnectAPIProxyPort string
}

type labelObject struct {
Expand Down Expand Up @@ -322,6 +326,10 @@ func DoAutoDiscover(config *configuration.TestConfiguration) DiscoveredTestData
data.PartnerName = config.PartnerName
data.CollectorAppPassword = config.CollectorAppPassword
data.CollectorAppEndpoint = config.CollectorAppEndpoint
data.ConnectAPIKey = config.ConnectAPIKey
data.ConnectProjectID = config.ConnectProjectID
data.ConnectAPIProxyURL = config.ConnectAPIProxyURL
data.ConnectAPIProxyPort = config.ConnectAPIProxyPort

return data
}
Expand Down
41 changes: 38 additions & 3 deletions pkg/certsuite/certsuite.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ func Shutdown() {
}
}

//nolint:funlen
//nolint:funlen,gocyclo
func Run(labelsFilter, outputFolder string) error {
testParams := configuration.GetTestParameters()

Expand Down Expand Up @@ -201,12 +201,47 @@ func Run(labelsFilter, outputFolder string) error {
// Add the log file path
allArtifactsFilePaths = append(allArtifactsFilePaths, filepath.Join(outputFolder, log.LogFileName))

// Red Hat Connect API key and project ID are required to send the tar.gz to Red Hat Connect.
sendToConnectAPI := false
if env.ConnectAPIKey != "" && env.ConnectProjectID != "" {
log.Info("Sending results to Red Hat Connect API with API key %s and project ID %s", env.ConnectAPIKey, env.ConnectProjectID)
sendToConnectAPI = true
} else {
log.Info("Red Hat Connect API key and project ID are not set. Results will not be sent to Red Hat Connect.")
}

// tar.gz file creation with results and html artifacts, unless omitted by env var.
if !configuration.GetTestParameters().OmitArtifactsZipFile {
err = results.CompressResultsArtifacts(resultsOutputDir, allArtifactsFilePaths)
if !configuration.GetTestParameters().OmitArtifactsZipFile || sendToConnectAPI {
zipFile, err := results.CompressResultsArtifacts(resultsOutputDir, allArtifactsFilePaths)
if err != nil {
log.Fatal("Failed to compress results artifacts: %v", err)
}

if sendToConnectAPI {
log.Debug("Get CertificationID from the Red Hat Connect API")
certificationID, err := results.GetCertIDFromConnectAPI(
env.ConnectAPIKey, env.ConnectProjectID, env.ConnectAPIProxyURL, env.ConnectAPIProxyPort)
if err != nil {
log.Fatal("Failed to get CertificationID from Red Hat Connect: %v", err)
}

log.Debug("Sending ZIP file %s to Red Hat Connect", zipFile)
err = results.SendResultsToConnectAPI(zipFile,
env.ConnectAPIKey, certificationID, env.ConnectAPIProxyURL, env.ConnectAPIProxyPort)
if err != nil {
log.Fatal("Failed to send results to Red Hat Connect: %v", err)
}

log.Info("Results successfully sent to Red Hat Connect with CertificationID %s", certificationID)
}

if !configuration.GetTestParameters().OmitArtifactsZipFile {
// delete the zip as the user does not want it.
err = os.Remove(zipFile)
if err != nil {
log.Fatal("Failed to remove zip file %s: %v", zipFile, err)
}
}
}

// Remove web artifacts if user does not want them.
Expand Down
8 changes: 8 additions & 0 deletions pkg/configuration/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ type TestConfiguration struct {
PartnerName string `yaml:"partnerName,omitempty" json:"partnerName,omitempty"`
CollectorAppPassword string `yaml:"collectorAppPassword,omitempty" json:"collectorAppPassword,omitempty"`
CollectorAppEndpoint string `yaml:"collectorAppEndpoint,omitempty" json:"collectorAppEndpoint,omitempty"`
ConnectAPIKey string `yaml:"connectAPIKey,omitempty" json:"connectAPIKey,omitempty"`
ConnectProjectID string `yaml:"connectProjectID,omitempty" json:"connectProjectID,omitempty"`
ConnectAPIProxyURL string `yaml:"connectAPIProxyURL,omitempty" json:"connectAPIProxyURL,omitempty"`
ConnectAPIProxyPort string `yaml:"connectAPIProxyPort,omitempty" json:"connectAPIProxyPort,omitempty"`
}

type TestParameters struct {
Expand All @@ -117,4 +121,8 @@ type TestParameters struct {
EnableXMLCreation bool
ServerMode bool
Timeout time.Duration
ConnectAPIKey string
ConnectProjectID string
ConnectAPIProxyURL string
ConnectAPIProxyPort string
}
Loading

0 comments on commit ad28178

Please sign in to comment.