From 69faadd1d613bbdfc3e7546f34065275a12ddb83 Mon Sep 17 00:00:00 2001 From: heedaeshin Date: Wed, 2 Oct 2024 17:54:02 +0900 Subject: [PATCH] Feat: Add Read System - Add GET /readZ - validProfile - Add docker lxc healthcheck --- Dockerfile | 2 +- config/profile.go | 85 +++++++++++++++++++++++ docker-compose.yaml | 8 +++ websrc/controllers/healthCheckHandlers.go | 55 +++++++++++++++ websrc/controllers/publicfunc.go | 2 +- websrc/serve/serve.go | 1 + 6 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 websrc/controllers/healthCheckHandlers.go diff --git a/Dockerfile b/Dockerfile index 96bc069..0e70747 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,7 @@ ARG USER=root ARG GROUP=root #------------------------------------------------------------- RUN apt-get update -RUN apt-get install ca-certificates -y +RUN apt-get install ca-certificates curl -y #------------------------------------------------------------- # User Set RUN if [ "${USER}" != "root" ]; then \ diff --git a/config/profile.go b/config/profile.go index 067ba50..a9d1c6d 100644 --- a/config/profile.go +++ b/config/profile.go @@ -3,6 +3,7 @@ package config import ( "encoding/json" "errors" + "fmt" "io" "os" "path/filepath" @@ -180,3 +181,87 @@ func (fpm *FileProfileManager) LoadCredentialsByProfile(profileName string, prov return nil, errors.New("unsupported provider") } } + +// ValidateProfiles checks that at least one profile exists, each profile has at least one credential, +// and that all provided credentials have non-empty required fields. +func (fpm *FileProfileManager) ValidateProfiles() error { + fpm.mu.Lock() + defer fpm.mu.Unlock() + + // Open the profile file + file, err := os.Open(fpm.profileFilePath) + if err != nil { + return fmt.Errorf("unable to open profile file: %v", err) + } + defer file.Close() + + // Read the file content + data, err := io.ReadAll(file) + if err != nil { + return fmt.Errorf("unable to read profile file: %v", err) + } + + // Unmarshal JSON data into profiles + var profiles []struct { + ProfileName string `json:"profileName"` + Credentials models.ProfileCredentials `json:"credentials"` + } + + if err := json.Unmarshal(data, &profiles); err != nil { + return fmt.Errorf("unable to parse profile JSON: %v", err) + } + + // Check if there are any profiles + if len(profiles) == 0 { + return errors.New("no profiles found") + } + + // Validate each profile's credentials + for _, profile := range profiles { + if profile.ProfileName == "" { + return errors.New("a profile has an empty name") + } + + creds := profile.Credentials + + // Flag to check if at least one credential is present + hasAtLeastOneCredential := false + + // Validate AWS credentials if present + if creds.AWS.AccessKey != "" || creds.AWS.SecretKey != "" { + hasAtLeastOneCredential = true + if creds.AWS.AccessKey == "" { + return fmt.Errorf("AWS AccessKey for profile '%s' is missing", profile.ProfileName) + } + if creds.AWS.SecretKey == "" { + return fmt.Errorf("AWS SecretKey for profile '%s' is missing", profile.ProfileName) + } + } + + // Validate NCP credentials if present + if creds.NCP.AccessKey != "" || creds.NCP.SecretKey != "" { + hasAtLeastOneCredential = true + if creds.NCP.AccessKey == "" { + return fmt.Errorf("NCP AccessKey for profile '%s' is missing", profile.ProfileName) + } + if creds.NCP.SecretKey == "" { + return fmt.Errorf("NCP SecretKey for profile '%s' is missing", profile.ProfileName) + } + } + + // Validate GCP credentials if present + if creds.GCP.PrivateKeyID != "" { + hasAtLeastOneCredential = true + if creds.GCP.PrivateKeyID == "" { + return fmt.Errorf("GCP PrivateKeyID for profile '%s' is missing", profile.ProfileName) + } + } + + // Ensure that at least one credential is present + if !hasAtLeastOneCredential { + return fmt.Errorf("profile '%s' must have at least one set of credentials (AWS, NCP, or GCP)", profile.ProfileName) + } + } + + return nil +} diff --git a/docker-compose.yaml b/docker-compose.yaml index 20b4867..c8b5997 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -32,6 +32,14 @@ services: - /etc/localtime:/etc/localtime:ro env_file: - .env + # Health check configuration + # OK [ 2xx ,3xx}, ERR [4xx,5xx,...etc] + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3300/readyZ"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s ################## ## OPTIONAL ## diff --git a/websrc/controllers/healthCheckHandlers.go b/websrc/controllers/healthCheckHandlers.go new file mode 100644 index 0000000..33388bb --- /dev/null +++ b/websrc/controllers/healthCheckHandlers.go @@ -0,0 +1,55 @@ +/* +Copyright 2023 The Cloud-Barista Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package controllers + +import ( + "net/http" + "time" + + "github.com/cloud-barista/mc-data-manager/config" + "github.com/cloud-barista/mc-data-manager/models" + "github.com/labstack/echo/v4" +) + +// GetSystemReadyHandler godoc +// +// @ID GetSystemReadyHandler +// @Summary Get System Ready Handler +// @Description Get System Ready +// @Tags [Ready] +// @Produce json +// @Success 200 {object} models.BasicResponse "System is Ready" +// @Failure 404 {object} models.BasicResponse "Profile Load , Failed: err" +// @Router /readyZ [Get] +func GetSystemReadyHandler(ctx echo.Context) error { + start := time.Now() + logger, logstrings := pageLogInit(ctx, "healthcheck-task", "Ready?", start) + credentailManger := config.NewProfileManager() + err := credentailManger.ValidateProfiles() + if err != nil { + errStr := "Profile Load , Failed : " + err.Error() + logger.Error().Msg(errStr) + return ctx.JSON(http.StatusNotFound, models.BasicResponse{ + Result: logstrings.String(), + Error: &errStr, + }) + } + jobEnd(logger, "System is Ready", start) + return ctx.JSON(http.StatusOK, models.BasicResponse{ + Result: logstrings.String(), + Error: nil, + }) +} diff --git a/websrc/controllers/publicfunc.go b/websrc/controllers/publicfunc.go index 9fb3044..40c0dee 100644 --- a/websrc/controllers/publicfunc.go +++ b/websrc/controllers/publicfunc.go @@ -99,7 +99,7 @@ func pageLogInit(c echo.Context, pageName, pageInfo string, startTime time.Time) logger := parentLogger.Output(multiWriter) // Log page access information - logger.Info().Msgf("%s post page accessed", pageName) + logger.Info().Msgf("%s page accessed", pageName) logger.Info().Msg(pageInfo) logger.Info().Str("start time", startTime.Format(time.RFC3339)) diff --git a/websrc/serve/serve.go b/websrc/serve/serve.go index 3994c76..d6aeba1 100644 --- a/websrc/serve/serve.go +++ b/websrc/serve/serve.go @@ -136,6 +136,7 @@ func InitServer(port string, addIP ...string) *echo.Echo { e.GET("/swagger/*", echoSwagger.WrapHandler) e.GET("/", controllers.MainGetHandler) + e.GET("/readyZ", controllers.GetSystemReadyHandler) migrationGroup := e.Group("/migrate") routes.MigrationRoutes(migrationGroup)