Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions internal/templates/docker/Dockerfile.layered.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ RUN chmod +x /usr/local/bin/compute-source-env.sh \
LABEL tee.launch_policy.log_redirect={{.LogRedirect}}
{{- end}}

LABEL tee.launch_policy.monitoring_memory_allow={{.MonitoringMemoryAllow}}
LABEL eigenx_cli_version={{.EigenXCLIVersion}}
LABEL eigenx_use_ita=True

Expand Down
23 changes: 15 additions & 8 deletions pkg/commands/app/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ var DeployCommand = &cli.Command{
common.EnvFlag,
common.FileFlag,
common.LogVisibilityFlag,
common.MemoryMonitoringFlag,
common.InstanceTypeFlag,
common.NameFlag,
common.WebsiteFlag,
Expand Down Expand Up @@ -82,14 +83,20 @@ func deployAction(cCtx *cli.Context) error {
return fmt.Errorf("failed to get log settings: %w", err)
}

// 9. Generate random salt
// 9. Get memory monitoring preference
monitoringMemoryAllow, err := utils.GetMemoryMonitoringSetting(cCtx)
if err != nil {
return fmt.Errorf("failed to get memory monitoring setting: %w", err)
}

// 10. Generate random salt
salt := [32]byte{}
_, err = rand.Read(salt[:])
if err != nil {
return fmt.Errorf("failed to generate random salt: %w", err)
}

// 10. Get app ID
// 11. Get app ID
_, appController, err := utils.GetAppControllerBinding(cCtx)
if err != nil {
return fmt.Errorf("failed to get app controller binding: %w", err)
Expand All @@ -99,19 +106,19 @@ func deployAction(cCtx *cli.Context) error {
return fmt.Errorf("failed to get app id: %w", err)
}

// 11. Prepare the release (includes build/push if needed, with automatic retry on permission errors)
release, imageRef, err := utils.PrepareReleaseFromContext(cCtx, preflightCtx.EnvironmentConfig, appIDToBeDeployed, dockerfilePath, imageRef, envFilePath, logRedirect, instanceType, 3)
// 12. Prepare the release (includes build/push if needed, with automatic retry on permission errors)
release, imageRef, err := utils.PrepareReleaseFromContext(cCtx, preflightCtx.EnvironmentConfig, appIDToBeDeployed, dockerfilePath, imageRef, envFilePath, logRedirect, monitoringMemoryAllow, instanceType, 3)
if err != nil {
return err
}

// 12. Deploy the app
// 13. Deploy the app
appID, err := preflightCtx.Caller.DeployApp(cCtx.Context, salt, release, publicLogs, imageRef)
if err != nil {
return fmt.Errorf("failed to deploy app: %w", err)
}

// 13. Collect app profile while deployment is in progress (optional)
// 14. Collect app profile while deployment is in progress (optional)
environment := preflightCtx.EnvironmentConfig.Name
suggestedName, err := utils.ExtractAndFindAvailableName(environment, imageRef)
if err != nil {
Expand All @@ -126,7 +133,7 @@ func deployAction(cCtx *cli.Context) error {
profile = nil
}

// 14. Upload profile if provided (non-blocking - warn on failure but don't fail deployment)
// 15. Upload profile if provided (non-blocking - warn on failure but don't fail deployment)
if profile != nil {
logger.Info("Uploading app profile...")
userApiClient, err := utils.NewUserApiClient(cCtx)
Expand All @@ -142,7 +149,7 @@ func deployAction(cCtx *cli.Context) error {
}
}

// 15. Watch until deployment completes
// 16. Watch until deployment completes
return utils.WatchUntilTransitionComplete(cCtx, appID, common.AppStatusDeploying)
}

Expand Down
17 changes: 12 additions & 5 deletions pkg/commands/app/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ var UpgradeCommand = &cli.Command{
common.EnvFlag,
common.FileFlag,
common.LogVisibilityFlag,
common.MemoryMonitoringFlag,
common.InstanceTypeFlag,
}...),
Action: upgradeAction,
Expand Down Expand Up @@ -78,27 +79,33 @@ func upgradeAction(cCtx *cli.Context) error {
return fmt.Errorf("failed to get log settings: %w", err)
}

// 10. Prepare the release (includes build/push if needed, with automatic retry on permission errors)
release, imageRef, err := utils.PrepareReleaseFromContext(cCtx, preflightCtx.EnvironmentConfig, appID, dockerfilePath, imageRef, envFilePath, logRedirect, instanceType, 3)
// 10. Get memory monitoring preference
monitoringMemoryAllow, err := utils.GetMemoryMonitoringSetting(cCtx)
if err != nil {
return fmt.Errorf("failed to get memory monitoring setting: %w", err)
}

// 11. Prepare the release (includes build/push if needed, with automatic retry on permission errors)
release, imageRef, err := utils.PrepareReleaseFromContext(cCtx, preflightCtx.EnvironmentConfig, appID, dockerfilePath, imageRef, envFilePath, logRedirect, monitoringMemoryAllow, instanceType, 3)
if err != nil {
return err
}

// 11. Check current permission state and determine if change is needed
// 12. Check current permission state and determine if change is needed
currentlyPublic, err := utils.CheckAppLogPermission(cCtx, appID)
if err != nil {
return fmt.Errorf("failed to check current permission state: %w", err)
}

needsPermissionChange := currentlyPublic != publicLogs

// 12. Upgrade the app
// 13. Upgrade the app
err = preflightCtx.Caller.UpgradeApp(cCtx.Context, appID, release, publicLogs, needsPermissionChange, imageRef)
if err != nil {
return fmt.Errorf("failed to upgrade app: %w", err)
}

// 13. Watch until upgrade completes
// 14. Watch until upgrade completes
return utils.WatchUntilTransitionComplete(cCtx, appID, common.AppStatusUpgrading)
}

