diff --git a/compose.go b/compose.go index e047357..439fdf6 100644 --- a/compose.go +++ b/compose.go @@ -720,20 +720,30 @@ func (g *Generator) buildNixContainer(service types.ServiceConfig, networkMap ma func (g *Generator) parseServiceBuild(service types.ServiceConfig, c *NixContainer) (*NixBuild, error) { cx := service.Build.Context + isGitRepo := false + if strings.HasPrefix(cx, "http") { - return nil, fmt.Errorf("Git repo build context is not yet supported") - } - if !path.IsAbs(cx) { + // Process this as a Git repo. + isGitRepo = true + } else if !path.IsAbs(cx) { cx = path.Join(g.rootPath, cx) } + // Always prepend the image name to the list of specified tags. + tags := service.Build.Tags + if service.Image != "" { + tags = slices.Insert(tags, 0, service.Image) + } + b := &NixBuild{ - Runtime: g.Runtime, - Context: cx, - Args: service.Build.Args, - Tags: service.Build.Tags, - Dockerfile: service.Build.Dockerfile, - ServiceName: service.Name, + Runtime: g.Runtime, + Context: cx, + PullPolicy: NewServicePullPolicy(service.PullPolicy), + IsGitRepo: isGitRepo, + Args: service.Build.Args, + Tags: tags, + Dockerfile: service.Build.Dockerfile, + ContainerName: c.Name, } if g.IncludeBuild { @@ -774,7 +784,7 @@ func (g *Generator) buildNixContainers(composeProject *types.Project, networkMap return cmp.Compare(c1.Name, c2.Name) }) slices.SortFunc(builds, func(c1, c2 *NixBuild) int { - return cmp.Compare(c1.ServiceName, c2.ServiceName) + return cmp.Compare(c1.ContainerName, c2.ContainerName) }) return containers, builds, nil } diff --git a/nix.go b/nix.go index c104929..7440992 100644 --- a/nix.go +++ b/nix.go @@ -196,19 +196,52 @@ func (c *NixContainer) Unit() string { return fmt.Sprintf("%s-%s.service", c.Runtime, c.Name) } +// https://docs.docker.com/reference/compose-file/services/#pull_policy +type ServicePullPolicy int + +const ( + ServicePullPolicyInvalid ServicePullPolicy = iota + ServicePullPolicyAlways + ServicePullPolicyNever + ServicePullPolicyMissing + ServicePullPolicyBuild + ServicePullPolicyUnset +) + +func NewServicePullPolicy(s string) ServicePullPolicy { + switch strings.TrimSpace(s) { + case "always": + return ServicePullPolicyAlways + case "never": + return ServicePullPolicyNever + case "missing", "if_not_present": + return ServicePullPolicyMissing + case "build": + return ServicePullPolicyBuild + default: + return ServicePullPolicyUnset + } +} + // https://docs.docker.com/reference/compose-file/build/ // https://docs.docker.com/reference/cli/docker/buildx/build/ type NixBuild struct { - Runtime ContainerRuntime - Context string - Args map[string]*string - Tags []string - Dockerfile string // Relative to context path. - ServiceName string + Runtime ContainerRuntime + Context string + PullPolicy ServicePullPolicy + IsGitRepo bool + Args map[string]*string + Tags []string + Dockerfile string // Relative to context path. + ContainerName string // Name of the resolved Nix container. +} + +func (b *NixBuild) UnitName() string { + return fmt.Sprintf("%s-build-%s", b.Runtime, b.ContainerName) } func (b *NixBuild) Unit() string { - return fmt.Sprintf("podman-build-%s.service", b.ServiceName) + return b.UnitName() + ".service" } func (b *NixBuild) Command() string { diff --git a/templates/build.nix.tmpl b/templates/build.nix.tmpl index 3e569dc..3ead955 100644 --- a/templates/build.nix.tmpl +++ b/templates/build.nix.tmpl @@ -1,14 +1,21 @@ -systemd.services."{{.Runtime}}-build-{{.ServiceName}}" = { +systemd.services."{{.UnitName}}" = { {{- /* TODO: Support Git repo as a build source. */}} path = [ pkgs.{{.Runtime}} pkgs.git ]; serviceConfig = { Type = "oneshot"; - {{- if cfg.IncludeBuild}} + RuntimeDirectory = "{{.UnitName}}"; + {{- if cfg.IncludeBuild}} RemainAfterExit = true; - {{- end}} + {{- end}} }; script = '' + {{- if .IsGitRepo}} + cd /var/run/{{.UnitName}} + rm -rf * + git clone {{.Context}} . + {{- else}} cd {{.Context}} + {{- end}} {{escapeIndentedNixString .Command}} ''; {{- if and cfg.IncludeBuild rootTarget}} diff --git a/templates/main.nix.tmpl b/templates/main.nix.tmpl index 296c340..0bc8ae6 100644 --- a/templates/main.nix.tmpl +++ b/templates/main.nix.tmpl @@ -58,9 +58,6 @@ {{- if .Builds}} # Builds - # - # NOTE: These must be run manually before running any containers that require - # them to be present in the image store. {{- range .Builds}} {{execTemplate "build.nix.tmpl" . | indent 2}} {{- end}} diff --git a/testdata/TestBuildSpec.compose.yml b/testdata/TestBuildSpec.compose.yml index 9b414f1..ed76222 100644 --- a/testdata/TestBuildSpec.compose.yml +++ b/testdata/TestBuildSpec.compose.yml @@ -21,6 +21,13 @@ services: - ./data:/data:ro networks: - internal + prefetcharr: + image: prefetcharr + build: https://github.com/p-hueber/prefetcharr.git + environment: + - JELLYFIN_URL=http://example.com/jellyfin + volumes: + - /path/to/log/dir:/log volumes: custom-logs: diff --git a/testdata/TestBuildSpec.docker.nix b/testdata/TestBuildSpec.docker.nix index 5d412ce..20b85aa 100644 --- a/testdata/TestBuildSpec.docker.nix +++ b/testdata/TestBuildSpec.docker.nix @@ -44,8 +44,47 @@ "docker-volume-test_custom-logs.service" ]; }; + virtualisation.oci-containers.containers."test-prefetcharr" = { + image = "prefetcharr"; + environment = { + "JELLYFIN_URL" = "http://example.com/jellyfin"; + }; + volumes = [ + "/path/to/log/dir:/log:rw" + ]; + log-driver = "journald"; + autoStart = false; + extraOptions = [ + "--network-alias=prefetcharr" + "--network=test_default" + ]; + }; + systemd.services."docker-test-prefetcharr" = { + serviceConfig = { + Restart = lib.mkOverride 90 "no"; + }; + after = [ + "docker-network-test_default.service" + ]; + requires = [ + "docker-network-test_default.service" + ]; + }; # Networks + systemd.services."docker-network-test_default" = { + path = [ pkgs.docker ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStop = "docker network rm -f test_default"; + }; + script = '' + docker network inspect test_default || docker network create test_default + ''; + partOf = [ "docker-compose-test-root.target" ]; + wantedBy = [ "docker-compose-test-root.target" ]; + }; systemd.services."docker-network-test_internal" = { path = [ pkgs.docker ]; serviceConfig = { @@ -75,19 +114,29 @@ }; # Builds - # - # NOTE: These must be run manually before running any containers that require - # them to be present in the image store. - systemd.services."docker-build-museum" = { + systemd.services."docker-build-test-museum" = { path = [ pkgs.docker pkgs.git ]; serviceConfig = { Type = "oneshot"; + RuntimeDirectory = "docker-build-test-museum"; }; script = '' cd /some/path docker build -t latest -t non-latest --build-arg GIT_COMMIT=development-cluster . ''; }; + systemd.services."docker-build-test-prefetcharr" = { + path = [ pkgs.docker pkgs.git ]; + serviceConfig = { + Type = "oneshot"; + RuntimeDirectory = "docker-build-test-prefetcharr"; + }; + script = '' + cd /tmp + git clone https://github.com/p-hueber/prefetcharr.git . + docker build -t prefetcharr . + ''; + }; # Root service # When started, this will automatically create all resources and start diff --git a/testdata/TestBuildSpec.podman.nix b/testdata/TestBuildSpec.podman.nix index 0c0557f..19b5e66 100644 --- a/testdata/TestBuildSpec.podman.nix +++ b/testdata/TestBuildSpec.podman.nix @@ -54,8 +54,47 @@ "podman-volume-test_custom-logs.service" ]; }; + virtualisation.oci-containers.containers."test-prefetcharr" = { + image = "prefetcharr"; + environment = { + "JELLYFIN_URL" = "http://example.com/jellyfin"; + }; + volumes = [ + "/path/to/log/dir:/log:rw" + ]; + log-driver = "journald"; + autoStart = false; + extraOptions = [ + "--network-alias=prefetcharr" + "--network=test_default" + ]; + }; + systemd.services."podman-test-prefetcharr" = { + serviceConfig = { + Restart = lib.mkOverride 90 "no"; + }; + after = [ + "podman-network-test_default.service" + ]; + requires = [ + "podman-network-test_default.service" + ]; + }; # Networks + systemd.services."podman-network-test_default" = { + path = [ pkgs.podman ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStop = "podman network rm -f test_default"; + }; + script = '' + podman network inspect test_default || podman network create test_default + ''; + partOf = [ "podman-compose-test-root.target" ]; + wantedBy = [ "podman-compose-test-root.target" ]; + }; systemd.services."podman-network-test_internal" = { path = [ pkgs.podman ]; serviceConfig = { @@ -85,19 +124,29 @@ }; # Builds - # - # NOTE: These must be run manually before running any containers that require - # them to be present in the image store. - systemd.services."podman-build-museum" = { + systemd.services."podman-build-test-museum" = { path = [ pkgs.podman pkgs.git ]; serviceConfig = { Type = "oneshot"; + RuntimeDirectory = "podman-build-test-museum"; }; script = '' cd /some/path podman build -t latest -t non-latest --build-arg GIT_COMMIT=development-cluster . ''; }; + systemd.services."podman-build-test-prefetcharr" = { + path = [ pkgs.podman pkgs.git ]; + serviceConfig = { + Type = "oneshot"; + RuntimeDirectory = "podman-build-test-prefetcharr"; + }; + script = '' + cd /tmp + git clone https://github.com/p-hueber/prefetcharr.git . + podman build -t prefetcharr . + ''; + }; # Root service # When started, this will automatically create all resources and start diff --git a/testdata/TestBuildSpec_BuildEnabled.docker.nix b/testdata/TestBuildSpec_BuildEnabled.docker.nix index 7307b61..550680e 100644 --- a/testdata/TestBuildSpec_BuildEnabled.docker.nix +++ b/testdata/TestBuildSpec_BuildEnabled.docker.nix @@ -38,15 +38,15 @@ after = [ "docker-network-test_internal.service" "docker-volume-test_custom-logs.service" - "podman-build-museum.service" + "podman-build-test-museum.service" ]; requires = [ "docker-network-test_internal.service" "docker-volume-test_custom-logs.service" - "podman-build-museum.service" + "podman-build-test-museum.service" ]; upheldBy = [ - "podman-build-museum.service" + "podman-build-test-museum.service" ]; }; @@ -80,13 +80,11 @@ }; # Builds - # - # NOTE: These must be run manually before running any containers that require - # them to be present in the image store. - systemd.services."docker-build-museum" = { + systemd.services."docker-build-test-museum" = { path = [ pkgs.docker pkgs.git ]; serviceConfig = { Type = "oneshot"; + RuntimeDirectory = "docker-build-test-museum"; RemainAfterExit = true; }; script = '' diff --git a/testdata/TestBuildSpec_BuildEnabled.podman.nix b/testdata/TestBuildSpec_BuildEnabled.podman.nix index a6e1292..104648f 100644 --- a/testdata/TestBuildSpec_BuildEnabled.podman.nix +++ b/testdata/TestBuildSpec_BuildEnabled.podman.nix @@ -46,17 +46,17 @@ Restart = lib.mkOverride 90 "no"; }; after = [ - "podman-build-museum.service" + "podman-build-test-museum.service" "podman-network-test_internal.service" "podman-volume-test_custom-logs.service" ]; requires = [ - "podman-build-museum.service" + "podman-build-test-museum.service" "podman-network-test_internal.service" "podman-volume-test_custom-logs.service" ]; upheldBy = [ - "podman-build-museum.service" + "podman-build-test-museum.service" ]; }; @@ -90,13 +90,11 @@ }; # Builds - # - # NOTE: These must be run manually before running any containers that require - # them to be present in the image store. - systemd.services."podman-build-museum" = { + systemd.services."podman-build-test-museum" = { path = [ pkgs.podman pkgs.git ]; serviceConfig = { Type = "oneshot"; + RuntimeDirectory = "podman-build-test-museum"; RemainAfterExit = true; }; script = ''