diff --git a/cmd/thv/app/run.go b/cmd/thv/app/run.go index 0f743ae3f..756965db7 100644 --- a/cmd/thv/app/run.go +++ b/cmd/thv/app/run.go @@ -183,14 +183,16 @@ func runCmdFunc(cmd *cobra.Command, args []string) error { return fmt.Errorf("failed to create workload manager: %v", err) } - if runFlags.Name != "" { - exists, err := workloadManager.DoesWorkloadExist(ctx, runFlags.Name) - if err != nil { - return fmt.Errorf("failed to check if workload exists: %v", err) - } - if exists { - return fmt.Errorf("workload with name '%s' already exists", runFlags.Name) - } + if runFlags.Name == "" { + runFlags.Name = getworkloadDefaultName(serverOrImage) + logger.Infof("No workload name specified, using generated name: %s", runFlags.Name) + } + exists, err := workloadManager.DoesWorkloadExist(ctx, runFlags.Name) + if err != nil { + return fmt.Errorf("failed to check if workload exists: %v", err) + } + if exists { + return fmt.Errorf("workload with name '%s' already exists", runFlags.Name) } err = validateGroup(ctx, workloadManager, serverOrImage) if err != nil { @@ -238,6 +240,43 @@ func deriveRemoteName(remoteURL string) (string, error) { return hostname, nil } +// getworkloadDefaultName generates a default workload name based on the serverOrImage input +// This function reuses the existing system's naming logic to ensure consistency +func getworkloadDefaultName(serverOrImage string) string { + // If it's a protocol scheme (uvx://, npx://, go://) + if runner.IsImageProtocolScheme(serverOrImage) { + // Extract package name from protocol scheme using the existing parseProtocolScheme logic + _, packageName, err := runner.ParseProtocolScheme(serverOrImage) + if err != nil { + return "" + } + + // Use the existing packageNameToImageName function from the runner package + return runner.PackageNameToImageName(packageName) + } + + // If it's a URL (remote server) + if networking.IsURL(serverOrImage) { + name, err := deriveRemoteName(serverOrImage) + if err != nil { + return "" + } + return name + } + + // Check if it's a server name from registry + // Registry server names are typically multi-word names with hyphens + if !strings.Contains(serverOrImage, "://") && !strings.Contains(serverOrImage, "/") && !strings.Contains(serverOrImage, ":") { + // Likely a registry server name (no protocol, no slashes, no colons), return as-is + return serverOrImage + } + + // For container images, use the existing container.GetOrGenerateContainerName logic + // We pass empty string as containerName to force generation, and extract the baseName + _, baseName := container.GetOrGenerateContainerName("", serverOrImage) + return baseName +} + func runForeground(ctx context.Context, workloadManager workloads.Manager, runnerConfig *runner.RunConfig) error { ctx, cancel := context.WithCancel(ctx) defer cancel() diff --git a/pkg/runner/protocol.go b/pkg/runner/protocol.go index f8dbfb7a6..a06be04e8 100644 --- a/pkg/runner/protocol.go +++ b/pkg/runner/protocol.go @@ -48,7 +48,7 @@ func BuildFromProtocolSchemeWithName( imageName string, dryRun bool, ) (string, error) { - transportType, packageName, err := parseProtocolScheme(serverOrImage) + transportType, packageName, err := ParseProtocolScheme(serverOrImage) if err != nil { return "", err } @@ -70,8 +70,8 @@ func BuildFromProtocolSchemeWithName( return buildImageFromTemplateWithName(ctx, imageManager, transportType, packageName, templateData, imageName) } -// parseProtocolScheme extracts the transport type and package name from the protocol scheme. -func parseProtocolScheme(serverOrImage string) (templates.TransportType, string, error) { +// ParseProtocolScheme extracts the transport type and package name from the protocol scheme. +func ParseProtocolScheme(serverOrImage string) (templates.TransportType, string, error) { if strings.HasPrefix(serverOrImage, UVXScheme) { return templates.TransportTypeUVX, strings.TrimPrefix(serverOrImage, UVXScheme), nil } @@ -266,7 +266,7 @@ func generateImageName(transportType templates.TransportType, packageName string tag := time.Now().Format("20060102150405") return strings.ToLower(fmt.Sprintf("toolhivelocal/%s-%s:%s", string(transportType), - packageNameToImageName(packageName), + PackageNameToImageName(packageName), tag)) } @@ -335,10 +335,10 @@ func buildImageFromTemplateWithName( return finalImageName, nil } -// Replace slashes with dashes to create a valid Docker image name. If there +// PackageNameToImageName replaces slashes with dashes to create a valid Docker image name. If there // is a version in the package name, the @ is replaced with a dash. // For local paths, we clean up the path to make it a valid image name. -func packageNameToImageName(packageName string) string { +func PackageNameToImageName(packageName string) string { imageName := packageName // Handle local paths by cleaning them up diff --git a/pkg/runner/protocol_test.go b/pkg/runner/protocol_test.go index 74ecbf1e4..1fdec986d 100644 --- a/pkg/runner/protocol_test.go +++ b/pkg/runner/protocol_test.go @@ -113,9 +113,9 @@ func TestPackageNameToImageName(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() - result := packageNameToImageName(tt.input) + result := PackageNameToImageName(tt.input) if result != tt.expected { - t.Errorf("packageNameToImageName(%q) = %q, want %q", tt.input, result, tt.expected) + t.Errorf("PackageNameToImageName(%q) = %q, want %q", tt.input, result, tt.expected) } }) }