|
|
Entry point to the insights results smart proxy REST API service.
This file contains functions needed to start the service from command line.
|
package main
import (
"encoding/json"
"errors"
"flag"
"fmt"
"net/url"
"os"
"strings"
"time"
"github.com/RedHatInsights/insights-content-service/groups"
"github.com/RedHatInsights/insights-operator-utils/logger"
"github.com/rs/zerolog/log"
"github.com/RedHatInsights/insights-results-smart-proxy/amsclient"
"github.com/RedHatInsights/insights-results-smart-proxy/auth"
"github.com/RedHatInsights/insights-results-smart-proxy/conf"
"github.com/RedHatInsights/insights-results-smart-proxy/metrics"
"github.com/RedHatInsights/insights-results-smart-proxy/server"
"github.com/RedHatInsights/insights-results-smart-proxy/services"
proxy_content "github.com/RedHatInsights/insights-results-smart-proxy/content"
)
|
ExitCode represents numeric value returned to parent process when the
current process finishes
|
type ExitCode int
const (
|
ExitStatusOK means that the service have finished with success
|
ExitStatusOK = iota
|
ExitStatusServerError means that the HTTP server cannot be initialized
|
ExitStatusServerError
defaultConfigFileName = "config"
)
const helpMessageTemplate = `
Smart Proxy service for insights results
Usage:
%+v [command]
The commands are:
<EMPTY> starts smart-proxy
start-service starts smart-proxy
help prints help
print-help prints help
print-config prints current configuration set by files & env variables
print-env prints env variables
print-version-info prints version info
`
|
serverInstance represents instance of REST API server
|
var serverInstance * server . HTTPServer
|
printHelp function displays help on the standard output.
|
func printHelp ( ) ExitCode {
fmt . Printf ( helpMessageTemplate , os . Args [ 0 ] )
return ExitStatusOK
}
|
printConfig function displays loaded configuration on the standard output.
|
func printConfig ( ) ExitCode {
configBytes , err := json . MarshalIndent ( conf . Config , "" , " " )
if err != nil {
log . Error ( ) . Err ( err ) . Send ( )
return 1
}
|
convert configuration to string and displays it to standard output
|
fmt . Println ( string ( configBytes ) )
return ExitStatusOK
}
|
printEnv function prints all environment variables to standard output.
|
func printEnv ( ) ExitCode {
for _ , keyVal := range os . Environ ( ) {
fmt . Println ( keyVal )
}
return ExitStatusOK
}
|
startService function starts service and returns error code.
|
func startServer ( ) ExitCode {
_ = conf . GetSetupConfiguration ( )
serverCfg := conf . GetServerConfiguration ( )
metricsCfg := conf . GetMetricsConfiguration ( )
servicesCfg := conf . GetServicesConfiguration ( )
amsConfig := conf . GetAMSClientConfiguration ( )
redisConf := conf . GetRedisConfiguration ( )
rbacCfg := conf . GetRBACConfiguration ( )
groupsChannel := make ( chan [ ] groups . Group )
errorFoundChannel := make ( chan bool )
errorChannel := make ( chan error )
if metricsCfg . Namespace != "" {
metrics . AddAPIMetricsWithNamespace ( metricsCfg . Namespace )
}
amsClient , err := amsclient . NewAMSClient ( amsConfig )
if err != nil {
log . Error ( ) . Err ( err ) . Msg ( "Cannot init the AMSClient, using old approach" )
amsClient = nil
} else {
log . Info ( ) . Msg ( "AMSClient successfully created" )
}
redisClient , err := services . NewRedisClient ( redisConf )
if err != nil {
log . Error ( ) . Err ( err ) . Msg ( "failed to initialize Redis server" )
return ExitStatusServerError
}
if err = redisClient . HealthCheck ( ) ; err != nil {
log . Error ( ) . Err ( err ) . Msg ( "failed to ping Redis server" )
return ExitStatusServerError
}
log . Info ( ) . Msg ( "Redis client created, Redis server is responding" )
rbac , err := auth . NewRBACClient ( & rbacCfg )
if err != nil {
log . Error ( ) . Err ( err ) . Msg ( "failed to initialize RBAC client" )
return ExitStatusServerError
}
serverInstance = server . New ( serverCfg , servicesCfg , amsClient , redisClient , groupsChannel , errorFoundChannel , errorChannel , rbac )
|
fill-in additional info used by /info endpoint handler
|
fillInInfoParams ( serverInstance . InfoParams )
proxy_content . SetContentDirectoryTimeout ( servicesCfg . ContentDirectoryTimeout )
go updateGroupInfo ( servicesCfg , groupsChannel , errorFoundChannel , errorChannel )
go proxy_content . RunUpdateContentLoop ( servicesCfg )
err = serverInstance . Start ( )
if err != nil {
log . Error ( ) . Err ( err ) . Msg ( "HTTP(s) start error" )
return ExitStatusServerError
}
return ExitStatusOK
}
|
fillInInfoParams function fills-in additional info used by /info endpoint
handler
|
func fillInInfoParams ( params map [ string ] string ) {
params [ "BuildVersion" ] = BuildVersion
params [ "BuildTime" ] = BuildTime
params [ "BuildBranch" ] = BuildBranch
params [ "BuildCommit" ] = BuildCommit
params [ "UtilsVersion" ] = UtilsVersion
}
|
updateGroupInfo function is run in a goroutine. It runs forever, waiting for 1 of 2 events: a Ticker or a channel
* If ticker comes first, the groups configuration is updated, doing a request to the content-service
* If the channel comes first, the latest valid groups configuration is send through the channel
|
func updateGroupInfo ( servicesConf services . Configuration ,
groupsChannel chan [ ] groups . Group ,
errorFoundChannel chan bool ,
errorChannel chan error ) {
var currentGroups [ ] groups . Group
var currentError error
var currentErrorFound bool
retrievedGroups , err := services . GetGroups ( servicesConf )
if err != nil {
log . Error ( ) . Err ( err ) . Msg ( "Error retrieving groups" )
currentErrorFound = true
var e * url . Error
if errors . As ( err , & e ) {
currentError = & server . ContentServiceUnavailableError { }
} else {
currentError = err
}
} else {
currentErrorFound = false
currentGroups = retrievedGroups
}
uptimeTicker := time . NewTicker ( servicesConf . GroupsPollingTime )
log . Info ( ) . Msgf ( "Updating groups configuration each %f seconds" , servicesConf . GroupsPollingTime . Seconds ( ) )
for {
select {
case <- uptimeTicker . C :
retrievedGroups , err = services . GetGroups ( servicesConf )
currentGroups , currentErrorFound , currentError = handleGroupError ( err , currentGroups , retrievedGroups )
case errorFoundChannel <- currentErrorFound :
case errorChannel <- currentError :
case groupsChannel <- currentGroups :
}
}
}
|
handleGroupError handles error after retrieving groups info in updateGroupInfo
|
func handleGroupError ( err error ,
currentGroups [ ] groups . Group ,
newGroups [ ] groups . Group ) ( [ ] groups . Group , bool , error ) {
if err != nil {
log . Error ( ) . Err ( err ) . Msg ( "Error retrieving groups" )
var e * url . Error
if errors . As ( err , & e ) {
return currentGroups , true , & server . ContentServiceUnavailableError { }
}
return currentGroups , true , err
}
return newGroups , false , nil
}
|
handleCommand select the function to be called depending on command argument
|
func handleCommand ( command string ) ExitCode {
switch command {
case "start-service" :
return startServer ( )
case "print-version" :
printVersionInfo ( )
return ExitStatusOK
case "print-help" :
printHelp ( )
return ExitStatusOK
case "print-config" :
printConfig ( )
return ExitStatusOK
case "print-env" :
printEnv ( )
return ExitStatusOK
}
return ExitStatusOK
}
|
main represents entry point to CLI client.
|
func main ( ) {
err := conf . LoadConfiguration ( defaultConfigFileName )
if err != nil {
panic ( err )
}
err = logger . InitZerolog (
conf . GetLoggingConfiguration ( ) ,
conf . GetCloudWatchConfiguration ( ) ,
conf . GetSentryLoggingConfiguration ( ) ,
conf . GetKafkaZerologConfiguration ( ) ,
)
if err != nil {
panic ( err )
}
var (
showHelp bool
showVersion bool
)
flag . BoolVar ( & showHelp , "help" , false , "Show the help" )
flag . BoolVar ( & showVersion , "version" , false , "Show the version and exit" )
flag . Parse ( )
if showHelp {
os . Exit ( int ( printHelp ( ) ) )
}
if showVersion {
printVersionInfo ( )
os . Exit ( ExitStatusOK )
}
args := flag . Args ( )
command := "start-service"
if len ( args ) >= 1 {
command = strings . ToLower ( strings . TrimSpace ( args [ 0 ] ) )
}
os . Exit ( int ( handleCommand ( command ) ) )
}
|