Skip to content
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
68 changes: 34 additions & 34 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,37 +6,37 @@ repos:
# Go Build
#
- id: go-build-repo-mod
#
# Go Mod Tidy
#
- id: go-mod-tidy
#
# Go Test
#
- id: go-test-mod
#
# Go Vet
#
- id: go-vet-mod
#
# Revive
#
- id: go-revive
#
# GoSec
#
- id: go-sec-mod
#
# StructSlop
#
- id: go-structslop-mod
#
# Formatters
#
- id: go-fumpt # replaces go-fmt
- id: go-imports # replaces go-fmt
#
# Style Checkers
#
- id: go-lint
- id: go-critic
# #
# # Go Mod Tidy
# #
# - id: go-mod-tidy
# #
# # Go Test
# #
# - id: go-test-mod
# #
# # Go Vet
# #
# - id: go-vet-mod
# #
# # Revive
# #
# - id: go-revive
# #
# # GoSec
# #
# - id: go-sec-mod
# #
# # StructSlop
# #
# - id: go-structslop-mod
# #
# # Formatters
# #
# - id: go-fumpt # replaces go-fmt
# - id: go-imports # replaces go-fmt
# #
# # Style Checkers
# #
# - id: go-lint
# - id: go-critic
1 change: 0 additions & 1 deletion cmd/prowjob/createReport.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,6 @@ var createReportCmd = &cobra.Command{

func init() {
createReportCmd.Flags().StringVar(&prowJobID, prowJobIDParamName, "", "Prow job ID to analyze")
createReportCmd.Flags().StringVar(&artifactDir, artifactDirParamName, "", "Path to the folder where to store produced files")

_ = viper.BindPFlag(artifactDirParamName, createReportCmd.Flags().Lookup(artifactDirParamName))
_ = viper.BindPFlag(prowJobIDParamName, createReportCmd.Flags().Lookup(prowJobIDParamName))
Expand Down
193 changes: 193 additions & 0 deletions cmd/prowjob/healthCheck.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
package prowjob

import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"

"golang.org/x/exp/slices"

"github.com/spf13/cobra"
"github.com/spf13/viper"
"k8s.io/klog/v2"
)

var healthCheckConfig HealthCheckConfig

type HealthCheckConfig struct {
ExternalServices []Service `json:"externalServices"`
}

type HealthCheckStatus struct {
ExternalServices []Service `json:"externalServices"`
UnhealthyCriticalComponents []string `json:"unhealthyCriticalComponents"`
}

type Service struct {
Name string `json:"name"`
CriticalComponents []string `json:"criticalComponents"`
StatusPageURL string `json:"statusPageURL"`
CurrentStatus Summary `json:"currentStatus"`
}

// Summary is the Statuspage API component representation
type Summary struct {
Components []Component `json:"components"`
Incidents []Incident `json:"incidents"`
Status Status `json:"status"`
}

// Component is the Statuspage API component representation
type Component struct {
ID string `json:"id,omitempty"`
PageID string `json:"page_id,omitempty"`
GroupID string `json:"group_id,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
Group bool `json:"group,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Position int32 `json:"position,omitempty"`
Status string `json:"status,omitempty"`
Showcase bool `json:"showcase,omitempty"`
OnlyShowIfDegraded bool `json:"only_show_if_degraded,omitempty"`
AutomationEmail string `json:"automation_email,omitempty"`
}

// Incident entity reflects one single incident
type Incident struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Status string `json:"status,omitempty"`
Message string `json:"message,omitempty"`
Visible int `json:"visible,omitempty"`
ComponentID int `json:"component_id,omitempty"`
ComponentStatus int `json:"component_status,omitempty"`
Notify bool `json:"notify,omitempty"`
Stickied bool `json:"stickied,omitempty"`
OccurredAt string `json:"occurred_at,omitempty"`
Template string `json:"template,omitempty"`
Vars []string `json:"vars,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
DeletedAt string `json:"deleted_at,omitempty"`
IsResolved bool `json:"is_resolved,omitempty"`
Updates []IncidentUpdate `json:"incident_updates,omitempty"`
HumanStatus string `json:"human_status,omitempty"`
LatestUpdateID int `json:"latest_update_id,omitempty"`
LatestStatus int `json:"latest_status,omitempty"`
LatestHumanStatus string `json:"latest_human_status,omitempty"`
LatestIcon string `json:"latest_icon,omitempty"`
Permalink string `json:"permalink,omitempty"`
Duration int `json:"duration,omitempty"`
}

// IncidentUpdate entity reflects one single incident update
type IncidentUpdate struct {
ID string `json:"id,omitempty"`
Body string `json:"body,omitempty"`
IncidentID string `json:"incident_id,omitempty"`
ComponentID int `json:"component_id,omitempty"`
ComponentStatus int `json:"component_status,omitempty"`
Status string `json:"status,omitempty"`
Message string `json:"message,omitempty"`
UserID int `json:"user_id,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
HumanStatus string `json:"human_status,omitempty"`
Permalink string `json:"permalink,omitempty"`
}