Expand Down
19 changes: 10 additions & 9 deletions pkg/commands/utils/build_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ func checkIfImageAlreadyLayeredForEigenX(dockerClient *client.Client, ctx contex
// Image Building and Pushing
// ============================================================================

func buildAndPushLayeredImage(cCtx *cli.Context, environmentConfig common.EnvironmentConfig, dockerfilePath, targetImageRef, logRedirect, envFilePath string) (string, error) {
func buildAndPushLayeredImage(cCtx *cli.Context, environmentConfig common.EnvironmentConfig, dockerfilePath, targetImageRef, logRedirect, monitoringMemoryAllow, envFilePath string) (string, error) {
logger := common.LoggerFromContext(cCtx)

dockerClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
Expand All @@ -253,10 +253,10 @@ func buildAndPushLayeredImage(cCtx *cli.Context, environmentConfig common.Enviro
return "", fmt.Errorf("failed to build base image: %w", err)
}

return layerLocalImage(cCtx, dockerClient, environmentConfig, baseImageTag, targetImageRef, logRedirect, envFilePath)
return layerLocalImage(cCtx, dockerClient, environmentConfig, baseImageTag, targetImageRef, logRedirect, monitoringMemoryAllow, envFilePath)
}

func layerLocalImage(cCtx *cli.Context, dockerClient *client.Client, environmentConfig common.EnvironmentConfig, sourceImageRef, targetImageRef, logRedirect, envFilePath string) (string, error) {
func layerLocalImage(cCtx *cli.Context, dockerClient *client.Client, environmentConfig common.EnvironmentConfig, sourceImageRef, targetImageRef, logRedirect, monitoringMemoryAllow, envFilePath string) (string, error) {
logger := common.LoggerFromContext(cCtx)

// Extract original command and user from source image
Expand Down Expand Up @@ -286,12 +286,13 @@ func layerLocalImage(cCtx *cli.Context, dockerClient *client.Client, environment
}

layeredDockerfileContent, err := processTemplate(LayeredDockerfilePath, LayeredDockerfileTemplateData{
BaseImage: sourceImageRef,
OriginalCmd: originalCmdStr,
OriginalUser: originalUser,
LogRedirect: logRedirect,
IncludeTLS: includeTLS,
EigenXCLIVersion: version.GetVersion(),
BaseImage: sourceImageRef,
OriginalCmd: originalCmdStr,
OriginalUser: originalUser,
LogRedirect: logRedirect,
MonitoringMemoryAllow: monitoringMemoryAllow,
IncludeTLS: includeTLS,
EigenXCLIVersion: version.GetVersion(),
})
if err != nil {
return "", fmt.Errorf("failed to process dockerfile template: %w", err)
Expand Down
10 changes: 10 additions & 0 deletions pkg/commands/utils/contract_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,16 @@ func PrintAppInfoWithStatus(ctx context.Context, logger iface.Logger, client *et
logger.Info("Instance: %s", info.MachineType)
logger.Info("IP: %s", info.Ip)

// Display CPU and Memory metrics if available
if info.CPUUtilizationPercent > 0 {
logger.Info("CPU Usage: %.2f%%", info.CPUUtilizationPercent)
}
if info.MemoryTotalBytes > 0 {
memoryUsedGB := info.MemoryUsedBytes / (1024 * 1024 * 1024)
memoryTotalGB := info.MemoryTotalBytes / (1024 * 1024 * 1024)
logger.Info("Memory Usage: %.2f%% (%.2f GB / %.2f GB)", info.MemoryUtilizationPercent, memoryUsedGB, memoryTotalGB)
}

// Display app profile if available
if info.Profile != nil {
if info.Profile.Website != nil {
Expand Down
33 changes: 33 additions & 0 deletions pkg/commands/utils/interactive.go
Original file line number Diff line number Diff line change
Expand Up @@ -893,6 +893,39 @@ func GetLogSettingsInteractive(cCtx *cli.Context) (logRedirect string, publicLog
}
}

// GetMemoryMonitoringSetting returns the memory monitoring configuration from flags or interactive prompt
func GetMemoryMonitoringSetting(cCtx *cli.Context) (string, error) {
if flagValue := cCtx.String("memory-monitoring"); flagValue != "" {
switch strings.ToLower(flagValue) {
case "enable":
return "always", nil
case "disable":
return "never", nil
default:
return "", fmt.Errorf("invalid --memory-monitoring value: %s (must be enable or disable)", flagValue)
}
}

options := []string{
"Yes",
"No",
}

choice, err := output.SelectString("Enable memory monitoring?", options)
if err != nil {
return "", fmt.Errorf("failed to get memory monitoring choice: %w", err)
}

switch choice {
case "Yes":
return "always", nil
case "No":
return "never", nil
default:
return "", fmt.Errorf("unexpected choice: %s", choice)
}
}

// GetInstanceTypeInteractive prompts for instance type if not provided via flag.
// The defaultSKU parameter is used as the default selection in interactive mode:
// - For new deployments: pass empty string (uses first SKU from backend)
Expand Down
10 changes: 5 additions & 5 deletions pkg/commands/utils/release_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,16 @@ import (
// PrepareReleaseFromContext prepares a release with separated Dockerfile handling
// The dockerfile path and env file path are provided as parameters (already collected earlier)
// maxPushRetries controls how many times to retry on push permission errors (0 = no retries)
func PrepareReleaseFromContext(cCtx *cli.Context, environmentConfig *common.EnvironmentConfig, appID gethcommon.Address, dockerfilePath string, imageRef string, envFilePath string, logRedirect string, instanceType string, maxPushRetries int) (appcontrollerV2.IAppControllerRelease, string, error) {
func PrepareReleaseFromContext(cCtx *cli.Context, environmentConfig *common.EnvironmentConfig, appID gethcommon.Address, dockerfilePath string, imageRef string, envFilePath string, logRedirect string, monitoringMemoryAllow string, instanceType string, maxPushRetries int) (appcontrollerV2.IAppControllerRelease, string, error) {
logger := common.LoggerFromContext(cCtx)

// Create operation closures that capture context
buildAndPush := func(ref string) (string, error) {
return buildAndPushLayeredImage(cCtx, *environmentConfig, dockerfilePath, ref, logRedirect, envFilePath)
return buildAndPushLayeredImage(cCtx, *environmentConfig, dockerfilePath, ref, logRedirect, monitoringMemoryAllow, envFilePath)
}

layerRemoteImage := func(ref string) (string, error) {
return layerRemoteImageIfNeeded(cCtx, *environmentConfig, ref, logRedirect, envFilePath)
return layerRemoteImageIfNeeded(cCtx, *environmentConfig, ref, logRedirect, monitoringMemoryAllow, envFilePath)
}

// Ensure image is compatible with EigenX (either build from Dockerfile or layer existing image)
Expand Down Expand Up @@ -178,7 +178,7 @@ func retryImagePushOperation(
return imageRef, err
}

func layerRemoteImageIfNeeded(cCtx *cli.Context, environmentConfig common.EnvironmentConfig, imageRef, logRedirect, envFilePath string) (string, error) {
func layerRemoteImageIfNeeded(cCtx *cli.Context, environmentConfig common.EnvironmentConfig, imageRef, logRedirect, monitoringMemoryAllow, envFilePath string) (string, error) {
// Check if the provided image is missing image layering, which is required for EigenX
dockerClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
Expand All @@ -201,7 +201,7 @@ func layerRemoteImageIfNeeded(cCtx *cli.Context, environmentConfig common.Enviro
}

logger.Info("Adding EigenX components to create %s from %s...", targetImageRef, imageRef)
layeredImageRef, err := layerLocalImage(cCtx, dockerClient, environmentConfig, imageRef, targetImageRef, logRedirect, envFilePath)
layeredImageRef, err := layerLocalImage(cCtx, dockerClient, environmentConfig, imageRef, targetImageRef, logRedirect, monitoringMemoryAllow, envFilePath)
if err != nil {
return "", fmt.Errorf("failed to layer published image: %w", err)
}
Expand Down
13 changes: 7 additions & 6 deletions pkg/commands/utils/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@ const (
)

type LayeredDockerfileTemplateData struct {
BaseImage string
OriginalCmd string
OriginalUser string
LogRedirect string
IncludeTLS bool
EigenXCLIVersion string
BaseImage string
OriginalCmd string
OriginalUser string
LogRedirect string
MonitoringMemoryAllow string
IncludeTLS bool
EigenXCLIVersion string
}

type EnvSourceScriptTemplateData struct {
Expand Down
46 changes: 29 additions & 17 deletions pkg/commands/utils/userapi_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,21 +96,29 @@ type AppProfileResponse struct {
}

type RawAppInfo struct {
Addresses json.RawMessage `json:"addresses"`
Status string `json:"app_status"`
Ip string `json:"ip"`
MachineType string `json:"machine_type"`
Profile *AppProfileResponse `json:"profile,omitempty"`
Addresses json.RawMessage `json:"addresses"`
Status string `json:"app_status"`
Ip string `json:"ip"`
MachineType string `json:"machine_type"`
Profile *AppProfileResponse `json:"profile,omitempty"`
CPUUtilizationPercent float64 `json:"cpu_utilization_percent"`
MemoryUtilizationPercent float64 `json:"memory_utilization_percent"`
MemoryUsedBytes float64 `json:"memory_used_bytes"`
MemoryTotalBytes float64 `json:"memory_total_bytes"`
}

// AppInfo contains the app info with parsed and validated addresses
type AppInfo struct {
EVMAddresses []kmstypes.EVMAddressAndDerivationPath
SolanaAddresses []kmstypes.SolanaAddressAndDerivationPath
Status string
Ip string
MachineType string
Profile *AppProfileResponse
EVMAddresses []kmstypes.EVMAddressAndDerivationPath
SolanaAddresses []kmstypes.SolanaAddressAndDerivationPath
Status string
Ip string
MachineType string
Profile *AppProfileResponse
CPUUtilizationPercent float64
MemoryUtilizationPercent float64
MemoryUsedBytes float64
MemoryTotalBytes float64
}

type AppInfoResponse struct {
Expand Down Expand Up @@ -217,12 +225,16 @@ func (cc *UserApiClient) GetInfos(cCtx *cli.Context, appIDs []ethcommon.Address,
}

result.Apps[i] = AppInfo{
EVMAddresses: evmAddrs,
SolanaAddresses: solanaAddrs,
Status: rawApp.Status,
Ip: rawApp.Ip,
MachineType: rawApp.MachineType,
Profile: rawApp.Profile,
EVMAddresses: evmAddrs,
SolanaAddresses: solanaAddrs,
Status: rawApp.Status,
Ip: rawApp.Ip,
MachineType: rawApp.MachineType,
Profile: rawApp.Profile,
CPUUtilizationPercent: rawApp.CPUUtilizationPercent,
MemoryUtilizationPercent: rawApp.MemoryUtilizationPercent,
MemoryUsedBytes: rawApp.MemoryUsedBytes,
MemoryTotalBytes: rawApp.MemoryTotalBytes,
}
}

Expand Down
5 changes: 5 additions & 0 deletions pkg/common/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ var (
Usage: "Log visibility setting: public, private, or off",
}

MemoryMonitoringFlag = &cli.StringFlag{
Name: "memory-monitoring",
Usage: "Memory monitoring setting: enable or disable",
}

InstanceTypeFlag = &cli.StringFlag{
Name: "instance-type",
Usage: "Machine instance type to use e.g. g1-standard-4t, g1-standard-8t",
Expand Down
Loading