|
|
Package server contains implementation of REST API server (HTTPServer) for the
Insights Operator controller service.
|
package server
|
Generated documentation is available at:
https://godoc.org/github.com/RedHatInsights/insights-operator-controller/server
Documentation in literate-programming-style is available at:
https://redhatinsights.github.io/insights-operator-controller/packages/server/server.html
|
import (
"crypto/tls"
"crypto/x509"
"fmt"
"github.com/RedHatInsights/insights-operator-controller/logging"
"github.com/RedHatInsights/insights-operator-controller/storage"
"github.com/RedHatInsights/insights-operator-utils/env"
"github.com/RedHatInsights/insights-operator-utils/responses"
"github.com/gorilla/mux"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
"io"
"log"
"net/http"
"os"
"strconv"
"time"
)
|
Server data type represents configuration of server
|
type Server struct {
Address string
UseHTTPS bool
Storage storage . Storage
Splunk logging . Client
TLSCert string
TLSKey string
ClusterQuery * storage . ClusterQuery
}
|
APIPrefix is appended before all REST API endpoint addresses
|
var APIPrefix = env . GetEnv ( "CONTROLLER_PREFIX" , "/api/v1/" )
|
Environment CONTROLLER_ENV const for specifying production vs test environment
|
var Environment = os . Getenv ( "CONTROLLER_ENV" )
|
Prometheus metric with counter of REST API requests
|
var apiRequests = promauto . NewCounterVec ( prometheus . CounterOpts {
Name : "api_endpoints_requests" ,
Help : "The total number requests per API endpoint" ,
} , [ ] string { "url" } )
|
Prometheus metric with response times
|
var apiResponses = promauto . NewHistogramVec ( prometheus . HistogramOpts {
Name : "response_time" ,
Help : "Response time" ,
Buckets : prometheus . LinearBuckets ( 0 , 20 , 20 ) ,
} , [ ] string { "url" } )
|
checkSplunkOperation checks whether the Splunk operation (write/record) was successful
|
func checkSplunkOperation ( err error ) {
if err != nil {
log . Println ( "(not critical) Log into splunk failed" , err )
}
}
|
createTLSServer methods creates an instance of HTTPS server using TLS
|
func ( s Server ) createTLSServer ( router http . Handler ) * http . Server {
caCert , err := os . ReadFile ( s . TLSCert )
if err != nil {
log . Fatal ( err )
}
caCertPool := x509 . NewCertPool ( )
caCertPool . AppendCertsFromPEM ( caCert )
|
disable "G402 (CWE-295): TLS MinVersion too low. (Confidence: HIGH, Severity: HIGH)"
|
tlsConfig := & tls . Config {
ClientCAs : caCertPool ,
ClientAuth : tls . RequireAndVerifyClientCert ,
}
server := & http . Server {
Addr : s . Address ,
TLSConfig : tlsConfig ,
Handler : router ,
ReadTimeout : 1 * time . Minute ,
ReadHeaderTimeout : 5 * time . Second ,
WriteTimeout : 30 * time . Second ,
}
return server
}
|
countEndpoint function measures time to process the request and updates Prometheus metric accordingly
|
func countEndpoint ( request * http . Request , start time . Time ) {
url := request . URL . String ( )
duration := time . Since ( start )
log . Printf ( "Time to serve the page: %s\n" , duration )
apiRequests . With ( prometheus . Labels { "url" : url } ) . Inc ( )
apiResponses . With ( prometheus . Labels { "url" : url } ) . Observe ( float64 ( duration . Microseconds ( ) ) )
}
|
retrievePositiveIntRequestParameter gets param with paramName converts to int and checks
if it's positive
|
func retrievePositiveIntRequestParameter ( request * http . Request , paramName string ) ( int64 , error ) {
strValue , found := mux . Vars ( request ) [ paramName ]
if ! found {
return 0 , fmt . Errorf ( "'%v' param not found" , paramName )
}
intValue , err := strconv . ParseInt ( strValue , 10 , 0 )
if err != nil {
return 0 , err
}
if intValue < 0 {
return 0 , fmt . Errorf ( "'%v' param cannot be negative" , paramName )
}
return intValue , nil
}
|
retrieveIDRequestParameter helper function reads value of parameter named "id"
|
func retrieveIDRequestParameter ( request * http . Request ) ( int64 , error ) {
return retrievePositiveIntRequestParameter ( request , "id" )
}
|
MainEndpoint method is handler for the main endpoint of REST API server
|
func ( s Server ) MainEndpoint ( writer http . ResponseWriter , request * http . Request ) {
start := time . Now ( )
_ , err := io . WriteString ( writer , "Hello world!\n" )
if err != nil {
log . Println ( "Error preparing response" , err )
}
countEndpoint ( request , start )
}
|
logRequestHandler is an implementation of middleware for logging request parameters
|
func logRequestHandler ( writer http . ResponseWriter , request * http . Request , nextHandler http . Handler ) {
log . Println ( "Request URI: " + request . RequestURI )
log . Println ( "Request method: " + request . Method )
nextHandler . ServeHTTP ( writer , request )
}
|
LogRequest method represents middleware for loging requests
|
func ( s Server ) LogRequest ( nextHandler http . Handler ) http . Handler {
return http . HandlerFunc (
func ( writer http . ResponseWriter , request * http . Request ) {
logRequestHandler ( writer , request , nextHandler )
} )
}
|
AddDefaultHeaders method represents middleware for adding headers that should be in any response
|
func ( s Server ) AddDefaultHeaders ( nextHandler http . Handler ) http . Handler {
return http . HandlerFunc (
func ( w http . ResponseWriter , r * http . Request ) {
if Environment != "production" {
if origin := r . Header . Get ( "Origin" ) ; origin != "" {
w . Header ( ) . Set ( "Access-Control-Allow-Origin" , origin )
}
w . Header ( ) . Set ( "Access-Control-Allow-Methods" , "POST, GET, OPTIONS, PUT, DELETE" )
w . Header ( ) . Set ( "Access-Control-Allow-Headers" , "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token" )
w . Header ( ) . Set ( "Access-Control-Allow-Credentials" , "true" )
}
w . Header ( ) . Set ( "Content-Type" , "application/json; charset=utf-8" )
nextHandler . ServeHTTP ( w , r )
} )
}
|
Initialize perform the server initialization
|
func ( s Server ) Initialize ( ) {
log . Println ( "Environment: " , Environment )
log . Println ( "API Prefix: " , APIPrefix )
log . Println ( "Initializing HTTP server at" , s . Address )
s . ClusterQuery = storage . NewClusterQuery ( s . Storage )
router := mux . NewRouter ( ) . StrictSlash ( true )
router . Use ( s . LogRequest )
if Environment == "production" {
log . Println ( "Server is running in PRODUCTION mode" )
log . Println ( "JWT authentication is enabled" )
router . Use ( s . JWTAuthentication )
} else {
log . Println ( "Server is running in DEBUG mode" )
log . Println ( "JWT authentication is disabled" )
}
router . Use ( s . AddDefaultHeaders )
|
common REST API endpoints
|
router . HandleFunc ( APIPrefix , s . MainEndpoint ) . Methods ( "GET" )
|
REST API endpoints used by client
|
clientRouter := router . PathPrefix ( APIPrefix + "client" ) . Subrouter ( )
|
clusters-related operations
(handlers are implemented in the file cluster.go)
|
clientRouter . HandleFunc ( "/cluster" , s . GetClusters ) . Methods ( "GET" )
clientRouter . HandleFunc ( "/cluster/{name}" , s . NewCluster ) . Methods ( "POST" )
clientRouter . HandleFunc ( "/cluster/{id:[0-9]+}" , s . GetClusterByID ) . Methods ( "GET" )
clientRouter . HandleFunc ( "/cluster/{id:[0-9]+}" , s . DeleteCluster ) . Methods ( "DELETE" )
clientRouter . HandleFunc ( "/cluster/search" , s . SearchCluster ) . Methods ( "GET" )
|
configuration profiles
(handlers are implemented in the file profile.go)
|
clientRouter . HandleFunc ( "/profile" , s . ListConfigurationProfiles ) . Methods ( "GET" )
clientRouter . HandleFunc ( "/profile/{id}" , s . GetConfigurationProfile ) . Methods ( "GET" )
clientRouter . HandleFunc ( "/profile/{id}" , s . ChangeConfigurationProfile ) . Methods ( "PUT" )
clientRouter . HandleFunc ( "/profile" , s . NewConfigurationProfile ) . Methods ( "POST" )
clientRouter . HandleFunc ( "/profile/{id}" , s . DeleteConfigurationProfile ) . Methods ( "DELETE" )
|
configurations
(handlers are implemented in the file configuration.go)
|
clientRouter . HandleFunc ( "/configuration" , s . GetAllConfigurations ) . Methods ( "GET" )
clientRouter . HandleFunc ( "/configuration/{id}" , s . GetConfiguration ) . Methods ( "GET" )
clientRouter . HandleFunc ( "/configuration/{id}" , s . DeleteConfiguration ) . Methods ( "DELETE" )
clientRouter . HandleFunc ( "/configuration/{id}/enable" , s . EnableConfiguration ) . Methods ( "PUT" )
clientRouter . HandleFunc ( "/configuration/{id}/disable" , s . DisableConfiguration ) . Methods ( "PUT" )
|
clusters and its configurations
(handlers are implemented in the file configuration.go)
|
clientRouter . HandleFunc ( "/cluster/{cluster}/configuration" , s . GetClusterConfiguration ) . Methods ( "GET" )
clientRouter . HandleFunc ( "/cluster/{cluster}/configuration/create" , s . NewClusterConfiguration ) . Methods ( "POST" )
clientRouter . HandleFunc ( "/cluster/{cluster}/configuration/enable" , s . EnableClusterConfiguration ) . Methods ( "PUT" )
clientRouter . HandleFunc ( "/cluster/{cluster}/configuration/disable" , s . DisableClusterConfiguration ) . Methods ( "PUT" )
|
triggers
|
clientRouter . HandleFunc ( "/trigger" , s . GetAllTriggers ) . Methods ( "GET" )
clientRouter . HandleFunc ( "/trigger/{id}" , s . GetTrigger ) . Methods ( "GET" )
clientRouter . HandleFunc ( "/trigger/{id}" , s . DeleteTrigger ) . Methods ( "DELETE" )
clientRouter . HandleFunc ( "/trigger/{id}/activate" , s . ActivateTrigger ) . Methods ( "PUT" , "POST" )
clientRouter . HandleFunc ( "/trigger/{id}/deactivate" , s . DeactivateTrigger ) . Methods ( "PUT" , "POST" )
clientRouter . HandleFunc ( "/cluster/{cluster}/trigger" , s . GetClusterTriggers ) . Methods ( "GET" )
clientRouter . HandleFunc ( "/cluster/{cluster}/trigger/{trigger}" , s . RegisterClusterTrigger ) . Methods ( "POST" )
|
REST API endpoints used by insights operator
(handlers are implemented in the file operator.go)
|
operatorRouter := router . PathPrefix ( APIPrefix + "operator" ) . Subrouter ( )
operatorRouter . HandleFunc ( "/register/{cluster}" , s . RegisterCluster ) . Methods ( "GET" , "PUT" )
operatorRouter . HandleFunc ( "/configuration/{cluster}" , s . ReadConfigurationForOperator ) . Methods ( "GET" )
operatorRouter . HandleFunc ( "/triggers/{cluster}" , s . GetActiveTriggersForCluster ) . Methods ( "GET" )
operatorRouter . HandleFunc ( "/trigger/{cluster}/ack/{trigger}" , s . AckTriggerForCluster ) . Methods ( "GET" , "PUT" )
|
Prometheus metrics
|
router . Handle ( "/metrics" , promhttp . Handler ( ) ) . Methods ( "GET" )
log . Println ( "Starting HTTP server at" , s . Address )
|
try to record the action StartService into Splunk
|
err := s . Splunk . Log ( "Action" , "starting service at address " + s . Address )
|
and check whether the Splunk operation was successful
|
checkSplunkOperation ( err )
if s . UseHTTPS {
server := s . createTLSServer ( router )
err = server . ListenAndServeTLS ( s . TLSCert , s . TLSKey )
} else {
err = http . ListenAndServe ( s . Address , router )
}
if err != nil {
log . Fatal ( "Unable to initialize HTTP server" , err )
|
try to record the Error into Splunk
|
err = s . Splunk . Log ( "Error" , "service can not be started at address " + s . Address )
|
and check whether the Splunk operation was successful
|
checkSplunkOperation ( err )
|
TODO: name the magic constant 2
|
os . Exit ( 2 )
}
}
|
UnableToSendServerResponse function log an error when server response can
not be delivered to client.
|
func UnableToSendServerResponse ( err error ) {
log . Println ( "Unable to send server response" , err )
}
|
UnableToSendOKResponse function log an error when server response can
not be delivered to client.
|
func UnableToSendOKResponse ( err error ) {
log . Println ( "Unable to send server 'OK' response" , err )
}
|
UnableToSendCreatedResponse function log an error when server response can
not be delivered to client.
|
func UnableToSendCreatedResponse ( err error ) {
log . Println ( "Unable to send server 'Created' response" , err )
}
|
UnableToSendBadRequestServerResponse function log an error when server
response can not be delivered to client.
|
func UnableToSendBadRequestServerResponse ( err error ) {
log . Println ( "Unable to send bad request server response" , err )
}
|
UnableToSendInternalServerErrorResponse function log an error when server
response can not be delivered to client.
|
func UnableToSendInternalServerErrorResponse ( err error ) {
log . Println ( "Unable to send internal server error response" , err )
}
|
TryToSendInternalServerError function tries to send server response with
internal server error info.
|
func TryToSendInternalServerError ( writer http . ResponseWriter , message string ) {
err := responses . SendInternalServerError ( writer , message )
if err != nil {
UnableToSendInternalServerErrorResponse ( err )
}
}
|
TryToSendBadRequestServerResponse function tries to send server response with
bad request info.
|
func TryToSendBadRequestServerResponse ( writer http . ResponseWriter , message string ) {
err := responses . SendBadRequest ( writer , message )
if err != nil {
UnableToSendBadRequestServerResponse ( err )
}
}
|
TryToSendOKServerResponse function tries to send server response with
bad request info.
|
func TryToSendOKServerResponse ( writer http . ResponseWriter , payload map [ string ] interface { } ) {
err := responses . SendOK ( writer , payload )
if err != nil {
UnableToSendOKResponse ( err )
}
}
|
TryToSendCreatedServerResponse function tries to send server response with
info about created resource.
|
func TryToSendCreatedServerResponse ( writer http . ResponseWriter , payload map [ string ] interface { } ) {
err := responses . SendCreated ( writer , payload )
if err != nil {
UnableToSendCreatedResponse ( err )
}
}
|
TryToSendResponse function tries to send server response with any payload.
|
func TryToSendResponse ( httpStatus int , writer http . ResponseWriter , payload interface { } ) {
err := responses . Send ( httpStatus , writer , payload )
if err != nil {
UnableToSendServerResponse ( err )
}
}
|