fix(dev): address CLI review feedback

This commit is contained in:
Thomas Camlong committed 2026-07-31 14:53:43 +02:00
1 parent e97eb7fc58
commit c8485ed7fc
18 files changed
+307 -160

No files matched your search

+42 -25
View File
@@ -12,6 +12,8 @@ import (
"strings"
)
const defaultDevelopmentEncryptionKey = "cdca265c289df60f6605a2fc9c43cfa8eb6efdc98b203cad2c3f2970aca09fbe"
type Container struct {
ID string
Name string
@@ -82,8 +84,12 @@ func List() ([]Container, error) {
}
func FindFreePort(start int) int {
if start > 65535 {
return 0
}
used := usedDockerPorts()
for port := start; ; port++ {
start = max(start, 1)
for port := start; port <= 65535; port++ {
if used[port] {
continue
}
@@ -93,36 +99,43 @@ func FindFreePort(start int) int {
return port
}
}
return 0
}
func usedDockerPorts() map[int]bool {
used := make(map[int]bool)
out, err := exec.Command("docker", "ps", "--format", "{{.Ports}}").Output()
if err != nil {
return used
return make(map[int]bool)
}
for _, mapping := range strings.Split(string(out), ",") {
left, _, found := strings.Cut(mapping, "->")
if !found {
continue
}
colon := strings.LastIndex(left, ":")
if colon < 0 {
continue
}
ports := strings.SplitN(strings.TrimSpace(left[colon+1:]), "-", 2)
first, err := strconv.Atoi(ports[0])
if err != nil {
continue
}
last := first
if len(ports) == 2 {
if parsed, err := strconv.Atoi(ports[1]); err == nil {
last = parsed
return parseUsedDockerPorts(out)
}
func parseUsedDockerPorts(out []byte) map[int]bool {
used := make(map[int]bool)
for _, line := range strings.Split(string(out), "\n") {
for _, mapping := range strings.Split(line, ",") {
left, _, found := strings.Cut(mapping, "->")
if !found {
continue
}
colon := strings.LastIndex(left, ":")
if colon < 0 {
continue
}
ports := strings.SplitN(strings.TrimSpace(left[colon+1:]), "-", 2)
first, err := strconv.Atoi(ports[0])
if err != nil {
continue
}
last := first
if len(ports) == 2 {
if parsed, err := strconv.Atoi(ports[1]); err == nil {
last = parsed
}
}
for port := first; port <= last; port++ {
used[port] = true
}
}
for port := first; port <= last; port++ {
used[port] = true
}
}
return used
@@ -162,8 +175,12 @@ func Start(opts StartOptions) error {
_ = exec.Command("docker", "rm", "-f", opts.Name).Run()
encryptionKey := os.Getenv("HOMARR_DEV_SECRET_ENCRYPTION_KEY")
if encryptionKey == "" {
encryptionKey = defaultDevelopmentEncryptionKey
}
args := []string{"run", "--rm", "--name", opts.Name,
"-e", "SECRET_ENCRYPTION_KEY=cdca265c289df60f6605a2fc9c43cfa8eb6efdc98b203cad2c3f2970aca09fbe",
"-e", "SECRET_ENCRYPTION_KEY=" + encryptionKey,
}
if opts.Platform != "" {
args = append(args, "--platform", opts.Platform)
@@ -25,3 +25,18 @@ func TestIsPortConflict(t *testing.T) {
t.Fatal("manifest error classified as port conflict")
}
}
func TestParseUsedDockerPortsPreservesLineBoundaries(t *testing.T) {
ports := parseUsedDockerPorts([]byte("6379/tcp\n0.0.0.0:7575->7575/tcp, [::]:7575->7575/tcp\n0.0.0.0:8000-8002->8000-8002/tcp\n"))
for _, port := range []int{7575, 8000, 8001, 8002} {
if !ports[port] {
t.Fatalf("port %d was not detected", port)
}
}
}
func TestFindFreePortRejectsOutOfRangeStart(t *testing.T) {
if port := FindFreePort(65536); port != 0 {
t.Fatalf("port = %d, want 0", port)
}
}
+5 -9
View File
@@ -56,8 +56,8 @@ func parseImageList(output []byte) ([]Image, error) {
return images, nil
}
func ListLocalImages() ([]Image, error) {
out, err := exec.Command("docker", "image", "ls", "--filter", "reference=homarr:*", "--format", "{{json .}}").Output()
func ListLocalImages(ctx context.Context) ([]Image, error) {
out, err := exec.CommandContext(ctx, "docker", "image", "ls", "--filter", "reference=homarr:*", "--format", "{{json .}}").Output()
if err != nil {
return nil, fmt.Errorf("list local Homarr images: %w", err)
}
@@ -65,7 +65,7 @@ func ListLocalImages() ([]Image, error) {
if err != nil {
return nil, err
}
labelsByImage, err := inspectImageLabels(images)
labelsByImage, err := inspectImageLabels(ctx, images)
if err != nil {
return nil, err
}
@@ -77,7 +77,7 @@ func ListLocalImages() ([]Image, error) {
return images, nil
}
func inspectImageLabels(images []Image) ([]map[string]string, error) {
func inspectImageLabels(ctx context.Context, images []Image) ([]map[string]string, error) {
if len(images) == 0 {
return nil, nil
}
@@ -85,7 +85,7 @@ func inspectImageLabels(images []Image) ([]map[string]string, error) {
for _, image := range images {
args = append(args, image.Reference())
}
out, err := exec.Command("docker", args...).Output()
out, err := exec.CommandContext(ctx, "docker", args...).Output()
if err != nil {
return nil, fmt.Errorf("inspect local Homarr images: %w", err)
}
@@ -135,10 +135,6 @@ func BuildCommand(ctx context.Context, options BuildOptions) (*exec.Cmd, error)
return command, nil
}
func Build(options BuildOptions) error {
return BuildContext(context.Background(), options, os.Stdout, os.Stderr)
}
func BuildContext(ctx context.Context, options BuildOptions, stdout, stderr io.Writer) error {
command, err := BuildCommand(ctx, options)
if err != nil {
@@ -17,7 +17,7 @@ func TestParseImageListFiltersHomarrTags(t *testing.T) {
}
func TestInspectImageLabelsWithNoImages(t *testing.T) {
labels, err := inspectImageLabels(nil)
labels, err := inspectImageLabels(t.Context(), nil)
if err != nil {
t.Fatal(err)
}
@@ -53,7 +53,7 @@ func TestListLocalImagesIntegration(t *testing.T) {
if wantTag == "" {
t.Skip("set HOMARR_TEST_LOCAL_IMAGE to a local homarr image tag")
}
images, err := ListLocalImages()
images, err := ListLocalImages(t.Context())
if err != nil {
t.Fatal(err)
}