diff --git a/cmd/uncloud/service/run.go b/cmd/uncloud/service/run.go index 27ef6cb6..6928ccbc 100644 --- a/cmd/uncloud/service/run.go +++ b/cmd/uncloud/service/run.go @@ -109,9 +109,21 @@ func run(ctx context.Context, uncli *cli.CLI, opts runOptions) error { } defer client.Close() - if _, err = client.RunService(ctx, spec); err != nil { + resp, err := client.RunService(ctx, spec) + if err != nil { return fmt.Errorf("run service: %w", err) } + svc, err := client.InspectService(ctx, resp.ID) + if err != nil { + return fmt.Errorf("inspect service: %w", err) + } + + fmt.Println() + fmt.Printf("%s endpoints:\n", svc.Name) + for _, endpoint := range svc.Endpoints() { + fmt.Printf(" • %s\n", endpoint) + } + return nil } diff --git a/internal/api/service.go b/internal/api/service.go index d46e4089..a8ced1ba 100644 --- a/internal/api/service.go +++ b/internal/api/service.go @@ -4,8 +4,10 @@ import ( "encoding/json" "fmt" "github.com/distribution/reference" + "maps" "reflect" "regexp" + "slices" "uncloud/internal/machine/api/pb" ) @@ -79,6 +81,50 @@ type MachineContainer struct { Container Container } +// Endpoints returns the exposed HTTP and HTTPS endpoints of the service. +func (s *Service) Endpoints() []string { + endpoints := make(map[string]struct{}) + + // Container specs may differ between containers in the same service, e.g. during a rolling update, + // so we need to collect all unique endpoints. + for _, ctr := range s.Containers { + ports, err := ctr.Container.ServicePorts() + if err != nil { + continue + } + + for _, port := range ports { + protocol := "" + switch port.Protocol { + case ProtocolHTTP: + protocol = "http" + case ProtocolHTTPS: + protocol = "https" + default: + continue + } + + if port.Hostname == "" { + // There shouldn't be http(s) ports without a hostname but just in case ignore them. + continue + } + + endpoint := fmt.Sprintf("%s://%s", protocol, port.Hostname) + if port.PublishedPort != 0 { + // For non-standard ports (80/443), include the port in the URL. + if !(port.Protocol == ProtocolHTTP && port.PublishedPort == 80) && + !(port.Protocol == ProtocolHTTPS && port.PublishedPort == 443) { + endpoint += fmt.Sprintf(":%d", port.PublishedPort) + } + } + + endpoints[endpoint] = struct{}{} + } + } + + return slices.Sorted(maps.Keys(endpoints)) +} + func ServiceFromProto(s *pb.Service) (Service, error) { var err error containers := make([]MachineContainer, len(s.Containers))