Skip to content

Commit

Permalink
Fix health check when app path is not stripped
Browse files Browse the repository at this point in the history
  • Loading branch information
akclace committed Sep 9, 2024
1 parent 08403d1 commit 874e399
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 24 deletions.
4 changes: 2 additions & 2 deletions internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ const (
DOCKERFILE = "Dockerfile"
)

func (a *App) loadContainerManager(dryRun DryRun) error {
func (a *App) loadContainerManager(stripAppPath bool) error {
containerConfig, err := a.appDef.Attr("container")
if err != nil || containerConfig == starlark.None {
// Plugin not authorized, skip any container files
Expand Down Expand Up @@ -447,7 +447,7 @@ func (a *App) loadContainerManager(dryRun DryRun) error {
}

a.containerManager, err = NewContainerManager(a.Logger, a,
fileName, a.systemConfig, port, lifetime, scheme, health, buildDir, a.sourceFS, a.paramMap, a.appConfig.Container)
fileName, a.systemConfig, port, lifetime, scheme, health, buildDir, a.sourceFS, a.paramMap, a.appConfig.Container, stripAppPath)
if err != nil {
return fmt.Errorf("error creating container manager: %w", err)
}
Expand Down
24 changes: 19 additions & 5 deletions internal/app/container_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,12 @@ type ContainerManager struct {

// Health check related fields
healthCheckTicker *time.Ticker
stripAppPath bool
}

func NewContainerManager(logger *types.Logger, app *App, containerFile string,
systemConfig *types.SystemConfig, configPort int64, lifetime, scheme, health, buildDir string, sourceFS appfs.ReadableFS,
paramMap map[string]string, containerConfig types.Container) (*ContainerManager, error) {
paramMap map[string]string, containerConfig types.Container, stripAppPath bool) (*ContainerManager, error) {

image := ""
volumes := []string{}
Expand Down Expand Up @@ -140,6 +141,7 @@ func NewContainerManager(logger *types.Logger, app *App, containerFile string,
containerConfig: containerConfig,
stateLock: sync.RWMutex{},
currentState: ContainerStateUnknown,
stripAppPath: stripAppPath,
}

if containerConfig.IdleShutdownSecs > 0 && (!app.IsDev || containerConfig.IdleShutdownDevApps) {
Expand Down Expand Up @@ -444,17 +446,29 @@ func (m *ContainerManager) WaitForHealth(attempts int) error {

var err error
var resp *http.Response
url := m.GetProxyUrl()
if !m.stripAppPath {
// Apps like Streamlit require the app path to be present
url = url + m.app.Path
}

url += m.health

for attempt := 1; attempt <= attempts; attempt++ {
resp, err = client.Get(m.GetProxyUrl() + m.health)
if err == nil && resp.StatusCode == http.StatusOK {
return nil
resp, err = client.Get(url)
statusCode := "N/A"
if err == nil {
if resp.StatusCode == http.StatusOK {
return nil
}
statusCode = strconv.Itoa(resp.StatusCode)
}

if resp != nil {
resp.Body.Close()
}

m.Debug().Msgf("Attempt %d failed: %s", attempt, err)
m.Debug().Msgf("Attempt %d failed on %s : status %s err %s", attempt, url, statusCode, err)
time.Sleep(1 * time.Second)
}
return err
Expand Down
111 changes: 95 additions & 16 deletions internal/app/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,13 @@ func (a *App) loadStarlarkConfig(dryRun DryRun) error {
return err
}

var stripAppPath bool
if stripAppPath, err = a.checkAppPathStripping(); err != nil {
return err
}

// Load container config. The proxy config in routes depends on this being loaded first
if err = a.loadContainerManager(dryRun); err != nil {
if err = a.loadContainerManager(stripAppPath); err != nil {
return err
}

Expand Down Expand Up @@ -318,6 +323,63 @@ func verifyConfig(globals starlark.StringDict) (*starlarkstruct.Struct, error) {
return appDef, nil
}

// checkAppPathStripping checks if the app path should be stripped from the request path for container proxying
// This is required for container health checks.
func (a *App) checkAppPathStripping() (bool, error) {
appPathStripping := true
// Iterate through all the routes
routes, err := a.appDef.Attr("routes")
if err != nil {
return false, err
}

var ok bool
var routeList *starlark.List
if routeList, ok = routes.(*starlark.List); !ok {
return false, fmt.Errorf("routes is not a list")
}

iter := routeList.Iterate()
var val starlark.Value
var count int

for iter.Next(&val) {
count += 1
var pageDef *starlarkstruct.Struct
if pageDef, ok = val.(*starlarkstruct.Struct); !ok {
return false, fmt.Errorf("routes entry %d is not a struct", count)
}

_, err := pageDef.Attr("config")
if err == nil {
// "config" is defined, this must be a proxy config instead of a page definition
var configAttr starlark.HasAttrs
if configAttr, err = getProxyConfig(count, pageDef); err != nil {
return false, err
}

var urlValue starlark.Value
if urlValue, err = configAttr.Attr("Url"); err != nil {
return false, err
}

if urlValue.(starlark.String).GoString() != apptype.CONTAINER_URL {
// Not proxying to container url, ignore
continue
}

var stripAppValue starlark.Value
if stripAppValue, err = configAttr.Attr("StripApp"); err != nil {
return false, err
}

return bool(stripAppValue.(starlark.Bool)), nil
}
}

return appPathStripping, nil
}

func (a *App) initRouter() error {
var defaultHandler starlark.Callable
if a.globals.Has(apptype.DEFAULT_HANDLER) {
Expand Down Expand Up @@ -488,57 +550,74 @@ func (a *App) addRoute(count int, router *chi.Mux, routeVal starlark.Value, defa
return rootWildcard, nil
}

func (a *App) addProxyConfig(count int, router *chi.Mux, proxyDef *starlarkstruct.Struct) (bool, error) {
// getProxyConfig extracts the proxy config from the proxy definition
func getProxyConfig(count int, proxyDef *starlarkstruct.Struct) (starlark.HasAttrs, error) {
var err error
var pathStr string
rootWildcard := false

if pathStr, err = apptype.GetStringAttr(proxyDef, "path"); err != nil {
return rootWildcard, err
}

if pathStr == "/" {
rootWildcard = true // Root wildcard path, static files are not served
return nil, err
}

var ok bool
var responseAttr starlark.HasAttrs
pluginResponse, err := proxyDef.Attr("config")
if err != nil {
return rootWildcard, err
return nil, err
}
if responseAttr, ok = pluginResponse.(starlark.HasAttrs); !ok {
return rootWildcard, fmt.Errorf("proxy entry %d:%s is not a proxy response", count, pathStr)
return nil, fmt.Errorf("proxy entry %d:%s is not a proxy response", count, pathStr)
}

errorValue, err := responseAttr.Attr("error")
if err != nil {
return rootWildcard, fmt.Errorf("error in proxy config: %w", err)
return nil, fmt.Errorf("error in proxy config: %w", err)
}

if errorValue != nil && errorValue != starlark.None {
var errorString starlark.String
if errorString, ok = errorValue.(starlark.String); !ok {
return rootWildcard, fmt.Errorf("error in proxy config: %w", err)
return nil, fmt.Errorf("error in proxy config: %w", err)
}

if errorString.GoString() != "" {
return rootWildcard, fmt.Errorf("error in proxy config: %s", errorString.GoString())
return nil, fmt.Errorf("error in proxy config: %s", errorString.GoString())
}
}

config, err := responseAttr.Attr("value")
if err != nil {
return rootWildcard, err
return nil, err
}

if config.Type() != "ProxyConfig" {
return rootWildcard, fmt.Errorf("proxy entry %d:%s is not a proxy config", count, pathStr)
return nil, fmt.Errorf("proxy entry %d:%s is not a proxy config", count, pathStr)
}

var configAttr starlark.HasAttrs
if configAttr, ok = config.(starlark.HasAttrs); !ok {
return rootWildcard, fmt.Errorf("proxy entry %d:%s is not a proxy config attr", count, pathStr)
return nil, fmt.Errorf("proxy entry %d:%s is not a proxy config attr", count, pathStr)
}

return configAttr, nil
}

func (a *App) addProxyConfig(count int, router *chi.Mux, proxyDef *starlarkstruct.Struct) (bool, error) {
var err error
var pathStr string
rootWildcard := false

if pathStr, err = apptype.GetStringAttr(proxyDef, "path"); err != nil {
return rootWildcard, err
}

if pathStr == "/" {
rootWildcard = true // Root wildcard path, static files are not served
}

var configAttr starlark.HasAttrs
if configAttr, err = getProxyConfig(count, proxyDef); err != nil {
return rootWildcard, err
}

var urlValue, stripPathValue starlark.Value
Expand Down
2 changes: 1 addition & 1 deletion internal/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,8 +262,8 @@ type VersionMetadata struct {
type AppEntry struct {
Id AppId `json:"id"`
Path string `json:"path"`
MainApp AppId `json:"main_app"` // the id of the app that this app is linked to
Domain string `json:"domain"`
MainApp AppId `json:"main_app"` // the id of the app that this app is linked to
SourceUrl string `json:"source_url"`
IsDev bool `json:"is_dev"`
UserID string `json:"user_id"`
Expand Down

0 comments on commit 874e399

Please sign in to comment.