// Status entity contains the contents of API Response of a /status call.
type Status struct {
Indicator string `json:"indicator,omitempty"`
Description string `json:"description,omitempty"`
}

// healthCheckCmd represents the createReport command
var healthCheckCmd = &cobra.Command{
Use: "health-check",
Short: "Perform a health check on dependant services",
PreRunE: func(cmd *cobra.Command, args []string) error {
viper.AddConfigPath("./config/health-check")
viper.SetConfigName("config")
viper.SetConfigType("yaml")
if err := viper.ReadInConfig(); err != nil {
return fmt.Errorf("err readinconfig: %+v", err)
}
if err := viper.Unmarshal(&healthCheckConfig); err != nil {
return fmt.Errorf("failed to parse config: %+v", err)
}
return nil
},
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
status := &HealthCheckStatus{}
status.ExternalServices = healthCheckConfig.ExternalServices
for i, service := range status.ExternalServices {
r, err := http.Get(service.StatusPageURL)
if err != nil {
return fmt.Errorf("failed to get service %s status page: %+v", service.Name, err)
}
body, err := io.ReadAll(r.Body)
if err != nil {
return fmt.Errorf("failed to read response body for a service %s: %+v", service.Name, err)
}
v := Summary{}
if err := json.Unmarshal(body, &v); err != nil {
return fmt.Errorf("failed to unmarshal response body from a service %s: %+v", service.Name, err)
}
status.ExternalServices[i].CurrentStatus = v

for _, c := range v.Components {
if c.Status == "major_outage" && isCriticalComponent(service, c) {
desc := fmt.Sprintf("%s: %s", service.Name, c.Name)
status.UnhealthyCriticalComponents = append(status.UnhealthyCriticalComponents, desc)
}
}

}
artifactDir := viper.GetString(artifactDirParamName)
if artifactDir == "" {
artifactDir = "./tmp"
klog.Warningf("path to artifact dir was not provided - using default %q\n", artifactDir)
}
if err := os.MkdirAll(artifactDir, 0o750); err != nil {
return fmt.Errorf("failed to create directory for results '%s': %+v", artifactDir, err)
}
o, err := json.MarshalIndent(status, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal services status: %+v", err)
}
reportFilePath := artifactDir + "/services-status.json"
if err := os.WriteFile(reportFilePath, []byte(o), 0o600); err != nil {
return fmt.Errorf("failed to create file with the status of dependant services: %+v", err)
}
klog.Infof("health check report saved to %s", reportFilePath)

if viper.GetBool(failIfUnhealthyParamName) {
// for s := range status.Services.Github.Components {
Copy link

@srivickynesh srivickynesh Nov 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we remove this if not required?


// }
return fmt.Errorf("TESTING UNHEALTHY!")
}

return nil
},
}

func isCriticalComponent(service Service, c Component) bool {
return slices.Contains(service.CriticalComponents, c.Name)
}

func init() {
healthCheckCmd.Flags().BoolVar(&failIfUnhealthy, failIfUnhealthyParamName, false, "Exit with non-zero code if health check fails")

_ = viper.BindPFlag(artifactDirParamName, healthCheckCmd.Flags().Lookup(artifactDirParamName))
_ = viper.BindPFlag(failIfUnhealthyParamName, healthCheckCmd.Flags().Lookup(failIfUnhealthyParamName))
// Bind environment variables to viper (in case the associated command's parameter is not provided)
_ = viper.BindEnv(artifactDirParamName, artifactDirEnv)
}
11 changes: 9 additions & 2 deletions cmd/prowjob/prowjob.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@ const (
artifactDirEnv string = "ARTIFACT_DIR"
artifactDirParamName string = "artifact-dir"

failIfUnhealthyParamName string = "fail-if-unhealthy"

prowJobIDEnv string = "PROW_JOB_ID"
prowJobIDParamName string = "prow-job-id"
)

var (
artifactDir string
prowJobID string
artifactDir string
failIfUnhealthy bool
prowJobID string
)

// ProwjobCmd represents the prowjob command
Expand All @@ -26,4 +29,8 @@ var ProwjobCmd = &cobra.Command{
func init() {
ProwjobCmd.AddCommand(periodicSlackReportCmd)
ProwjobCmd.AddCommand(createReportCmd)
ProwjobCmd.AddCommand(healthCheckCmd)

createReportCmd.Flags().StringVar(&artifactDir, artifactDirParamName, "", "Path to the folder where to store produced files")
healthCheckCmd.Flags().StringVar(&artifactDir, artifactDirParamName, "", "Path to the folder where to store produced files")
}
19 changes: 19 additions & 0 deletions config/health-check/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
externalServices:
- name: redhat
criticalComponents:
- registry.redhat.io
- registry.access.redhat.com
statusPageURL: https://status.redhat.com/api/v2/summary.json
- name: quay
criticalComponents:
- Registry
- API
- Build System
statusPageURL: https://status.quay.io/api/v2/summary.json
- name: github
criticalComponents:
- Git Operations
- API Requests
- Webhooks
- Pull Requests
statusPageURL: https://www.githubstatus.com/api/v2/summary.json
Loading