From c91c9bae5812aacf34c8e2aedbef118e047658c3 Mon Sep 17 00:00:00 2001 From: adamperlin Date: Wed, 15 May 2024 18:53:13 +0000 Subject: [PATCH 01/14] WIP --- frontend/rpm/template.go | 88 +++++++++++++++++++++++ spec.go | 23 ++++++ test/azlinux_test.go | 94 +++++++++++++++++++++++++ test/fixtures/env-multiple-commands.yml | 1 + test/fixtures/service/main.go | 16 +++++ test/fixtures/service/simple.service | 11 +++ test/fixtures/systemd-unit.yml | 56 +++++++++++++++ 7 files changed, 289 insertions(+) create mode 100644 test/fixtures/service/main.go create mode 100644 test/fixtures/service/simple.service create mode 100644 test/fixtures/systemd-unit.yml diff --git a/frontend/rpm/template.go b/frontend/rpm/template.go index 6f60641aa..127eff649 100644 --- a/frontend/rpm/template.go +++ b/frontend/rpm/template.go @@ -11,6 +11,7 @@ import ( "text/template" "github.com/Azure/dalec" + "github.com/moby/buildkit/util/bklog" ) const gomodsName = "__gomods" @@ -40,6 +41,9 @@ BuildArch: noarch {{ .PrepareSources }} {{ .BuildSteps }} {{ .Install }} +{{ .Post }} +{{ .PreUn }} +{{ .PostUn }} {{ .Files }} {{ .Changelog }} `))) @@ -105,6 +109,11 @@ func (w *specWrapper) Requires() fmt.Stringer { writeDep(b, "BuildRequires", name, constraints) } + if len(w.Artifacts.Services) > 0 { + // We take advantage of the systemd-rpm-macros package to simplify the service file installation + writeDep(b, "BuildRequires", "systemd-rpm-macros", nil) + } + if len(deps.Build) > 0 && len(deps.Runtime) > 0 { b.WriteString("\n") } @@ -288,6 +297,54 @@ func (w *specWrapper) BuildSteps() fmt.Stringer { return b } +func (w *specWrapper) PreUn() fmt.Stringer { + b := &strings.Builder{} + b.WriteString("%preun\n") + + keys := dalec.SortMapKeys(w.Spec.Artifacts.Services) + for _, servicePath := range keys { + // must include '.service' suffix + serviceName := filepath.Base(servicePath) + fmt.Fprintf(b, "%%systemd_preun %s\n", serviceName) + } + + return b +} + +func (w *specWrapper) Post() fmt.Stringer { + b := &strings.Builder{} + b.WriteString("%post\n") + + keys := dalec.SortMapKeys(w.Spec.Artifacts.Services) + for _, servicePath := range keys { + // must include '.service' suffix + serviceName := filepath.Base(servicePath) + fmt.Fprintf(b, "%%systemd_post %s\n", serviceName) + } + + return b +} + +func (w *specWrapper) PostUn() fmt.Stringer { + b := &strings.Builder{} + b.WriteString("%postun\n") + keys := dalec.SortMapKeys(w.Spec.Artifacts.Services) + for _, servicePath := range keys { + // must include '.service' suffix + cfg := w.Spec.Artifacts.Services[servicePath] + bklog.L.Infof("servicePath: %s, cfg: %v", servicePath, cfg) + serviceName := filepath.Base(servicePath) + + if cfg.NoRestart { + fmt.Fprintf(b, "%%systemd_postun %s\n", serviceName) + } else { + fmt.Fprintf(b, "%%systemd_postun_with_restart %s\n", serviceName) + } + } + + return b +} + func (w *specWrapper) Install() fmt.Stringer { b := &strings.Builder{} @@ -355,6 +412,27 @@ func (w *specWrapper) Install() fmt.Stringer { cfg := w.Spec.Artifacts.ConfigFiles[c] copyArtifact(`%{buildroot}/%{_sysconfdir}`, c, cfg) } + serviceKeys := dalec.SortMapKeys(w.Spec.Artifacts.Services) + for _, p := range serviceKeys { + cfg := w.Spec.Artifacts.Services[p] + // must include '.service' suffix in name + copyArtifact(`%{buildroot}/%{_unitdir}`, p, cfg.Artifact()) + + verb := "enable" + if cfg.Disable { + verb = "disable" + } + + serviceName := filepath.Base(p) + if cfg.Name != "" { + serviceName = cfg.Name + } + + presetName := strings.TrimSuffix(serviceName, ".service") + ".preset" + fmt.Fprintf(b, "echo '%s %s' >> %s\n", verb, serviceName, presetName) + copyArtifact(`%{buildroot}/%{_presetdir}`, presetName, dalec.ArtifactConfig{}) + } + return b } @@ -399,6 +477,16 @@ func (w *specWrapper) Files() fmt.Stringer { fmt.Fprintln(b, fullDirective) } + serviceKeys := dalec.SortMapKeys(w.Spec.Artifacts.Services) + for _, p := range serviceKeys { + serviceName := filepath.Base(p) + prefixName := strings.TrimSuffix(serviceName, ".service") + ".preset" + unitPath := filepath.Join(`%{_unitdir}/`, serviceName) + prefixPath := filepath.Join(`%{_presetdir}/`, prefixName) + fmt.Fprintln(b, unitPath) + fmt.Fprintln(b, prefixPath) + } + return b } diff --git a/spec.go b/spec.go index 44976a125..48f64a747 100644 --- a/spec.go +++ b/spec.go @@ -132,6 +132,7 @@ type Artifacts struct { // ConfigFiles is a list of files that should be marked as config files in the package. ConfigFiles map[string]ArtifactConfig `yaml:"configFiles,omitempty" json:"configFiles,omitempty"` // TODO: other types of artifacts (systtemd units, libexec, etc) + Services map[string]ServiceConfig `yaml:"services,omitempty" json:"services,omitempty"` } // CreateArtifactDirectories describes various directories that should be created on install. @@ -163,6 +164,23 @@ type ArtifactConfig struct { Name string `yaml:"name,omitempty" json:"name,omitempty"` } +// ServiceConfig is the configuration for a service to include in the package. +type ServiceConfig struct { + Name string `yaml:"name" json:"name" jsonschema:"omitempty"` + + // Some services don't support restarting, in which case this should be set to true + NoRestart bool `yaml:"noRestart,omitempty" json:"noRestart,omitempty"` + + Disable bool `yaml:"disable,omitempty" json:"disable,omitempty"` +} + +func (s ServiceConfig) Artifact() ArtifactConfig { + return ArtifactConfig{ + SubPath: "", + Name: s.Name, + } +} + // IsEmpty is used to determine if there are any artifacts to include in the package. func (a *Artifacts) IsEmpty() bool { if len(a.Binaries) > 0 { @@ -177,6 +195,11 @@ func (a *Artifacts) IsEmpty() bool { if len(a.ConfigFiles) > 0 { return false } + + if len(a.Services) > 0 { + return false + } + return true } diff --git a/test/azlinux_test.go b/test/azlinux_test.go index eb6810292..c1c5286b2 100644 --- a/test/azlinux_test.go +++ b/test/azlinux_test.go @@ -26,6 +26,15 @@ func TestAzlinux3(t *testing.T) { testLinuxDistro(ctx, t, "azlinux3/container", "azlinux3/rpm") } +func mustParse(t *testing.T, spec string) *dalec.Spec { + t.Helper() + s, err := dalec.LoadSpec([]byte(spec)) + if err != nil { + t.Fatalf("failed to parse spec: %v", err) + } + return s +} + func testLinuxDistro(ctx context.Context, t *testing.T, buildTarget string, signTarget string) { t.Run("Fail when non-zero exit code during build", func(t *testing.T) { t.Parallel() @@ -340,6 +349,90 @@ echo "$BAR" > bar.txt runTest(t, distroSigningTest(t, &spec, signTarget)) }) + + t.Run("systemd unit", func(t *testing.T) { + t.Parallel() + spec := mustParse(t, ` +name: dalec-test-systemd-unit +description: "Test systemd unit" +website: https://www.github.com/Azure/dalec +version: 0.0.1 +revision: 1 +vendor: Microsoft +license: Apache 2.0 +packager: Microsoft + +targets: + mariner2: + image: + base: mcr.microsoft.com/cbl-mariner/base/core:2.0 + +dependencies: + build: + msft-golang: + runtime: + bash: + curl: + systemd: + +sources: + src: + dir: + files: + simple.service: + contents: | +[Unit] +Description=Phony Service +After=network.target + +[Service] +Type=simple +ExecStart=/usr/bin/service +Restart=always + +[Install] +WantedBy=multi-user.target + + + +build: + steps: + - command: | + cd src/ + go build -o service main.go + +artifacts: + binaries: + src/service: {} + + services: + src/simple.service: + # is the service enabled or disabled by default on boot + # this determines what goes in the sytemctl preset file + disable: true + noRestart: false + +`) + + /* + tests: + - name: Check service files + files: + /usr/lib/systemd/system/simple.service: + permissions: 0644 + contains: + - "ExecStart=/usr/bin/service" + /usr/lib/systemd/system-preset/simple.preset: + permissions: 0644 + contains: + - "enable simple.service" + */ + testEnv.RunTest(ctx, t, func(ctx context.Context, client gwclient.Client) (*gwclient.Result, error) { + req := newSolveRequest(withBuildTarget(buildTarget), withSpec(ctx, t, spec)) + return client.Solve(ctx, req) + }) + }) + t.Run("go module", func(t *testing.T) { t.Parallel() ctx := startTestSpan(baseCtx, t) @@ -394,6 +487,7 @@ echo "$BAR" > bar.txt req := newSolveRequest(withBuildTarget(buildTarget), withSpec(ctx, t, spec)) return client.Solve(ctx, req) }) + }) t.Run("test directory creation", func(t *testing.T) { diff --git a/test/fixtures/env-multiple-commands.yml b/test/fixtures/env-multiple-commands.yml index 17bd80225..9a3252e17 100644 --- a/test/fixtures/env-multiple-commands.yml +++ b/test/fixtures/env-multiple-commands.yml @@ -11,6 +11,7 @@ license: Apache 2.0 targets: # Distro specific build requirements mariner2: + base: mcr.microsoft.com/cbl-mariner/base/core:2.0 dependencies: build: diff --git a/test/fixtures/service/main.go b/test/fixtures/service/main.go new file mode 100644 index 000000000..82738f44b --- /dev/null +++ b/test/fixtures/service/main.go @@ -0,0 +1,16 @@ +package main + +import ( + "fmt" + "net/http" +) + +func main() { + var mux = http.NewServeMux() + mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + fmt.Fprintln(w, "Phony Service") + })) + + http.ListenAndServe(":8080", mux) +} diff --git a/test/fixtures/service/simple.service b/test/fixtures/service/simple.service new file mode 100644 index 000000000..f75646ecc --- /dev/null +++ b/test/fixtures/service/simple.service @@ -0,0 +1,11 @@ +[Unit] +Description=Phony Service +After=network.target + +[Service] +Type=simple +ExecStart=/usr/bin/service +Restart=always + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/test/fixtures/systemd-unit.yml b/test/fixtures/systemd-unit.yml new file mode 100644 index 000000000..cd75c23c5 --- /dev/null +++ b/test/fixtures/systemd-unit.yml @@ -0,0 +1,56 @@ +# syntax=local/dalec/frontend + +name: dalec-test-systemd-unit +description: A test fixture which tests the installation of a simple systemd unit +website: https://www.github.com/Azure/dalec +version: 0.0.1 +revision: 1 +vendor: Microsoft +license: Apache 2.0 +packager: Microsoft + +targets: + mariner2: + image: + base: mcr.microsoft.com/cbl-mariner/base/core:2.0 + +dependencies: + build: + msft-golang: + runtime: + bash: + curl: + systemd: + +sources: + src: + context: {} + path: test/fixtures/service + includes: + - test/fixtures/service + +build: + steps: + - command: | + cd src/ + go build -o service main.go + +artifacts: + binaries: + src/service: {} + + services: + src/simple.service: + # is the service enabled or disabled by default on boot + # this determines what goes in the sytemctl preset file + # disable: true + # noRestart: false + +image: + # entrypoint: /bin/service + entrypoint: /sbin/init + stop_signal: SIGRTMIN+3 + user: root + + + From 0d038562406c2396bba71dad59623b521108d093 Mon Sep 17 00:00:00 2001 From: adamperlin Date: Wed, 15 May 2024 22:20:17 +0000 Subject: [PATCH 02/14] Fix test case --- frontend/rpm/template.go | 3 +- test/azlinux_test.go | 135 ++++++++++++++++++++++++--------------- 2 files changed, 85 insertions(+), 53 deletions(-) diff --git a/frontend/rpm/template.go b/frontend/rpm/template.go index 127eff649..82d098c61 100644 --- a/frontend/rpm/template.go +++ b/frontend/rpm/template.go @@ -11,7 +11,6 @@ import ( "text/template" "github.com/Azure/dalec" - "github.com/moby/buildkit/util/bklog" ) const gomodsName = "__gomods" @@ -314,6 +313,7 @@ func (w *specWrapper) PreUn() fmt.Stringer { func (w *specWrapper) Post() fmt.Stringer { b := &strings.Builder{} b.WriteString("%post\n") + // TODO: can inject other post install steps here in the future keys := dalec.SortMapKeys(w.Spec.Artifacts.Services) for _, servicePath := range keys { @@ -332,7 +332,6 @@ func (w *specWrapper) PostUn() fmt.Stringer { for _, servicePath := range keys { // must include '.service' suffix cfg := w.Spec.Artifacts.Services[servicePath] - bklog.L.Infof("servicePath: %s, cfg: %v", servicePath, cfg) serviceName := filepath.Base(servicePath) if cfg.NoRestart { diff --git a/test/azlinux_test.go b/test/azlinux_test.go index c1c5286b2..639e02aa9 100644 --- a/test/azlinux_test.go +++ b/test/azlinux_test.go @@ -350,37 +350,37 @@ echo "$BAR" > bar.txt runTest(t, distroSigningTest(t, &spec, signTarget)) }) - t.Run("systemd unit", func(t *testing.T) { + t.Run("test systemd unit", func(t *testing.T) { t.Parallel() - spec := mustParse(t, ` -name: dalec-test-systemd-unit -description: "Test systemd unit" -website: https://www.github.com/Azure/dalec -version: 0.0.1 -revision: 1 -vendor: Microsoft -license: Apache 2.0 -packager: Microsoft - -targets: - mariner2: - image: - base: mcr.microsoft.com/cbl-mariner/base/core:2.0 - -dependencies: - build: - msft-golang: - runtime: - bash: - curl: - systemd: - -sources: - src: - dir: - files: - simple.service: - contents: | + spec := &dalec.Spec{ + Name: "test-systemd-unit", + Description: "Test systemd unit", + Website: "https://www.github.com/Azure/dalec", + Version: "0.0.1", + Revision: "1", + Vendor: "Microsoft", + License: "Apache 2.0", + Packager: "Microsoft ", + Targets: map[string]dalec.Target{ + "mariner2": { + Image: &dalec.ImageConfig{ + Base: "mcr.microsoft.com/cbl-mariner/base/core:2.0", + }, + }, + }, + Dependencies: &dalec.PackageDependencies{ + Build: map[string][]string{ + "msft-golang": {}, + }, + }, + Sources: map[string]dalec.Source{ + "src": { + Inline: &dalec.SourceInline{ + Dir: &dalec.SourceInlineDir{ + + Files: map[string]*dalec.SourceInlineFile{ + "simple.service": { + Contents: `, [Unit] Description=Phony Service After=network.target @@ -392,27 +392,33 @@ Restart=always [Install] WantedBy=multi-user.target - - - -build: - steps: - - command: | - cd src/ - go build -o service main.go - -artifacts: - binaries: - src/service: {} - - services: - src/simple.service: - # is the service enabled or disabled by default on boot - # this determines what goes in the sytemctl preset file - disable: true - noRestart: false - -`) +`}, + }, + }, + }, + }, + }, + Artifacts: dalec.Artifacts{ + Services: map[string]dalec.ServiceConfig{ + "src/simple.service": {}, + }, + }, + Tests: []*dalec.TestSpec{ + { + Name: "Check service files", + Files: map[string]dalec.FileCheckOutput{ + "/usr/lib/systemd/system/simple.service": { + CheckOutput: dalec.CheckOutput{Contains: []string{"ExecStart=/usr/bin/service"}}, + Permissions: 0644, + }, + "/usr/lib/systemd/system-preset/simple.preset": { + CheckOutput: dalec.CheckOutput{Contains: []string{"enable simple.service"}}, + Permissions: 0644, + }, + }, + }, + }, + } /* tests: @@ -431,6 +437,33 @@ artifacts: req := newSolveRequest(withBuildTarget(buildTarget), withSpec(ctx, t, spec)) return client.Solve(ctx, req) }) + + // Test to ensure disabling works + spec.Artifacts.Services["src/simple.service"] = dalec.ServiceConfig{ + Disable: true, + } + spec.Tests = []*dalec.TestSpec{ + { + Name: "Check service files", + Files: map[string]dalec.FileCheckOutput{ + "/usr/lib/systemd/system/simple.service": { + CheckOutput: dalec.CheckOutput{Contains: []string{"ExecStart=/usr/bin/service"}}, + Permissions: 0644, + }, + "/usr/lib/systemd/system-preset/simple.preset": { + // This is the only change from the previous test, service should be + // disabled in preset + CheckOutput: dalec.CheckOutput{Contains: []string{"disable simple.service"}}, + Permissions: 0644, + }, + }, + }, + } + + testEnv.RunTest(ctx, t, func(ctx context.Context, client gwclient.Client) (*gwclient.Result, error) { + req := newSolveRequest(withBuildTarget(buildTarget), withSpec(ctx, t, spec)) + return client.Solve(ctx, req) + }) }) t.Run("go module", func(t *testing.T) { From 55bf328083a6e302facb4f1902bde8bcbd78238d Mon Sep 17 00:00:00 2001 From: adamperlin Date: Wed, 15 May 2024 22:48:53 +0000 Subject: [PATCH 03/14] Update test to work on Azlinux3 --- test/azlinux_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/azlinux_test.go b/test/azlinux_test.go index 639e02aa9..6539ae896 100644 --- a/test/azlinux_test.go +++ b/test/azlinux_test.go @@ -362,6 +362,11 @@ echo "$BAR" > bar.txt License: "Apache 2.0", Packager: "Microsoft ", Targets: map[string]dalec.Target{ + "azlinux3": { + Image: &dalec.ImageConfig{ + Base: "azurelinuxpreview.azurecr.io/public/azurelinux/base/core:3.0", + }, + }, "mariner2": { Image: &dalec.ImageConfig{ Base: "mcr.microsoft.com/cbl-mariner/base/core:2.0", From 43bd955544f6bfd14dbdb4281b2edb6cf7ac785f Mon Sep 17 00:00:00 2001 From: adamperlin Date: Wed, 15 May 2024 23:04:21 +0000 Subject: [PATCH 04/14] Update JSON schema --- docs/spec.schema.json | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/docs/spec.schema.json b/docs/spec.schema.json index 594f2c9f4..1387b7364 100644 --- a/docs/spec.schema.json +++ b/docs/spec.schema.json @@ -79,6 +79,13 @@ }, "type": "object", "description": "ConfigFiles is a list of files that should be marked as config files in the package." + }, + "services": { + "additionalProperties": { + "$ref": "#/$defs/ServiceConfig" + }, + "type": "object", + "description": "TODO: other types of artifacts (systtemd units, libexec, etc)" } }, "additionalProperties": false, @@ -492,6 +499,26 @@ "type": "object", "description": "PostInstall is the post install configuration for the image." }, + "ServiceConfig": { + "properties": { + "name": { + "type": "string" + }, + "noRestart": { + "type": "boolean", + "description": "Some services don't support restarting, in which case this should be set to true" + }, + "disable": { + "type": "boolean" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "name" + ], + "description": "ServiceConfig is the configuration for a service to include in the package." + }, "Source": { "properties": { "image": { From 050a8fbc59b3f91b0ab057d02cb6183ad1b6d5d5 Mon Sep 17 00:00:00 2001 From: adamperlin Date: Wed, 15 May 2024 23:25:22 +0000 Subject: [PATCH 05/14] Fix some linter errors and remove unneeded explicit base images --- test/azlinux_test.go | 34 ------------------------- test/fixtures/env-multiple-commands.yml | 1 - test/fixtures/service/main.go | 4 ++- test/fixtures/systemd-unit.yml | 5 ---- 4 files changed, 3 insertions(+), 41 deletions(-) diff --git a/test/azlinux_test.go b/test/azlinux_test.go index 6539ae896..50855b973 100644 --- a/test/azlinux_test.go +++ b/test/azlinux_test.go @@ -26,15 +26,6 @@ func TestAzlinux3(t *testing.T) { testLinuxDistro(ctx, t, "azlinux3/container", "azlinux3/rpm") } -func mustParse(t *testing.T, spec string) *dalec.Spec { - t.Helper() - s, err := dalec.LoadSpec([]byte(spec)) - if err != nil { - t.Fatalf("failed to parse spec: %v", err) - } - return s -} - func testLinuxDistro(ctx context.Context, t *testing.T, buildTarget string, signTarget string) { t.Run("Fail when non-zero exit code during build", func(t *testing.T) { t.Parallel() @@ -361,18 +352,6 @@ echo "$BAR" > bar.txt Vendor: "Microsoft", License: "Apache 2.0", Packager: "Microsoft ", - Targets: map[string]dalec.Target{ - "azlinux3": { - Image: &dalec.ImageConfig{ - Base: "azurelinuxpreview.azurecr.io/public/azurelinux/base/core:3.0", - }, - }, - "mariner2": { - Image: &dalec.ImageConfig{ - Base: "mcr.microsoft.com/cbl-mariner/base/core:2.0", - }, - }, - }, Dependencies: &dalec.PackageDependencies{ Build: map[string][]string{ "msft-golang": {}, @@ -425,19 +404,6 @@ WantedBy=multi-user.target }, } - /* - tests: - - name: Check service files - files: - /usr/lib/systemd/system/simple.service: - permissions: 0644 - contains: - - "ExecStart=/usr/bin/service" - /usr/lib/systemd/system-preset/simple.preset: - permissions: 0644 - contains: - - "enable simple.service" - */ testEnv.RunTest(ctx, t, func(ctx context.Context, client gwclient.Client) (*gwclient.Result, error) { req := newSolveRequest(withBuildTarget(buildTarget), withSpec(ctx, t, spec)) return client.Solve(ctx, req) diff --git a/test/fixtures/env-multiple-commands.yml b/test/fixtures/env-multiple-commands.yml index 9a3252e17..17bd80225 100644 --- a/test/fixtures/env-multiple-commands.yml +++ b/test/fixtures/env-multiple-commands.yml @@ -11,7 +11,6 @@ license: Apache 2.0 targets: # Distro specific build requirements mariner2: - base: mcr.microsoft.com/cbl-mariner/base/core:2.0 dependencies: build: diff --git a/test/fixtures/service/main.go b/test/fixtures/service/main.go index 82738f44b..516d416dc 100644 --- a/test/fixtures/service/main.go +++ b/test/fixtures/service/main.go @@ -12,5 +12,7 @@ func main() { fmt.Fprintln(w, "Phony Service") })) - http.ListenAndServe(":8080", mux) + if err := http.ListenAndServe(":8080", mux); err != nil { + panic(err) + } } diff --git a/test/fixtures/systemd-unit.yml b/test/fixtures/systemd-unit.yml index cd75c23c5..255801bfe 100644 --- a/test/fixtures/systemd-unit.yml +++ b/test/fixtures/systemd-unit.yml @@ -9,11 +9,6 @@ vendor: Microsoft license: Apache 2.0 packager: Microsoft -targets: - mariner2: - image: - base: mcr.microsoft.com/cbl-mariner/base/core:2.0 - dependencies: build: msft-golang: From 710bbd6c163a94ada3f6147ba1a319b39ee93ffe Mon Sep 17 00:00:00 2001 From: adamperlin Date: Wed, 22 May 2024 00:38:27 +0000 Subject: [PATCH 06/14] Allow different unit types Consolidate presets for all services into one preset file per package Address review feedback --- frontend/azlinux/mariner2.go | 2 +- frontend/rpm/template.go | 65 +++++++------- frontend/rpm/template_test.go | 55 ++++++++++++ go.mod | 5 +- load.go | 14 +++ load_test.go | 18 ++++ spec.go | 41 ++++++++- test/azlinux_test.go | 104 +++++++++++++++++++++-- test/fixtures/service/socket/foo.service | 12 +++ test/fixtures/service/socket/foo.socket | 10 +++ test/fixtures/service/socket/main.py | 26 ++++++ test/fixtures/systemd-socket-unit.yml | 43 ++++++++++ 12 files changed, 350 insertions(+), 45 deletions(-) create mode 100644 test/fixtures/service/socket/foo.service create mode 100644 test/fixtures/service/socket/foo.socket create mode 100755 test/fixtures/service/socket/main.py create mode 100644 test/fixtures/systemd-socket-unit.yml diff --git a/frontend/azlinux/mariner2.go b/frontend/azlinux/mariner2.go index 0bf6bbf92..54a98f9ae 100644 --- a/frontend/azlinux/mariner2.go +++ b/frontend/azlinux/mariner2.go @@ -28,7 +28,7 @@ type mariner2 struct{} func (w mariner2) Base(resolver llb.ImageMetaResolver, opts ...llb.ConstraintsOpt) llb.State { return llb.Image(mariner2Ref, llb.WithMetaResolver(resolver), dalec.WithConstraints(opts...)).Run( - w.Install([]string{"rpm-build", "mariner-rpm-macros", "build-essential", "ca-certificates"}, installWithConstraints(opts)), + w.Install([]string{"rpm-build", "mariner-rpm-macros", "systemd-rpm-macros", "build-essential", "ca-certificates"}, installWithConstraints(opts)), dalec.WithConstraints(opts...), ).Root() } diff --git a/frontend/rpm/template.go b/frontend/rpm/template.go index 82d098c61..c4200b717 100644 --- a/frontend/rpm/template.go +++ b/frontend/rpm/template.go @@ -108,11 +108,6 @@ func (w *specWrapper) Requires() fmt.Stringer { writeDep(b, "BuildRequires", name, constraints) } - if len(w.Artifacts.Services) > 0 { - // We take advantage of the systemd-rpm-macros package to simplify the service file installation - writeDep(b, "BuildRequires", "systemd-rpm-macros", nil) - } - if len(deps.Build) > 0 && len(deps.Runtime) > 0 { b.WriteString("\n") } @@ -300,9 +295,8 @@ func (w *specWrapper) PreUn() fmt.Stringer { b := &strings.Builder{} b.WriteString("%preun\n") - keys := dalec.SortMapKeys(w.Spec.Artifacts.Services) + keys := dalec.SortMapKeys(w.Spec.Artifacts.SystemdUnits) for _, servicePath := range keys { - // must include '.service' suffix serviceName := filepath.Base(servicePath) fmt.Fprintf(b, "%%systemd_preun %s\n", serviceName) } @@ -315,11 +309,10 @@ func (w *specWrapper) Post() fmt.Stringer { b.WriteString("%post\n") // TODO: can inject other post install steps here in the future - keys := dalec.SortMapKeys(w.Spec.Artifacts.Services) + keys := dalec.SortMapKeys(w.Spec.Artifacts.SystemdUnits) for _, servicePath := range keys { - // must include '.service' suffix - serviceName := filepath.Base(servicePath) - fmt.Fprintf(b, "%%systemd_post %s\n", serviceName) + unitConf := w.Spec.Artifacts.SystemdUnits[servicePath].Artifact() + fmt.Fprintf(b, "%%systemd_post %s\n", unitConf.ResolveName(servicePath)) } return b @@ -328,16 +321,19 @@ func (w *specWrapper) Post() fmt.Stringer { func (w *specWrapper) PostUn() fmt.Stringer { b := &strings.Builder{} b.WriteString("%postun\n") - keys := dalec.SortMapKeys(w.Spec.Artifacts.Services) + keys := dalec.SortMapKeys(w.Spec.Artifacts.SystemdUnits) for _, servicePath := range keys { - // must include '.service' suffix - cfg := w.Spec.Artifacts.Services[servicePath] - serviceName := filepath.Base(servicePath) + cfg := w.Spec.Artifacts.SystemdUnits[servicePath] + a := cfg.Artifact() + serviceName := a.ResolveName(servicePath) - if cfg.NoRestart { - fmt.Fprintf(b, "%%systemd_postun %s\n", serviceName) - } else { + // TODO: verify mutual exclusivity of these options + if cfg.Restart { fmt.Fprintf(b, "%%systemd_postun_with_restart %s\n", serviceName) + } else if cfg.Reload { + fmt.Fprintf(b, "%%systemd_postun_with_reload %s\n", serviceName) + } else { + fmt.Fprintf(b, "%%systemd_postun %s\n", serviceName) } } @@ -411,24 +407,28 @@ func (w *specWrapper) Install() fmt.Stringer { cfg := w.Spec.Artifacts.ConfigFiles[c] copyArtifact(`%{buildroot}/%{_sysconfdir}`, c, cfg) } - serviceKeys := dalec.SortMapKeys(w.Spec.Artifacts.Services) + + serviceKeys := dalec.SortMapKeys(w.Spec.Artifacts.SystemdUnits) + presetName := "%{name}.preset" for _, p := range serviceKeys { - cfg := w.Spec.Artifacts.Services[p] + cfg := w.Spec.Artifacts.SystemdUnits[p] // must include '.service' suffix in name copyArtifact(`%{buildroot}/%{_unitdir}`, p, cfg.Artifact()) - verb := "enable" - if cfg.Disable { - verb = "disable" + verb := "disable" + if cfg.Enable { + verb = "enable" } - serviceName := filepath.Base(p) + unitName := filepath.Base(p) if cfg.Name != "" { - serviceName = cfg.Name + unitName = cfg.Name } - presetName := strings.TrimSuffix(serviceName, ".service") + ".preset" - fmt.Fprintf(b, "echo '%s %s' >> %s\n", verb, serviceName, presetName) + fmt.Fprintf(b, "echo '%s %s' >> '%s'\n", verb, unitName, presetName) + } + + if len(serviceKeys) > 0 { copyArtifact(`%{buildroot}/%{_presetdir}`, presetName, dalec.ArtifactConfig{}) } @@ -446,7 +446,7 @@ func (w *specWrapper) Files() fmt.Stringer { binKeys := dalec.SortMapKeys(w.Spec.Artifacts.Binaries) for _, p := range binKeys { cfg := w.Spec.Artifacts.Binaries[p] - full := filepath.Join(`%{_bindir}/`, cfg.SubPath, filepath.Base(p)) + full := filepath.Join(`%{_bindir}/`, cfg.SubPath, cfg.Name) fmt.Fprintln(b, full) } @@ -476,14 +476,15 @@ func (w *specWrapper) Files() fmt.Stringer { fmt.Fprintln(b, fullDirective) } - serviceKeys := dalec.SortMapKeys(w.Spec.Artifacts.Services) + serviceKeys := dalec.SortMapKeys(w.Spec.Artifacts.SystemdUnits) for _, p := range serviceKeys { serviceName := filepath.Base(p) - prefixName := strings.TrimSuffix(serviceName, ".service") + ".preset" unitPath := filepath.Join(`%{_unitdir}/`, serviceName) - prefixPath := filepath.Join(`%{_presetdir}/`, prefixName) fmt.Fprintln(b, unitPath) - fmt.Fprintln(b, prefixPath) + } + + if len(serviceKeys) > 0 { + fmt.Fprintln(b, "%{_presetdir}/%{name}.preset") } return b diff --git a/frontend/rpm/template_test.go b/frontend/rpm/template_test.go index 7b34f389d..2d2fc6294 100644 --- a/frontend/rpm/template_test.go +++ b/frontend/rpm/template_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/Azure/dalec" + "gotest.tools/v3/assert" ) func TestTemplateSources(t *testing.T) { @@ -206,3 +207,57 @@ func TestTemplateSources(t *testing.T) { } }) } + +func TestTemplate_Artifacts(t *testing.T) { + t.Run("systemd artifacts with restart", func(t *testing.T) { + w := &specWrapper{Spec: &dalec.Spec{ + Artifacts: dalec.Artifacts{ + SystemdUnits: map[string]dalec.SystemdUnitConfig{ + "test.service": { + Restart: true, + }, + }, + }, + }} + + got := w.PostUn().String() + want := `%postun +%systemd_postun_with_restart test.service +` + assert.Equal(t, want, got) + }) + + t.Run("systemd artifacts with reload", func(t *testing.T) { + w := &specWrapper{Spec: &dalec.Spec{ + Artifacts: dalec.Artifacts{ + SystemdUnits: map[string]dalec.SystemdUnitConfig{ + "test.service": { + Reload: true, + }, + }, + }, + }} + + got := w.PostUn().String() + want := `%postun +%systemd_postun_with_reload test.service +` + assert.Equal(t, want, got) + }) + + t.Run("systemd artifacts no reload/restart", func(t *testing.T) { + w := &specWrapper{Spec: &dalec.Spec{ + Artifacts: dalec.Artifacts{ + SystemdUnits: map[string]dalec.SystemdUnitConfig{ + "test.service": {}, + }, + }, + }} + + got := w.PostUn().String() + want := `%postun +%systemd_postun test.service +` + assert.Equal(t, want, got) + }) +} diff --git a/go.mod b/go.mod index b2fb71010..d5796b73b 100644 --- a/go.mod +++ b/go.mod @@ -19,11 +19,13 @@ require ( github.com/opencontainers/image-spec v1.1.0 github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.9.3 + github.com/stretchr/testify v1.8.4 github.com/tonistiigi/fsutil v0.0.0-20240424095704-91a3fc46842c go.opentelemetry.io/otel v1.21.0 golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81 golang.org/x/sys v0.18.0 google.golang.org/grpc v1.59.0 + gotest.tools/v3 v3.5.0 ) require ( @@ -41,6 +43,7 @@ require ( github.com/containerd/log v0.1.0 // indirect github.com/containerd/ttrpc v1.2.2 // indirect github.com/containerd/typeurl/v2 v2.1.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.5.0 // indirect github.com/docker/docker v25.0.3+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect @@ -65,6 +68,7 @@ require ( github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/sys/signal v0.7.0 // indirect github.com/morikuni/aec v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.17.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/common v0.44.0 // indirect @@ -101,5 +105,4 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - gotest.tools/v3 v3.5.0 // indirect ) diff --git a/load.go b/load.go index 630cdab07..2578b3e63 100644 --- a/load.go +++ b/load.go @@ -390,6 +390,20 @@ func (s Spec) Validate() error { } } + for p, art := range s.Artifacts.SystemdUnits { + if err := art.validate(); err != nil { + return errors.Wrapf(err, "error validating config for systemd unit with path %s", p) + } + } + + return nil +} + +func (s *SystemdUnitConfig) validate() error { + if s.Reload && s.Restart { + return fmt.Errorf("cannot specify both reload and restart") + } + return nil } diff --git a/load_test.go b/load_test.go index 9de99e61b..4614d6026 100644 --- a/load_test.go +++ b/load_test.go @@ -8,6 +8,8 @@ import ( "os" "reflect" "testing" + + "github.com/stretchr/testify/assert" ) //go:embed test/fixtures/unmarshall/source-inline.yml @@ -470,3 +472,19 @@ sources: } }) } + +func TestArtifact_Validation(t *testing.T) { + spec := &Spec{ + Artifacts: Artifacts{ + SystemdUnits: map[string]SystemdUnitConfig{ + "unit.service": { + Restart: true, + Reload: true, + }, + }, + }, + } + want := errors.New("error validating config for systemd unit with path unit.service: cannot specify both reload and restart") + err := spec.Validate() + assert.Equal(t, want.Error(), err.Error()) +} diff --git a/spec.go b/spec.go index 48f64a747..2ac1c3fef 100644 --- a/spec.go +++ b/spec.go @@ -4,6 +4,7 @@ package dalec import ( "fmt" "io/fs" + "path/filepath" "regexp" "strings" "time" @@ -131,8 +132,35 @@ type Artifacts struct { Directories *CreateArtifactDirectories `yaml:"createDirectories,omitempty" json:"createDirectories,omitempty"` // ConfigFiles is a list of files that should be marked as config files in the package. ConfigFiles map[string]ArtifactConfig `yaml:"configFiles,omitempty" json:"configFiles,omitempty"` - // TODO: other types of artifacts (systtemd units, libexec, etc) - Services map[string]ServiceConfig `yaml:"services,omitempty" json:"services,omitempty"` + + // SystemdUnits is a list of systemd units to include in the package. + SystemdUnits map[string]SystemdUnitConfig `yaml:"systemdUnits,omitempty" json:"systemdUnits,omitempty"` + + // TODO: other types of artifacts (libexec, etc) +} + +type SystemdUnitConfig struct { + // Name is the name systemd unit should be copied under. + // Nested paths are not supported. It is the user's responsibility + // to name the service with the appropriate extension, i.e. .service, .timer, etc. + Name string `yaml:"name" json:"name"` + + // Enable is used to enable the systemd unit on install + // This determines what will be written to a systemd preset file + Enable bool `yaml:"enable" json:"enabled"` + + // Should service be reloaded instead of restarted? (Mutually exclusive with `Restart`) + Reload bool + + // Can service be restarted? (Mutually exclusive with Reload) + Restart bool +} + +func (s SystemdUnitConfig) Artifact() ArtifactConfig { + return ArtifactConfig{ + SubPath: "", + Name: s.Name, + } } // CreateArtifactDirectories describes various directories that should be created on install. @@ -164,6 +192,13 @@ type ArtifactConfig struct { Name string `yaml:"name,omitempty" json:"name,omitempty"` } +func (a *ArtifactConfig) ResolveName(path string) string { + if a.Name != "" { + return a.Name + } + return filepath.Base(path) +} + // ServiceConfig is the configuration for a service to include in the package. type ServiceConfig struct { Name string `yaml:"name" json:"name" jsonschema:"omitempty"` @@ -196,7 +231,7 @@ func (a *Artifacts) IsEmpty() bool { return false } - if len(a.Services) > 0 { + if len(a.SystemdUnits) > 0 { return false } diff --git a/test/azlinux_test.go b/test/azlinux_test.go index 50855b973..99445ea9f 100644 --- a/test/azlinux_test.go +++ b/test/azlinux_test.go @@ -383,8 +383,10 @@ WantedBy=multi-user.target }, }, Artifacts: dalec.Artifacts{ - Services: map[string]dalec.ServiceConfig{ - "src/simple.service": {}, + SystemdUnits: map[string]dalec.SystemdUnitConfig{ + "src/simple.service": { + Enable: true, + }, }, }, Tests: []*dalec.TestSpec{ @@ -395,7 +397,7 @@ WantedBy=multi-user.target CheckOutput: dalec.CheckOutput{Contains: []string{"ExecStart=/usr/bin/service"}}, Permissions: 0644, }, - "/usr/lib/systemd/system-preset/simple.preset": { + "/usr/lib/systemd/system-preset/test-systemd-unit.preset": { CheckOutput: dalec.CheckOutput{Contains: []string{"enable simple.service"}}, Permissions: 0644, }, @@ -409,10 +411,8 @@ WantedBy=multi-user.target return client.Solve(ctx, req) }) - // Test to ensure disabling works - spec.Artifacts.Services["src/simple.service"] = dalec.ServiceConfig{ - Disable: true, - } + // Test to ensure disabling works by default + spec.Artifacts.SystemdUnits["src/simple.service"] = dalec.SystemdUnitConfig{} spec.Tests = []*dalec.TestSpec{ { Name: "Check service files", @@ -421,7 +421,7 @@ WantedBy=multi-user.target CheckOutput: dalec.CheckOutput{Contains: []string{"ExecStart=/usr/bin/service"}}, Permissions: 0644, }, - "/usr/lib/systemd/system-preset/simple.preset": { + "/usr/lib/systemd/system-preset/test-systemd-unit.preset": { // This is the only change from the previous test, service should be // disabled in preset CheckOutput: dalec.CheckOutput{Contains: []string{"disable simple.service"}}, @@ -437,6 +437,94 @@ WantedBy=multi-user.target }) }) + t.Run("test systemd unit multiple components", func(t *testing.T) { + t.Parallel() + spec := &dalec.Spec{ + Name: "test-systemd-unit", + Description: "Test systemd unit", + Website: "https://www.github.com/Azure/dalec", + Version: "0.0.1", + Revision: "1", + Vendor: "Microsoft", + License: "Apache 2.0", + Packager: "Microsoft ", + Dependencies: &dalec.PackageDependencies{ + Build: map[string][]string{ + "msft-golang": {}, + }, + }, + Sources: map[string]dalec.Source{ + "src": { + Inline: &dalec.SourceInline{ + Dir: &dalec.SourceInlineDir{ + + Files: map[string]*dalec.SourceInlineFile{ + "foo.service": { + Contents: ` +# simple-socket.service +[Unit] +Description=Foo Service +After=network.target foo.socket +Requires=foo.socket + +[Service] +Type=simple +ExecStart=/usr/bin/foo +ExecReload=/bin/kill -HUP $MAINPID +StandardOutput=journal +StandardError=journal +`}, + + "foo.socket": { + Contents: ` +[Unit] +Description=foo socket +PartOf=foo.service + +[Socket] +ListenStream=127.0.0.1:8080 + +[Install] +WantedBy=sockets.target + `, + }, + }, + }, + }, + }, + }, + Artifacts: dalec.Artifacts{ + SystemdUnits: map[string]dalec.SystemdUnitConfig{ + "src/foo.service": {}, + "src/foo.socket": { + Enable: true, + }, + }, + }, + Tests: []*dalec.TestSpec{ + { + Name: "Check service files", + Files: map[string]dalec.FileCheckOutput{ + "/usr/lib/systemd/system/foo.service": { + CheckOutput: dalec.CheckOutput{Contains: []string{"ExecStart=/usr/bin/foo"}}, + Permissions: 0644, + }, + "/usr/lib/systemd/system-preset/test-systemd-unit.preset": { + CheckOutput: dalec.CheckOutput{Contains: []string{"enable foo.socket", + "disable foo.service"}}, + Permissions: 0644, + }, + }, + }, + }, + } + + testEnv.RunTest(ctx, t, func(ctx context.Context, client gwclient.Client) (*gwclient.Result, error) { + req := newSolveRequest(withBuildTarget(buildTarget), withSpec(ctx, t, spec)) + return client.Solve(ctx, req) + }) + }) + t.Run("go module", func(t *testing.T) { t.Parallel() ctx := startTestSpan(baseCtx, t) diff --git a/test/fixtures/service/socket/foo.service b/test/fixtures/service/socket/foo.service new file mode 100644 index 000000000..6201babba --- /dev/null +++ b/test/fixtures/service/socket/foo.service @@ -0,0 +1,12 @@ +# simple-socket.service +[Unit] +Description=Foo Service +After=network.target foo.socket +Requires=foo.socket + +[Service] +Type=simple +ExecStart=/usr/bin/foo +ExecReload=/bin/kill -HUP $MAINPID +StandardOutput=journal +StandardError=journal \ No newline at end of file diff --git a/test/fixtures/service/socket/foo.socket b/test/fixtures/service/socket/foo.socket new file mode 100644 index 000000000..e088225e3 --- /dev/null +++ b/test/fixtures/service/socket/foo.socket @@ -0,0 +1,10 @@ +# simple-socket.socket +[Unit] +Description=foo socket +PartOf=foo.service + +[Socket] +ListenStream=127.0.0.1:8080 + +[Install] +WantedBy=sockets.target diff --git a/test/fixtures/service/socket/main.py b/test/fixtures/service/socket/main.py new file mode 100755 index 000000000..16691c98d --- /dev/null +++ b/test/fixtures/service/socket/main.py @@ -0,0 +1,26 @@ +#!/usr/bin/python3 + +import sys +import os +import socket +from socketserver import TCPServer, StreamRequestHandler + +class Handler(StreamRequestHandler): + def handle(self): + # self.request is the TCP socket connected to the client + self.data = self.request.recv(1024).strip() + print("Received from {}:".format(self.client_address[0])) + print(type(self.data)) + return_msg = "Received: {}".format(self.data) + self.request.sendall(bytes(return_msg.encode('utf-8'))) + +class Server(TCPServer): + SYSTEMD_FD = 3 + def __init__(self, address, handler_cls): + # bind and activate must be false because systemd will handle this + TCPServer.__init__(self, address, handler_cls, bind_and_activate=False) + self.socket = socket.fromfd(self.SYSTEMD_FD, self.address_family, self.socket_type) + +if __name__ == "__main__": + server = Server(("localhost", 8080), Handler) + server.serve_forever() diff --git a/test/fixtures/systemd-socket-unit.yml b/test/fixtures/systemd-socket-unit.yml new file mode 100644 index 000000000..096be0674 --- /dev/null +++ b/test/fixtures/systemd-socket-unit.yml @@ -0,0 +1,43 @@ +# syntax=local/dalec/frontend + +name: dalec-test-systemd-socket +description: A test fixture which tests the installation of a simple systemd unit +website: https://www.github.com/Azure/dalec +version: 0.0.1 +revision: 1 +vendor: Microsoft +license: Apache 2.0 +packager: Microsoft + +dependencies: + build: + msft-golang: + runtime: + nmap-ncat: + python3: + bash: + curl: + systemd: + +sources: + src: + context: {} + path: test/fixtures/service/socket + includes: + - test/fixtures/service/socket/ + +artifacts: + binaries: + src/main.py: + name: foo + + systemdUnits: + src/foo.service: {} + src/foo.socket: + enable: false + +image: + # entrypoint: /bin/service + entrypoint: /sbin/init + stop_signal: SIGRTMIN+3 + user: root \ No newline at end of file From be71e37e23252d499e6031393ebb28f019c93cb7 Mon Sep 17 00:00:00 2001 From: adamperlin Date: Wed, 22 May 2024 00:44:34 +0000 Subject: [PATCH 07/14] Update json schema --- docs/spec.schema.json | 54 +++++++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/docs/spec.schema.json b/docs/spec.schema.json index 1387b7364..4be604c7b 100644 --- a/docs/spec.schema.json +++ b/docs/spec.schema.json @@ -80,12 +80,12 @@ "type": "object", "description": "ConfigFiles is a list of files that should be marked as config files in the package." }, - "services": { + "systemdUnits": { "additionalProperties": { - "$ref": "#/$defs/ServiceConfig" + "$ref": "#/$defs/SystemdUnitConfig" }, "type": "object", - "description": "TODO: other types of artifacts (systtemd units, libexec, etc)" + "description": "SystemdUnits is a list of systemd units to include in the package." } }, "additionalProperties": false, @@ -499,26 +499,6 @@ "type": "object", "description": "PostInstall is the post install configuration for the image." }, - "ServiceConfig": { - "properties": { - "name": { - "type": "string" - }, - "noRestart": { - "type": "boolean", - "description": "Some services don't support restarting, in which case this should be set to true" - }, - "disable": { - "type": "boolean" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "name" - ], - "description": "ServiceConfig is the configuration for a service to include in the package." - }, "Source": { "properties": { "image": { @@ -927,6 +907,34 @@ ], "description": "SymlinkTarget specifies the properties of a symlink" }, + "SystemdUnitConfig": { + "properties": { + "name": { + "type": "string", + "description": "Name is the name systemd unit should be copied under.\nNested paths are not supported. It is the user's responsibility\nto name the service with the appropriate extension, i.e. .service, .timer, etc." + }, + "enabled": { + "type": "boolean", + "description": "Enable is used to enable the systemd unit on install\nThis determines what will be written to a systemd preset file" + }, + "Reload": { + "type": "boolean", + "description": "Should service be reloaded instead of restarted? (Mutually exclusive with `Restart`)" + }, + "Restart": { + "type": "boolean", + "description": "Can service be restarted? (Mutually exclusive with Reload)" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "name", + "enabled", + "Reload", + "Restart" + ] + }, "Target": { "properties": { "dependencies": { From 0c420ad4263373c287685667ee17972f1cad09f5 Mon Sep 17 00:00:00 2001 From: adamperlin Date: Wed, 22 May 2024 17:04:16 +0000 Subject: [PATCH 08/14] Switch systemd unit upgrade reload policy to enum --- docs/spec.schema.json | 33 ++++++++++++++++----------------- frontend/rpm/template.go | 8 ++++---- frontend/rpm/template_test.go | 4 ++-- load.go | 22 ++++++++-------------- load_test.go | 19 +++++++++++-------- spec.go | 13 ++++++++----- 6 files changed, 49 insertions(+), 50 deletions(-) diff --git a/docs/spec.schema.json b/docs/spec.schema.json index 49e824f0c..01736d1a7 100644 --- a/docs/spec.schema.json +++ b/docs/spec.schema.json @@ -80,13 +80,6 @@ "type": "object", "description": "ConfigFiles is a list of files that should be marked as config files in the package." }, - "systemdUnits": { - "additionalProperties": { - "$ref": "#/$defs/SystemdUnitConfig" - }, - "type": "object", - "description": "SystemdUnits is a list of systemd units to include in the package." - }, "docs": { "additionalProperties": { "$ref": "#/$defs/ArtifactConfig" @@ -100,6 +93,13 @@ }, "type": "object", "description": "Licenses is a list of doc files included in the package" + }, + "systemdUnits": { + "additionalProperties": { + "$ref": "#/$defs/SystemdUnitConfig" + }, + "type": "object", + "description": "SystemdUnits is a list of systemd units to include in the package." } }, "additionalProperties": false, @@ -931,22 +931,21 @@ "type": "boolean", "description": "Enable is used to enable the systemd unit on install\nThis determines what will be written to a systemd preset file" }, - "Reload": { - "type": "boolean", - "description": "Should service be reloaded instead of restarted? (Mutually exclusive with `Restart`)" - }, - "Restart": { - "type": "boolean", - "description": "Can service be restarted? (Mutually exclusive with Reload)" + "systemdRefreshPolicy": { + "type": "string", + "enum": [ + "none", + "reload", + "restart" + ], + "description": "On upgrade, should the service be restarted, reloaded, or neither" } }, "additionalProperties": false, "type": "object", "required": [ "name", - "enabled", - "Reload", - "Restart" + "enabled" ] }, "Target": { diff --git a/frontend/rpm/template.go b/frontend/rpm/template.go index a2e0be8a3..b5a7d498e 100644 --- a/frontend/rpm/template.go +++ b/frontend/rpm/template.go @@ -327,12 +327,12 @@ func (w *specWrapper) PostUn() fmt.Stringer { a := cfg.Artifact() serviceName := a.ResolveName(servicePath) - // TODO: verify mutual exclusivity of these options - if cfg.Restart { + switch cfg.UpgradeRefreshPolicy { + case dalec.SystemdUnitUpgradePolicyRestart: fmt.Fprintf(b, "%%systemd_postun_with_restart %s\n", serviceName) - } else if cfg.Reload { + case dalec.SystemdUnitUpgradePolicyReload: fmt.Fprintf(b, "%%systemd_postun_with_reload %s\n", serviceName) - } else { + default: fmt.Fprintf(b, "%%systemd_postun %s\n", serviceName) } } diff --git a/frontend/rpm/template_test.go b/frontend/rpm/template_test.go index 2d2fc6294..183bf56a5 100644 --- a/frontend/rpm/template_test.go +++ b/frontend/rpm/template_test.go @@ -214,7 +214,7 @@ func TestTemplate_Artifacts(t *testing.T) { Artifacts: dalec.Artifacts{ SystemdUnits: map[string]dalec.SystemdUnitConfig{ "test.service": { - Restart: true, + UpgradeRefreshPolicy: dalec.SystemdUnitUpgradePolicyRestart, }, }, }, @@ -232,7 +232,7 @@ func TestTemplate_Artifacts(t *testing.T) { Artifacts: dalec.Artifacts{ SystemdUnits: map[string]dalec.SystemdUnitConfig{ "test.service": { - Reload: true, + UpgradeRefreshPolicy: dalec.SystemdUnitUpgradePolicyReload, }, }, }, diff --git a/load.go b/load.go index 2578b3e63..468e91293 100644 --- a/load.go +++ b/load.go @@ -362,6 +362,14 @@ func (s *Spec) FillDefaults() { s.Patches[k][i].Strip = &strip } } + + for p := range s.Artifacts.SystemdUnits { + conf := s.Artifacts.SystemdUnits[p] + if conf.UpgradeRefreshPolicy == "" { + conf.UpgradeRefreshPolicy = SystemdUnitUpgradePolicyNone + } + s.Artifacts.SystemdUnits[p] = conf + } } func (s Spec) Validate() error { @@ -390,20 +398,6 @@ func (s Spec) Validate() error { } } - for p, art := range s.Artifacts.SystemdUnits { - if err := art.validate(); err != nil { - return errors.Wrapf(err, "error validating config for systemd unit with path %s", p) - } - } - - return nil -} - -func (s *SystemdUnitConfig) validate() error { - if s.Reload && s.Restart { - return fmt.Errorf("cannot specify both reload and restart") - } - return nil } diff --git a/load_test.go b/load_test.go index 4614d6026..b43e8fd1d 100644 --- a/load_test.go +++ b/load_test.go @@ -473,18 +473,21 @@ sources: }) } -func TestArtifact_Validation(t *testing.T) { +func TestArtifact_Defaults(t *testing.T) { spec := &Spec{ Artifacts: Artifacts{ SystemdUnits: map[string]SystemdUnitConfig{ - "unit.service": { - Restart: true, - Reload: true, - }, + "unit.service": {}, }, }, } - want := errors.New("error validating config for systemd unit with path unit.service: cannot specify both reload and restart") - err := spec.Validate() - assert.Equal(t, want.Error(), err.Error()) + + spec.FillDefaults() + + wantConf := SystemdUnitConfig{ + UpgradeRefreshPolicy: "none", + } + + unitConf := spec.Artifacts.SystemdUnits["unit.service"] + assert.Equal(t, wantConf, unitConf) } diff --git a/spec.go b/spec.go index d254ca9ff..656e62f44 100644 --- a/spec.go +++ b/spec.go @@ -141,6 +141,12 @@ type Artifacts struct { // TODO: other types of artifacts (systtemd units, libexec, etc) } +const ( + SystemdUnitUpgradePolicyNone = "none" + SystemdUnitUpgradePolicyReload = "reload" + SystemdUnitUpgradePolicyRestart = "restart" +) + type SystemdUnitConfig struct { // Name is the name systemd unit should be copied under. // Nested paths are not supported. It is the user's responsibility @@ -151,11 +157,8 @@ type SystemdUnitConfig struct { // This determines what will be written to a systemd preset file Enable bool `yaml:"enable" json:"enabled"` - // Should service be reloaded instead of restarted? (Mutually exclusive with `Restart`) - Reload bool - - // Can service be restarted? (Mutually exclusive with Reload) - Restart bool + // On upgrade, should the service be restarted, reloaded, or neither + UpgradeRefreshPolicy string `yaml:"upgradeRefreshPolicy,omitempty" json:"systemdRefreshPolicy,omitempty" jsonschema:"enum=none,enum=reload,enum=restart"` } func (s SystemdUnitConfig) Artifact() ArtifactConfig { From 4b25a20fe9e3aeac99f0f84bd8a4f5d82156a637 Mon Sep 17 00:00:00 2001 From: adamperlin Date: Wed, 22 May 2024 17:14:42 +0000 Subject: [PATCH 09/14] Update comment --- frontend/rpm/template.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/rpm/template.go b/frontend/rpm/template.go index b5a7d498e..cb1ace48a 100644 --- a/frontend/rpm/template.go +++ b/frontend/rpm/template.go @@ -412,7 +412,7 @@ func (w *specWrapper) Install() fmt.Stringer { presetName := "%{name}.preset" for _, p := range serviceKeys { cfg := w.Spec.Artifacts.SystemdUnits[p] - // must include '.service' suffix in name + // must include systemd unit extension (.service, .socket, .timer, etc.) in name copyArtifact(`%{buildroot}/%{_unitdir}`, p, cfg.Artifact()) verb := "disable" From 24c1a177d075ef1c47c9a904e823fb4cb60d3ba0 Mon Sep 17 00:00:00 2001 From: adamperlin Date: Wed, 22 May 2024 23:16:10 +0000 Subject: [PATCH 10/14] Address review feedback --- docs/spec.schema.json | 13 +----- frontend/rpm/template.go | 10 +---- frontend/rpm/template_test.go | 55 ++++-------------------- load.go | 7 --- load_test.go | 21 --------- spec.go | 7 +-- test/fixtures/service/main.go | 18 -------- test/fixtures/service/simple.service | 11 ----- test/fixtures/service/socket/foo.service | 12 ------ test/fixtures/service/socket/foo.socket | 10 ----- test/fixtures/service/socket/main.py | 26 ----------- 11 files changed, 14 insertions(+), 176 deletions(-) delete mode 100644 test/fixtures/service/main.go delete mode 100644 test/fixtures/service/simple.service delete mode 100644 test/fixtures/service/socket/foo.service delete mode 100644 test/fixtures/service/socket/foo.socket delete mode 100755 test/fixtures/service/socket/main.py diff --git a/docs/spec.schema.json b/docs/spec.schema.json index 01736d1a7..4f332de26 100644 --- a/docs/spec.schema.json +++ b/docs/spec.schema.json @@ -927,25 +927,16 @@ "type": "string", "description": "Name is the name systemd unit should be copied under.\nNested paths are not supported. It is the user's responsibility\nto name the service with the appropriate extension, i.e. .service, .timer, etc." }, - "enabled": { + "enable": { "type": "boolean", "description": "Enable is used to enable the systemd unit on install\nThis determines what will be written to a systemd preset file" - }, - "systemdRefreshPolicy": { - "type": "string", - "enum": [ - "none", - "reload", - "restart" - ], - "description": "On upgrade, should the service be restarted, reloaded, or neither" } }, "additionalProperties": false, "type": "object", "required": [ "name", - "enabled" + "enable" ] }, "Target": { diff --git a/frontend/rpm/template.go b/frontend/rpm/template.go index cb1ace48a..8b66bef9c 100644 --- a/frontend/rpm/template.go +++ b/frontend/rpm/template.go @@ -326,15 +326,7 @@ func (w *specWrapper) PostUn() fmt.Stringer { cfg := w.Spec.Artifacts.SystemdUnits[servicePath] a := cfg.Artifact() serviceName := a.ResolveName(servicePath) - - switch cfg.UpgradeRefreshPolicy { - case dalec.SystemdUnitUpgradePolicyRestart: - fmt.Fprintf(b, "%%systemd_postun_with_restart %s\n", serviceName) - case dalec.SystemdUnitUpgradePolicyReload: - fmt.Fprintf(b, "%%systemd_postun_with_reload %s\n", serviceName) - default: - fmt.Fprintf(b, "%%systemd_postun %s\n", serviceName) - } + fmt.Fprintf(b, "%%systemd_postun %s\n", serviceName) } return b diff --git a/frontend/rpm/template_test.go b/frontend/rpm/template_test.go index 183bf56a5..01263264d 100644 --- a/frontend/rpm/template_test.go +++ b/frontend/rpm/template_test.go @@ -209,55 +209,18 @@ func TestTemplateSources(t *testing.T) { } func TestTemplate_Artifacts(t *testing.T) { - t.Run("systemd artifacts with restart", func(t *testing.T) { - w := &specWrapper{Spec: &dalec.Spec{ - Artifacts: dalec.Artifacts{ - SystemdUnits: map[string]dalec.SystemdUnitConfig{ - "test.service": { - UpgradeRefreshPolicy: dalec.SystemdUnitUpgradePolicyRestart, - }, - }, - }, - }} - got := w.PostUn().String() - want := `%postun -%systemd_postun_with_restart test.service -` - assert.Equal(t, want, got) - }) - - t.Run("systemd artifacts with reload", func(t *testing.T) { - w := &specWrapper{Spec: &dalec.Spec{ - Artifacts: dalec.Artifacts{ - SystemdUnits: map[string]dalec.SystemdUnitConfig{ - "test.service": { - UpgradeRefreshPolicy: dalec.SystemdUnitUpgradePolicyReload, - }, - }, + w := &specWrapper{Spec: &dalec.Spec{ + Artifacts: dalec.Artifacts{ + SystemdUnits: map[string]dalec.SystemdUnitConfig{ + "test.service": {}, }, - }} - - got := w.PostUn().String() - want := `%postun -%systemd_postun_with_reload test.service -` - assert.Equal(t, want, got) - }) - - t.Run("systemd artifacts no reload/restart", func(t *testing.T) { - w := &specWrapper{Spec: &dalec.Spec{ - Artifacts: dalec.Artifacts{ - SystemdUnits: map[string]dalec.SystemdUnitConfig{ - "test.service": {}, - }, - }, - }} + }, + }} - got := w.PostUn().String() - want := `%postun + got := w.PostUn().String() + want := `%postun %systemd_postun test.service ` - assert.Equal(t, want, got) - }) + assert.Equal(t, want, got) } diff --git a/load.go b/load.go index 468e91293..155171163 100644 --- a/load.go +++ b/load.go @@ -363,13 +363,6 @@ func (s *Spec) FillDefaults() { } } - for p := range s.Artifacts.SystemdUnits { - conf := s.Artifacts.SystemdUnits[p] - if conf.UpgradeRefreshPolicy == "" { - conf.UpgradeRefreshPolicy = SystemdUnitUpgradePolicyNone - } - s.Artifacts.SystemdUnits[p] = conf - } } func (s Spec) Validate() error { diff --git a/load_test.go b/load_test.go index b43e8fd1d..9de99e61b 100644 --- a/load_test.go +++ b/load_test.go @@ -8,8 +8,6 @@ import ( "os" "reflect" "testing" - - "github.com/stretchr/testify/assert" ) //go:embed test/fixtures/unmarshall/source-inline.yml @@ -472,22 +470,3 @@ sources: } }) } - -func TestArtifact_Defaults(t *testing.T) { - spec := &Spec{ - Artifacts: Artifacts{ - SystemdUnits: map[string]SystemdUnitConfig{ - "unit.service": {}, - }, - }, - } - - spec.FillDefaults() - - wantConf := SystemdUnitConfig{ - UpgradeRefreshPolicy: "none", - } - - unitConf := spec.Artifacts.SystemdUnits["unit.service"] - assert.Equal(t, wantConf, unitConf) -} diff --git a/spec.go b/spec.go index 656e62f44..10eee12e6 100644 --- a/spec.go +++ b/spec.go @@ -151,14 +151,11 @@ type SystemdUnitConfig struct { // Name is the name systemd unit should be copied under. // Nested paths are not supported. It is the user's responsibility // to name the service with the appropriate extension, i.e. .service, .timer, etc. - Name string `yaml:"name" json:"name"` + Name string `yaml:"name,omitempty" json:"name"` // Enable is used to enable the systemd unit on install // This determines what will be written to a systemd preset file - Enable bool `yaml:"enable" json:"enabled"` - - // On upgrade, should the service be restarted, reloaded, or neither - UpgradeRefreshPolicy string `yaml:"upgradeRefreshPolicy,omitempty" json:"systemdRefreshPolicy,omitempty" jsonschema:"enum=none,enum=reload,enum=restart"` + Enable bool `yaml:"enable,omitempty" json:"enable"` } func (s SystemdUnitConfig) Artifact() ArtifactConfig { diff --git a/test/fixtures/service/main.go b/test/fixtures/service/main.go deleted file mode 100644 index 516d416dc..000000000 --- a/test/fixtures/service/main.go +++ /dev/null @@ -1,18 +0,0 @@ -package main - -import ( - "fmt" - "net/http" -) - -func main() { - var mux = http.NewServeMux() - mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - fmt.Fprintln(w, "Phony Service") - })) - - if err := http.ListenAndServe(":8080", mux); err != nil { - panic(err) - } -} diff --git a/test/fixtures/service/simple.service b/test/fixtures/service/simple.service deleted file mode 100644 index f75646ecc..000000000 --- a/test/fixtures/service/simple.service +++ /dev/null @@ -1,11 +0,0 @@ -[Unit] -Description=Phony Service -After=network.target - -[Service] -Type=simple -ExecStart=/usr/bin/service -Restart=always - -[Install] -WantedBy=multi-user.target \ No newline at end of file diff --git a/test/fixtures/service/socket/foo.service b/test/fixtures/service/socket/foo.service deleted file mode 100644 index 6201babba..000000000 --- a/test/fixtures/service/socket/foo.service +++ /dev/null @@ -1,12 +0,0 @@ -# simple-socket.service -[Unit] -Description=Foo Service -After=network.target foo.socket -Requires=foo.socket - -[Service] -Type=simple -ExecStart=/usr/bin/foo -ExecReload=/bin/kill -HUP $MAINPID -StandardOutput=journal -StandardError=journal \ No newline at end of file diff --git a/test/fixtures/service/socket/foo.socket b/test/fixtures/service/socket/foo.socket deleted file mode 100644 index e088225e3..000000000 --- a/test/fixtures/service/socket/foo.socket +++ /dev/null @@ -1,10 +0,0 @@ -# simple-socket.socket -[Unit] -Description=foo socket -PartOf=foo.service - -[Socket] -ListenStream=127.0.0.1:8080 - -[Install] -WantedBy=sockets.target diff --git a/test/fixtures/service/socket/main.py b/test/fixtures/service/socket/main.py deleted file mode 100755 index 16691c98d..000000000 --- a/test/fixtures/service/socket/main.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/python3 - -import sys -import os -import socket -from socketserver import TCPServer, StreamRequestHandler - -class Handler(StreamRequestHandler): - def handle(self): - # self.request is the TCP socket connected to the client - self.data = self.request.recv(1024).strip() - print("Received from {}:".format(self.client_address[0])) - print(type(self.data)) - return_msg = "Received: {}".format(self.data) - self.request.sendall(bytes(return_msg.encode('utf-8'))) - -class Server(TCPServer): - SYSTEMD_FD = 3 - def __init__(self, address, handler_cls): - # bind and activate must be false because systemd will handle this - TCPServer.__init__(self, address, handler_cls, bind_and_activate=False) - self.socket = socket.fromfd(self.SYSTEMD_FD, self.address_family, self.socket_type) - -if __name__ == "__main__": - server = Server(("localhost", 8080), Handler) - server.serve_forever() From eb70e0016a0711e7da609b6561695bb97d9d6acd Mon Sep 17 00:00:00 2001 From: adamperlin Date: Thu, 23 May 2024 00:05:18 +0000 Subject: [PATCH 11/14] go mod tidy --- go.mod | 1 - 1 file changed, 1 deletion(-) diff --git a/go.mod b/go.mod index 7db6ed87c..d5796b73b 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,6 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.8.4 github.com/tonistiigi/fsutil v0.0.0-20240424095704-91a3fc46842c - github.com/stretchr/testify v1.8.4 go.opentelemetry.io/otel v1.21.0 golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81 golang.org/x/sys v0.18.0 From 67eabb6eeb8f52bc499175c4247b4b1fea69dd86 Mon Sep 17 00:00:00 2001 From: adamperlin Date: Thu, 23 May 2024 00:28:14 +0000 Subject: [PATCH 12/14] Remove unused systemd test fixtures --- test/fixtures/systemd-socket-unit.yml | 43 ---------------------- test/fixtures/systemd-unit.yml | 51 --------------------------- 2 files changed, 94 deletions(-) delete mode 100644 test/fixtures/systemd-socket-unit.yml delete mode 100644 test/fixtures/systemd-unit.yml diff --git a/test/fixtures/systemd-socket-unit.yml b/test/fixtures/systemd-socket-unit.yml deleted file mode 100644 index 096be0674..000000000 --- a/test/fixtures/systemd-socket-unit.yml +++ /dev/null @@ -1,43 +0,0 @@ -# syntax=local/dalec/frontend - -name: dalec-test-systemd-socket -description: A test fixture which tests the installation of a simple systemd unit -website: https://www.github.com/Azure/dalec -version: 0.0.1 -revision: 1 -vendor: Microsoft -license: Apache 2.0 -packager: Microsoft - -dependencies: - build: - msft-golang: - runtime: - nmap-ncat: - python3: - bash: - curl: - systemd: - -sources: - src: - context: {} - path: test/fixtures/service/socket - includes: - - test/fixtures/service/socket/ - -artifacts: - binaries: - src/main.py: - name: foo - - systemdUnits: - src/foo.service: {} - src/foo.socket: - enable: false - -image: - # entrypoint: /bin/service - entrypoint: /sbin/init - stop_signal: SIGRTMIN+3 - user: root \ No newline at end of file diff --git a/test/fixtures/systemd-unit.yml b/test/fixtures/systemd-unit.yml deleted file mode 100644 index 255801bfe..000000000 --- a/test/fixtures/systemd-unit.yml +++ /dev/null @@ -1,51 +0,0 @@ -# syntax=local/dalec/frontend - -name: dalec-test-systemd-unit -description: A test fixture which tests the installation of a simple systemd unit -website: https://www.github.com/Azure/dalec -version: 0.0.1 -revision: 1 -vendor: Microsoft -license: Apache 2.0 -packager: Microsoft - -dependencies: - build: - msft-golang: - runtime: - bash: - curl: - systemd: - -sources: - src: - context: {} - path: test/fixtures/service - includes: - - test/fixtures/service - -build: - steps: - - command: | - cd src/ - go build -o service main.go - -artifacts: - binaries: - src/service: {} - - services: - src/simple.service: - # is the service enabled or disabled by default on boot - # this determines what goes in the sytemctl preset file - # disable: true - # noRestart: false - -image: - # entrypoint: /bin/service - entrypoint: /sbin/init - stop_signal: SIGRTMIN+3 - user: root - - - From 827aea863b5513d83e2f05ab711a65b0c4359898 Mon Sep 17 00:00:00 2001 From: adamperlin Date: Thu, 23 May 2024 00:30:51 +0000 Subject: [PATCH 13/14] Remove stray newlines --- load.go | 1 - test/azlinux_test.go | 1 - 2 files changed, 2 deletions(-) diff --git a/load.go b/load.go index 155171163..630cdab07 100644 --- a/load.go +++ b/load.go @@ -362,7 +362,6 @@ func (s *Spec) FillDefaults() { s.Patches[k][i].Strip = &strip } } - } func (s Spec) Validate() error { diff --git a/test/azlinux_test.go b/test/azlinux_test.go index e43186232..568f5f10d 100644 --- a/test/azlinux_test.go +++ b/test/azlinux_test.go @@ -579,7 +579,6 @@ WantedBy=sockets.target req := newSolveRequest(withBuildTarget(buildTarget), withSpec(ctx, t, spec)) return client.Solve(ctx, req) }) - }) t.Run("test directory creation", func(t *testing.T) { From 7839dfe46ff8634b9007083ef7acfa641de8532e Mon Sep 17 00:00:00 2001 From: adamperlin Date: Thu, 23 May 2024 21:17:29 +0000 Subject: [PATCH 14/14] Remove unused constants Remove unneeded dependencies for systemd unit integration tests --- spec.go | 6 ------ test/azlinux_test.go | 10 ---------- 2 files changed, 16 deletions(-) diff --git a/spec.go b/spec.go index 10eee12e6..92c7e9335 100644 --- a/spec.go +++ b/spec.go @@ -141,12 +141,6 @@ type Artifacts struct { // TODO: other types of artifacts (systtemd units, libexec, etc) } -const ( - SystemdUnitUpgradePolicyNone = "none" - SystemdUnitUpgradePolicyReload = "reload" - SystemdUnitUpgradePolicyRestart = "restart" -) - type SystemdUnitConfig struct { // Name is the name systemd unit should be copied under. // Nested paths are not supported. It is the user's responsibility diff --git a/test/azlinux_test.go b/test/azlinux_test.go index 568f5f10d..d1e4b7e06 100644 --- a/test/azlinux_test.go +++ b/test/azlinux_test.go @@ -352,11 +352,6 @@ echo "$BAR" > bar.txt Vendor: "Microsoft", License: "Apache 2.0", Packager: "Microsoft ", - Dependencies: &dalec.PackageDependencies{ - Build: map[string][]string{ - "msft-golang": {}, - }, - }, Sources: map[string]dalec.Source{ "src": { Inline: &dalec.SourceInline{ @@ -448,11 +443,6 @@ WantedBy=multi-user.target Vendor: "Microsoft", License: "Apache 2.0", Packager: "Microsoft ", - Dependencies: &dalec.PackageDependencies{ - Build: map[string][]string{ - "msft-golang": {}, - }, - }, Sources: map[string]dalec.Source{ "src": { Inline: &dalec.SourceInline{