diff --git a/pkg/upgradeservice/handlers/handlers.go b/pkg/upgradeservice/handlers/handlers.go index 4a98a28d62..210eac18b8 100644 --- a/pkg/upgradeservice/handlers/handlers.go +++ b/pkg/upgradeservice/handlers/handlers.go @@ -34,6 +34,9 @@ func RegisterRoutes(r *mux.Router, handler UpgradeServiceHandler) { subRouter.Path("/liveconfig").Methods("POST").HandlerFunc(handler.LiveAppConfig) subRouter.Path("/config").Methods("PUT").HandlerFunc(handler.SaveAppConfig) subRouter.Path("/config/{filename}/download").Methods("GET").HandlerFunc(handler.DownloadFileFromConfig) + + subRouter.Path("/preflight/run").Methods("POST").HandlerFunc(handler.StartPreflightChecks) + subRouter.Path("/preflight/result").Methods("GET").HandlerFunc(handler.GetPreflightResult) } func JSON(w http.ResponseWriter, code int, payload interface{}) { diff --git a/pkg/upgradeservice/handlers/interface.go b/pkg/upgradeservice/handlers/interface.go index 6bcb314e17..eec2059730 100644 --- a/pkg/upgradeservice/handlers/interface.go +++ b/pkg/upgradeservice/handlers/interface.go @@ -9,4 +9,7 @@ type UpgradeServiceHandler interface { LiveAppConfig(w http.ResponseWriter, r *http.Request) SaveAppConfig(w http.ResponseWriter, r *http.Request) DownloadFileFromConfig(w http.ResponseWriter, r *http.Request) + + StartPreflightChecks(w http.ResponseWriter, r *http.Request) + GetPreflightResult(w http.ResponseWriter, r *http.Request) } diff --git a/pkg/upgradeservice/handlers/preflight.go b/pkg/upgradeservice/handlers/preflight.go new file mode 100644 index 0000000000..ca008c275a --- /dev/null +++ b/pkg/upgradeservice/handlers/preflight.go @@ -0,0 +1,105 @@ +package handlers + +import ( + "net/http" + + "github.com/gorilla/mux" + "github.com/pkg/errors" + apptypes "github.com/replicatedhq/kots/pkg/app/types" + "github.com/replicatedhq/kots/pkg/kotsutil" + "github.com/replicatedhq/kots/pkg/logger" + preflighttypes "github.com/replicatedhq/kots/pkg/preflight/types" + registrytypes "github.com/replicatedhq/kots/pkg/registry/types" + "github.com/replicatedhq/kots/pkg/reporting" + upgradepreflight "github.com/replicatedhq/kots/pkg/upgradeservice/preflight" +) + +type GetPreflightResultResponse struct { + PreflightProgress string `json:"preflightProgress,omitempty"` + PreflightResult preflighttypes.PreflightResult `json:"preflightResult"` +} + +func (h *Handler) StartPreflightChecks(w http.ResponseWriter, r *http.Request) { + params := GetContextParams(r) + appSlug := mux.Vars(r)["appSlug"] + + if params.AppSlug != appSlug { + logger.Error(errors.Errorf("app slug in path %s does not match app slug in context %s", appSlug, params.AppSlug)) + w.WriteHeader(http.StatusForbidden) + return + } + + appLicense, err := kotsutil.LoadLicenseFromBytes([]byte(params.AppLicense)) + if err != nil { + logger.Error(errors.Wrap(err, "failed to load license from bytes")) + w.WriteHeader(http.StatusInternalServerError) + return + } + + app := &apptypes.App{ + ID: params.AppID, + Slug: params.AppSlug, + IsAirgap: params.AppIsAirgap, + IsGitOps: params.AppIsGitOps, + } + + localRegistry := registrytypes.RegistrySettings{ + Hostname: params.RegistryEndpoint, + Username: params.RegistryUsername, + Password: params.RegistryPassword, + Namespace: params.RegistryNamespace, + IsReadOnly: params.RegistryIsReadOnly, + } + + if err := upgradepreflight.ResetPreflightData(); err != nil { + logger.Error(errors.Wrap(err, "failed to reset preflight data")) + w.WriteHeader(http.StatusInternalServerError) + return + } + + reportingFn := func() error { + if params.AppIsAirgap { + // TODO NOW: airgap reporting + return nil + } + return reporting.SendOnlineAppInfo(appLicense, params.ReportingInfo) + } + + go func() { + if err := upgradepreflight.Run(app, params.BaseArchive, params.NextSequence, localRegistry, false, reportingFn); err != nil { + logger.Error(errors.Wrap(err, "failed to run preflights")) + return + } + }() + + JSON(w, http.StatusOK, struct{}{}) +} + +func (h *Handler) GetPreflightResult(w http.ResponseWriter, r *http.Request) { + params := GetContextParams(r) + appSlug := mux.Vars(r)["appSlug"] + + if params.AppSlug != appSlug { + logger.Error(errors.Errorf("app slug in path %s does not match app slug in context %s", appSlug, params.AppSlug)) + w.WriteHeader(http.StatusForbidden) + return + } + + preflightData, err := upgradepreflight.GetPreflightData() + if err != nil { + logger.Error(errors.Wrap(err, "failed to get preflight data")) + w.WriteHeader(http.StatusInternalServerError) + return + } + + var preflightResult preflighttypes.PreflightResult + if preflightData.Result != nil { + preflightResult = *preflightData.Result + } + + response := GetPreflightResultResponse{ + PreflightResult: preflightResult, + PreflightProgress: preflightData.Progress, + } + JSON(w, 200, response) +} diff --git a/pkg/upgradeservice/preflight/preflight.go b/pkg/upgradeservice/preflight/preflight.go index 7428964192..8bce434f22 100644 --- a/pkg/upgradeservice/preflight/preflight.go +++ b/pkg/upgradeservice/preflight/preflight.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path/filepath" + "time" "github.com/pkg/errors" apptypes "github.com/replicatedhq/kots/pkg/app/types" @@ -24,8 +25,8 @@ import ( ) type PreflightData struct { - Progress map[string]interface{} `json:"progress"` - Results *types.PreflightResults `json:"results"` + Progress string `json:"progress,omitempty"` + Result *types.PreflightResult `json:"result"` } var PreflightDataFilepath string @@ -107,7 +108,7 @@ func Run(app *apptypes.App, archiveDir string, sequence int64, registrySettings }, }, } - if err := setPreflightResults(preflightResults); err != nil { + if err := setPreflightResults(app.Slug, preflightResults); err != nil { logger.Error(errors.Wrap(err, "failed to set preflight results")) return } @@ -126,7 +127,11 @@ func Run(app *apptypes.App, archiveDir string, sequence int64, registrySettings zap.String("appID", app.ID), zap.Int64("sequence", sequence)) - _, err := preflightpkg.Execute(preflight, ignoreRBAC, setPreflightProgress, setPreflightResults) + setResults := func(results *types.PreflightResults) error { + return setPreflightResults(app.Slug, results) + } + + _, err := preflightpkg.Execute(preflight, ignoreRBAC, setPreflightProgress, setResults) if err != nil { logger.Error(errors.Wrap(err, "failed to run preflight checks")) return @@ -142,31 +147,58 @@ func Run(app *apptypes.App, archiveDir string, sequence int64, registrySettings return nil } -func setPreflightResults(results *types.PreflightResults) error { - preflightData, err := getPreflightData() +func setPreflightResults(appSlug string, results *types.PreflightResults) error { + resultsBytes, err := json.Marshal(results) if err != nil { - return errors.Wrap(err, "failed to get preflight data") + return errors.Wrap(err, "failed to marshal preflight results") + } + createdAt := time.Now() + preflightData := &PreflightData{ + Result: &types.PreflightResult{ + Result: string(resultsBytes), + CreatedAt: &createdAt, + AppSlug: appSlug, + ClusterSlug: "this-cluster", + Skipped: false, + HasFailingStrictPreflights: hasFailingStrictPreflights(results), + }, + Progress: "", // clear the progress once the results are set } - preflightData.Results = results if err := setPreflightData(preflightData); err != nil { return errors.Wrap(err, "failed to set preflight results") } return nil } +func hasFailingStrictPreflights(results *types.PreflightResults) bool { + // convert to troubleshoot type so we can use the existing function + uploadResults := &troubleshootpreflight.UploadPreflightResults{} + uploadResults.Results = results.Results + for _, e := range results.Errors { + uploadResults.Errors = append(uploadResults.Errors, &troubleshootpreflight.UploadPreflightError{ + Error: e.Error, + }) + } + return troubleshootpreflight.HasStrictAnalyzersFailed(uploadResults) +} + func setPreflightProgress(progress map[string]interface{}) error { - preflightData, err := getPreflightData() + preflightData, err := GetPreflightData() if err != nil { return errors.Wrap(err, "failed to get preflight data") } - preflightData.Progress = progress + progressBytes, err := json.Marshal(progress) + if err != nil { + return errors.Wrap(err, "failed to marshal preflight progress") + } + preflightData.Progress = string(progressBytes) if err := setPreflightData(preflightData); err != nil { return errors.Wrap(err, "failed to set preflight progress") } return nil } -func getPreflightData() (*PreflightData, error) { +func GetPreflightData() (*PreflightData, error) { var preflightData *PreflightData if _, err := os.Stat(PreflightDataFilepath); err != nil { if !os.IsNotExist(err) { @@ -195,3 +227,10 @@ func setPreflightData(preflightData *PreflightData) error { } return nil } + +func ResetPreflightData() error { + if err := os.RemoveAll(PreflightDataFilepath); err != nil { + return errors.Wrap(err, "failed to remove preflight data") + } + return nil +}