Skip to content
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

Add upload of tar.gz to Red Hat Connect endpoint #2629

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
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: ""
241 changes: 235 additions & 6 deletions internal/results/archiver.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@ package results

import (
"archive/tar"
"bytes"
"compress/gzip"
"encoding/json"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"time"

"github.com/redhat-best-practices-for-k8s/certsuite/internal/log"
Expand All @@ -16,6 +22,13 @@ 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"

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

func generateZipFileName() string {
Expand All @@ -38,14 +51,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 +73,241 @@ 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()
}

// Create fully qualified path to the zip file
zipFilePath, err = filepath.Abs(zipFilePath)
if err != nil {
return "", fmt.Errorf("failed to get absolute path for %s: %v", zipFilePath, err)
}

// 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")

// sanitize the incoming variables, remove the double quotes if any
apiKey = strings.ReplaceAll(apiKey, "\"", "")
projectID = strings.ReplaceAll(projectID, "\"", "")
proxyURL = strings.ReplaceAll(proxyURL, "\"", "")
proxyPort = strings.ReplaceAll(proxyPort, "\"", "")

// remove quotes from projectID
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)
}

log.Debug("Request Body: %s", req.Body)

// 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)
}

log.Info("Certification ID retrieved from the API: %d", certIDResponse.ID)

// 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")

// sanitize the incoming variables, remove the double quotes if any
apiKey = strings.ReplaceAll(apiKey, "\"", "")
certID = strings.ReplaceAll(certID, "\"", "")
proxyURL = strings.ReplaceAll(proxyURL, "\"", "")
proxyPort = strings.ReplaceAll(proxyPort, "\"", "")

var buffer bytes.Buffer

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

log.Debug("Creating form file for %s", zipFile)

claimFile, err := os.Open(zipFile)
if err != nil {
return err
}
defer claimFile.Close()

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

if _, err = io.Copy(fw, claimFile); err != nil {
return 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) {
// print the request
log.Debug("Sending request to %s", req.URL)
log.Info("request: %v", req)

res, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to send post request: %v", err)
}

if res.StatusCode != http.StatusOK {
log.Debug("Response: %v", res)
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)
}
log.Debug("Proxy URL: %s", parsedURL)
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
Loading
Loading