Repository: osbuild/osbuild-composer
Pull request #4021 (2024-03-18 20:08:19)

worker: drop backwards compatibility for DepsolveJob serialisation (COMPOSER-1612)
We've kept this around a lot longer than we should have.


Patch:
diff --git a/Makefile b/Makefile
index f04fbdc4f..feb82ab86 100644
--- a/Makefile
+++ b/Makefile
@@ -125,6 +125,8 @@ build: $(BUILDDIR)/bin/
 	go build -o $<osbuild-upload-generic-s3 ./cmd/osbuild-upload-generic-s3/
 	go build -o $<osbuild-mock-openid-provider ./cmd/osbuild-mock-openid-provider
 	go build -o $<osbuild-service-maintenance ./cmd/osbuild-service-maintenance
+	go build -o $<osbuild-jobsite-manager ./cmd/osbuild-jobsite-manager
+	go build -o $<osbuild-jobsite-builder ./cmd/osbuild-jobsite-builder
 	go test -c -tags=integration -o $<osbuild-composer-cli-tests ./cmd/osbuild-composer-cli-tests/main_test.go
 	go test -c -tags=integration -o $<osbuild-weldr-tests ./internal/client/
 	go test -c -tags=integration -o $<osbuild-dnf-json-tests ./cmd/osbuild-dnf-json-tests/main_test.go
diff --git a/cmd/osbuild-jobsite-builder/main.go b/cmd/osbuild-jobsite-builder/main.go
index 5843f339e..35327730d 100644
--- a/cmd/osbuild-jobsite-builder/main.go
+++ b/cmd/osbuild-jobsite-builder/main.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+	"archive/tar"
 	"bufio"
 	"bytes"
 	"context"
@@ -12,6 +13,8 @@ import (
 	"os"
 	"os/exec"
 	"path"
+	"path/filepath"
+	"strings"
 	"sync"
 	"time"
 
@@ -206,6 +209,46 @@ func (builder *Builder) HandlePopulate(w http.ResponseWriter, r *http.Request) e
 	if r.Method != "POST" {
 		return fmt.Errorf("Builder.HandlePopulate: unexpected request method")
 	}
+	storePath := path.Join(argBuildPath, "store")
+	err := os.Mkdir(storePath, 0755)
+	if err != nil {
+		return fmt.Errorf("Builder.HandlePopulate: failed to make store directory: %v", err)
+	}
+
+	tarReader := tar.NewReader(r.Body)
+	for header, err := tarReader.Next(); err != io.EOF; header, err = tarReader.Next() {
+		if err != nil {
+			return fmt.Errorf("Builder.HandlerPopulate: failed to unpack sources: %v", err)
+		}
+
+		// gosec seems overly zealous here, as the destination gets verified
+		dest := filepath.Join(storePath, header.Name) // #nosec G305
+		if !strings.HasPrefix(dest, filepath.Clean(storePath)) {
+			return fmt.Errorf("Builder.HandlerPopulate: name not clean: %v doesn't have %v prefix", dest, filepath.Clean(storePath))
+		}
+
+		switch header.Typeflag {
+		case tar.TypeDir:
+			if err := os.Mkdir(dest, header.FileInfo().Mode()); err != nil {
+				return fmt.Errorf("Builder.HandlerPopulate: unable to make dir in sources: %v", err)
+			}
+		case tar.TypeReg:
+			file, err := os.Create(dest)
+			if err != nil {
+				return fmt.Errorf("Builder.HandlerPopulate: unable to open file in sources: %v", err)
+			}
+			defer file.Close()
+
+			// the inputs are trusted so ignore G110
+			_, err = io.Copy(file, tarReader) // #nosec G110
+			if err != nil {
+				return fmt.Errorf("Builder.HandlerPopulate: unable to write file in sources: %v", err)
+			}
+			file.Close()
+		default:
+			return fmt.Errorf("Builder.HandlerPopulate: unexpected tar header type: %v", header.Typeflag)
+		}
+	}
 
 	w.WriteHeader(http.StatusOK)
 
diff --git a/cmd/osbuild-jobsite-manager/main.go b/cmd/osbuild-jobsite-manager/main.go
index 7461ff84d..b1fb55507 100644
--- a/cmd/osbuild-jobsite-manager/main.go
+++ b/cmd/osbuild-jobsite-manager/main.go
@@ -2,6 +2,7 @@
 package main
 
 import (
+	"archive/tar"
 	"bytes"
 	"encoding/json"
 	"errors"
@@ -14,6 +15,7 @@ import (
 	"os/signal"
 	"path"
 	"path/filepath"
+	"strings"
 	"syscall"
 	"time"
 
@@ -57,6 +59,7 @@ var (
 	argEnvironments ArgumentList
 	argExports      ArgumentList
 	argOutputPath   string
+	argStore        string
 )
 
 type BuildRequest struct {
@@ -87,6 +90,7 @@ func init() {
 
 	flag.Var(&argEnvironments, "environment", "Environments to add. Can be passed multiple times.")
 	flag.StringVar(&argOutputPath, "output", "/dev/null", "Output directory to write to.")
+	flag.StringVar(&argStore, "store", "", "Store to send to builder.")
 
 	flag.Parse()
 
@@ -170,11 +174,11 @@ func Dance(done chan<- struct{}, errs chan<- error) {
 	close(done)
 }
 
-func Request(method string, path string, body []byte) (*http.Response, error) {
+func Request(method string, path string, body io.Reader) (*http.Response, error) {
 	cli := &http.Client{}
 	url := fmt.Sprintf("http://%s:%d/%s", argBuilderHost, argBuilderPort, path)
 
-	req, err := http.NewRequest(method, url, bytes.NewBuffer(body))
+	req, err := http.NewRequest(method, url, body)
 
 	if err != nil {
 		return nil, err
@@ -220,7 +224,7 @@ func Wait(timeout int, fn Step) error {
 
 func StepClaim() error {
 	return Wait(argTimeoutClaim, func(done chan<- struct{}, errs chan<- error) {
-		res, err := Request("POST", "claim", []byte(""))
+		res, err := Request("POST", "claim", nil)
 
 		if err != nil {
 			errs <- err
@@ -242,7 +246,7 @@ func StepClaim() error {
 
 func StepProvision(manifest []byte) error {
 	return Wait(argTimeoutProvision, func(done chan<- struct{}, errs chan<- error) {
-		res, err := Request("PUT", "provision", manifest)
+		res, err := Request("PUT", "provision", bytes.NewBuffer(manifest))
 
 		if err != nil {
 			errs <- err
@@ -264,13 +268,68 @@ func StepProvision(manifest []byte) error {
 
 func StepPopulate() error {
 	return Wait(argTimeoutPopulate, func(done chan<- struct{}, errs chan<- error) {
-		res, err := Request("POST", "populate", []byte(""))
+		file, err := os.CreateTemp(filepath.Dir(argStore), "store.*.tar")
+		if err != nil {
+			errs <- err
+			return
+		}
+		tw := tar.NewWriter(file)
+		defer tw.Close()
+		err = filepath.Walk(argStore, func(filePath string, fi os.FileInfo, err error) error {
+			if err != nil {
+				return err
+			}
+
+			// skip the store direction itself, just package the contents
+			if filePath == argStore {
+				return nil
+			}
+
+			if !fi.Mode().IsRegular() && !fi.Mode().IsDir() {
+				return nil
+			}
+			header, err := tar.FileInfoHeader(fi, fi.Name())
+			if err != nil {
+				return err
+			}
+
+			// FileInfo only contains the basename, see https://pkg.go.dev/archive/tar#FileInfoHeader.
+			header.Name = strings.TrimPrefix(strings.Replace(filePath, argStore, "", -1), string(filepath.Separator))
+			if err := tw.WriteHeader(header); err != nil {
+				return err
+			}
 
+			if fi.Mode().IsRegular() {
+				f, err := os.Open(filePath)
+				if err != nil {
+					return err
+				}
+				defer f.Close()
+				if err != nil {
+					return err
+				}
+				if _, err := io.Copy(tw, f); err != nil {
+					return err
+				}
+				f.Close()
+			}
+			return nil
+		})
+		if err != nil {
+			errs <- err
+			return
+		}
+		_, err = file.Seek(0, io.SeekStart)
 		if err != nil {
 			errs <- err
 			return
 		}
 
+		res, err := Request("POST", "populate", file)
+		if err != nil {
+			errs <- err
+			return
+		}
 		defer res.Body.Close()
 
 		if res.StatusCode != http.StatusOK {
@@ -297,7 +356,7 @@ func StepBuild() error {
 			logrus.Fatalf("StepBuild: Failed to marshal data: %s", err)
 		}
 
-		res, err := Request("POST", "build", dat)
+		res, err := Request("POST", "build", bytes.NewBuffer(dat))
 
 		if err != nil {
 			errs <- err
@@ -320,7 +379,7 @@ func StepBuild() error {
 func StepProgress() error {
 	return Wait(argTimeoutProgress, func(done chan<- struct{}, errs chan<- error) {
 		for {
-			res, err := Request("GET", "progress", []byte(""))
+			res, err := Request("GET", "progress", nil)
 
 			if err != nil {
 				errs <- err
@@ -357,7 +416,7 @@ func StepProgress() error {
 func StepExport() error {
 	return Wait(argTimeoutExport, func(done chan<- struct{}, errs chan<- error) {
 		for _, export := range argExports {
-			res, err := Request("GET", fmt.Sprintf("export?path=%s", url.PathEscape(export)), []byte(""))
+			res, err := Request("GET", fmt.Sprintf("export?path=%s", url.PathEscape(export)), nil)
 
 			if err != nil {
 				errs <- err
diff --git a/internal/blueprint/blueprint.go b/internal/blueprint/blueprint.go
index 600247f11..6726dd1e4 100644
--- a/internal/blueprint/blueprint.go
+++ b/internal/blueprint/blueprint.go
@@ -351,6 +351,10 @@ func Convert(bp Blueprint) iblueprint.Blueprint {
 		if fips := c.FIPS; fips != nil {
 			customizations.FIPS = fips
 		}
+		if installer := c.Installer; installer != nil {
+			iinst := iblueprint.InstallerCustomization(*installer)
+			customizations.Installer = &iinst
+		}
 	}
 
 	ibp := iblueprint.Blueprint{
diff --git a/internal/blueprint/customizations.go b/internal/blueprint/customizations.go
index ccee534aa..6a565fc62 100644
--- a/internal/blueprint/customizations.go
+++ b/internal/blueprint/customizations.go
@@ -28,6 +28,7 @@ type Customizations struct {
 	Files              []FileCustomization       `json:"files,omitempty" toml:"files,omitempty"`
 	Repositories       []RepositoryCustomization `json:"repositories,omitempty" toml:"repositories,omitempty"`
 	FIPS               *bool                     `json:"fips,omitempty" toml:"fips,omitempty"`
+	Installer          *InstallerCustomization   `json:"installer,omitempty" toml:"installer,omitempty"`
 }
 
 type IgnitionCustomization struct {
diff --git a/internal/blueprint/installer_customizatios.go b/internal/blueprint/installer_customizatios.go
new file mode 100644
index 000000000..9932b7590
--- /dev/null
+++ b/internal/blueprint/installer_customizatios.go
@@ -0,0 +1,6 @@
+package blueprint
+
+type InstallerCustomization struct {
+	Unattended   bool     `json:"unattended,omitempty" toml:"unattended,omitempty"`
+	SudoNopasswd []string `json:"sudo-nopasswd,omitempty" toml:"sudo-nopasswd,omitempty"`
+}
diff --git a/internal/cloud/awscloud/secure-instance.go b/internal/cloud/awscloud/secure-instance.go
index c7365f101..2adfdbd79 100644
--- a/internal/cloud/awscloud/secure-instance.go
+++ b/internal/cloud/awscloud/secure-instance.go
@@ -277,9 +277,76 @@ func (a *AWS) createOrReplaceSG(hostInstanceID, hostIP, vpcID string) (string, e
 	if len(describeSGOutput.SecurityGroups) != 1 {
 		return sgID, fmt.Errorf("Expected 1 security group, got %d", len(describeSGOutput.SecurityGroups))
 	}
+
+	if len(describeSGOutput.SecurityGroups[0].IpPermissionsEgress) != 1 {
+		return sgID, fmt.Errorf("Expected exactly 1 egress rule on the security group (got %d)", len(describeSGOutput.SecurityGroups[0].IpPermissionsEgress))
+	}
+
+	describeSGROutput, err := a.ec2.DescribeSecurityGroupRules(&ec2.DescribeSecurityGroupRulesInput{
+		Filters: []*ec2.Filter{
+			&ec2.Filter{
+				Name: aws.String("group-id"),
+				Values: []*string{
+					aws.String(sgID),
+				},
+			},
+		},
+	})
+	if err != nil {
+		return sgID, err
+	}
+
+	for _, rule := range describeSGROutput.SecurityGroupRules {
+		if *rule.IsEgress {
+			revokeOutput, err := a.ec2.RevokeSecurityGroupEgress(&ec2.RevokeSecurityGroupEgressInput{
+				GroupId: aws.String(sgID),
+				SecurityGroupRuleIds: []*string{
+					rule.SecurityGroupRuleId,
+				},
+			})
+			if err != nil {
+				return sgID, err
+			}
+			if !*revokeOutput.Return {
+				return sgID, fmt.Errorf("Failed to revoke security group %s's egress rule %s", sgID, *rule.SecurityGroupRuleId)
+			}
+		}
+	}
+
+	sgEgressOutput, err := a.ec2.AuthorizeSecurityGroupEgress(&ec2.AuthorizeSecurityGroupEgressInput{
+		GroupId: aws.String(sgID),
+		IpPermissions: []*ec2.IpPermission{
+			&ec2.IpPermission{
+				IpProtocol: aws.String(ec2.ProtocolTcp),
+				FromPort:   aws.Int64(1),
+				ToPort:     aws.Int64(65535),
+				IpRanges: []*ec2.IpRange{
+					&ec2.IpRange{
+						CidrIp: aws.String(fmt.Sprintf("%s/32", hostIP)),
+					},
+				},
+			},
+		},
+	})
+	if err != nil {
+		return sgID, err
+	}
+	if !*sgEgressOutput.Return {
+		return sgID, fmt.Errorf("Unable to attach egress rules to SG")
+	}
+
+	describeSGOutput, err = a.ec2.DescribeSecurityGroups(&ec2.DescribeSecurityGroupsInput{
+		GroupIds: []*string{
+			aws.String(sgID),
+		},
+	})
+	if err != nil {
+		return sgID, err
+	}
+
 	// SGs are created with a predefind egress rule that allows all outgoing traffic, so expecting 1 outbound rule
 	if len(describeSGOutput.SecurityGroups[0].IpPermissions) != 1 || len(describeSGOutput.SecurityGroups[0].IpPermissionsEgress) != 1 {
-		return sgID, fmt.Errorf("Expected 3 security group rules: 1 inbound (got %d) and 1 outbound (got %d)",
+		return sgID, fmt.Errorf("Expected 2 security group rules: 1 inbound (got %d) and 1 outbound (got %d)",
 			len(describeSGOutput.SecurityGroups[0].IpPermissions), len(describeSGOutput.SecurityGroups[0].IpPermissionsEgress))
 	}
 
diff --git a/internal/cloudapi/v2/compose.go b/internal/cloudapi/v2/compose.go
index 25f8aff6d..f170fac8c 100644
--- a/internal/cloudapi/v2/compose.go
+++ b/internal/cloudapi/v2/compose.go
@@ -413,6 +413,16 @@ func (request *ComposeRequest) GetCustomizationsFromBlueprintRequest() (*bluepri
 		c.FIPS = rbpc.Fips
 	}
 
+	if installer := rbpc.Installer; installer != nil {
+		c.Installer = &blueprint.InstallerCustomization{}
+		if installer.Unattended != nil {
+			c.Installer.Unattended = *installer.Unattended
+		}
+		if installer.SudoNopasswd != nil {
+			c.Installer.SudoNopasswd = *installer.SudoNopasswd
+		}
+	}
+
 	return c, nil
 }
 
@@ -872,6 +882,17 @@ func (request *ComposeRequest) GetBlueprintFromCustomizations() (blueprint.Bluep
 		bp.Customizations.FIPS = request.Customizations.Fips.Enabled
 	}
 
+	if request.Customizations.Installer != nil {
+		installer := &blueprint.InstallerCustomization{}
+		if request.Customizations.Installer.Unattended != nil {
+			installer.Unattended = *request.Customizations.Installer.Unattended
+		}
+		if request.Customizations.Installer.SudoNopasswd != nil {
+			installer.SudoNopasswd = *request.Customizations.Installer.SudoNopasswd
+		}
+		bp.Customizations.Installer = installer
+	}
+
 	// Did bp.Customizations get set at all? If not, set it back to nil
 	if reflect.DeepEqual(*bp.Customizations, blueprint.Customizations{}) {
 		bp.Customizations = nil
diff --git a/internal/cloudapi/v2/openapi.v2.gen.go b/internal/cloudapi/v2/openapi.v2.gen.go
index cfba08d29..adb68c3a6 100644
--- a/internal/cloudapi/v2/openapi.v2.gen.go
+++ b/internal/cloudapi/v2/openapi.v2.gen.go
@@ -270,8 +270,9 @@ type BlueprintCustomizations struct {
 	Ignition *Ignition `json:"ignition,omitempty"`
 
 	// Name of the installation device, currently only useful for the edge-simplified-installer type
-	InstallationDevice *string `json:"installation_device,omitempty"`
-	Kernel             *Kernel `json:"kernel,omitempty"`
+	InstallationDevice *string    `json:"installation_device,omitempty"`
+	Installer          *Installer `json:"installer,omitempty"`
+	Kernel             *Kernel    `json:"kernel,omitempty"`
 
 	// Locale configuration
 	Locale   *Locale            `json:"locale,omitempty"`
@@ -560,8 +561,9 @@ type Customizations struct {
 	Ignition *Ignition `json:"ignition,omitempty"`
 
 	// Name of the installation device, currently only useful for the edge-simplified-installer type
-	InstallationDevice *string `json:"installation_device,omitempty"`
-	Kernel             *Kernel `json:"kernel,omitempty"`
+	InstallationDevice *string    `json:"installation_device,omitempty"`
+	Installer          *Installer `json:"installer,omitempty"`
+	Kernel             *Kernel    `json:"kernel,omitempty"`
 
 	// Locale configuration
 	Locale   *Locale   `json:"locale,omitempty"`
@@ -807,6 +809,12 @@ type ImageStatusValue string
 // ImageTypes defines model for ImageTypes.
 type ImageTypes string
 
+// Installer defines model for Installer.
+type Installer struct {
+	SudoNopasswd *[]string `json:"sudo-nopasswd,omitempty"`
+	Unattended   *bool     `json:"unattended,omitempty"`
+}
+
 // Kernel defines model for Kernel.
 type Kernel struct {
 	// Appends arguments to the bootloader kernel command line
@@ -1366,151 +1374,151 @@ var swaggerSpec = []string{
 	"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",
-	"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",
-	"X0v71bOPbmfYCV+PRXD/sov65PzWObjb3XmBvUZwf9Dwjy5Oa8EIEXRbsnr+1683o8vpDXc/V+nN58nh",
-	"2113UGlfXrSH7WNn9Ll5U+2Tt6cR61htdlS+qU7Y2cCDoe3efcT3kLQOuF9pPh5+5YNG6662a4s7dlG7",
-	"ebQfnL3bj5/x9fC+edsnZ/svvXJtfL9/ZV90+WNt7xy2yU4nqFyNg2bnkJY66PD+sfLVb19dt+BZeXB6",
-	"UguHTr0dohH/2Ov2yeTmoYfa56/h0/nO1cVnenV9Nhlf3AxfB07l80FzHD6Vz8RLybo8qb7CsPzq81a4",
-	"d3IaoNH46vr21euT6VfxMn0aMnqP0dE0mDw545uJIOSiWXK6h2Hp9L7HHsuNqn9419ttW4Pd+sg6Oeod",
-	"DS9GHhkdl/qkPLyrt25ho1w/qb2+lEdigGrjM+v6M72+Cs/27/lJd1wu3x0/tqbXKJx+bO5ad6XHQ/di",
-	"d1Tr3p+99MkO6jw5U3xxVZ54lcfjg9szK/QmI77X+hh6I6dCe4M6r735T+Pr8u4x7b0+1Ksv8Kzx0P14",
-	"6T4h1CfNnfJneu8OrMpZ0P34MnyiL5wdiqfm9eDu6ePj+Kh5GzD7ocVeTgano+ppcHvWeu25r/ymxffd",
-	"40qflM/D1+oDvNgvO9VO49q6sE9L1tcXWm5aFnvZ/xzi1weGGzjcu/gcNL/2SsPu26XP7Y5DmqWvT2d9",
-	"gps3oTcMd3fDr+5DaSKqA0GwcG751xf39SJ8ebyrPw3q7kgcNd2zu9Lnz7v16lf3vHE2ad22blr7fSIO",
-	"jo6fHm7Hln/onB1cVM66reaTfz8a1E7d895F5fzz/hQ+VFyLeK3ouXVyOob+/Yvdboz7xPKtj/jm9Gp/",
-	"/2K/3WrVj/DhITrZ8Zl7dLIb3vOb84uLavmxYT255PWxedTyFQ21jyfNo/Zk1OmT/Unn+OiGnrZbvL2/",
-	"/9huTQ7bJ85h+6jearWd0c2s98fLx1Zpd/8xcLxpt/X0eOK+TM/cPil9HO68XQ/vx4OTavnwa23U2b06",
-	"2r8sk/PPH/fvKn447n782gu7tYdztl/za8ehJ4Kz28PTs3PhNw4P+qTCjt8+t2ivMg32HjvN89aBfdFu",
-	"X01fWi+cPtw1dx/vwvbH0oC8sB66rZ7fXrWH0+v27s7DXrOBr+77xG90Pw74zcFkt109Z57duqhfHIR0",
-	"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",
-	"QjzNunG+fPv/AQAA//+t7x7Xl7cAAA==",
+	"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",
 }
 
 // GetSwagger returns the content of the embedded swagger specification file
diff --git a/internal/cloudapi/v2/openapi.v2.yml b/internal/cloudapi/v2/openapi.v2.yml
index d83b89bbd..7dbced108 100644
--- a/internal/cloudapi/v2/openapi.v2.yml
+++ b/internal/cloudapi/v2/openapi.v2.yml
@@ -1000,6 +1000,15 @@ components:
           type: array
           items:
             type: string
+    Installer:
+      type: object
+      properties:
+        unattended:
+          type: boolean
+        sudo-nopasswd:
+          type: array
+          items:
+            type: string
     UploadTarget:
       type: object
       required:
@@ -1328,6 +1337,8 @@ components:
         fips:
           type: boolean
           description: Enable FIPS mode
+        installer:
+          $ref: '#/components/schemas/Installer'
     SSHKey:
       type: object
       additionalProperties: false
@@ -1470,6 +1481,8 @@ components:
             even when there are one or more mountpoints.
         fips:
           $ref: '#/components/schemas/FIPS'
+        installer:
+          $ref: '#/components/schemas/Installer'
     Container:
       type: object
       required:
diff --git a/internal/osbuildexecutor/runner-impl-aws-ec2.go b/internal/osbuildexecutor/runner-impl-aws-ec2.go
index 026a64c00..45ebc4264 100644
--- a/internal/osbuildexecutor/runner-impl-aws-ec2.go
+++ b/internal/osbuildexecutor/runner-impl-aws-ec2.go
@@ -18,8 +18,20 @@ type awsEC2Executor struct {
 	cloudWatchGroup string
 }
 
+func prepareSources(manifest []byte, store string, result bool, errorWriter io.Writer) error {
+	hostExecutor := NewHostExecutor()
+	_, err := hostExecutor.RunOSBuild(manifest, store, "", nil, nil, nil, nil, result, errorWriter)
+	return err
+}
+
 func (ec2e *awsEC2Executor) RunOSBuild(manifest []byte, store, outputDirectory string, exports, exportPaths, checkpoints,
 	extraEnv []string, result bool, errorWriter io.Writer) (*osbuild.Result, error) {
+
+	err := prepareSources(manifest, store, result, errorWriter)
+	if err != nil {
+		return nil, err
+	}
+
 	region, err := awscloud.RegionFromInstanceMetadata()
 	if err != nil {
 		return nil, err
@@ -45,6 +57,8 @@ func (ec2e *awsEC2Executor) RunOSBuild(manifest []byte, store, outputDirectory s
 	args := []string{
 		"--builder-host",
 		*si.Instance.PrivateIpAddress,
+		"--store",
+		store,
 	}
 
 	for _, exp := range exports {
diff --git a/internal/worker/json.go b/internal/worker/json.go
index 67642fa70..fa50ef546 100644
--- a/internal/worker/json.go
+++ b/internal/worker/json.go
@@ -183,75 +183,6 @@ type DepsolveJob struct {
 	Releasever       string                        `json:"releasever"`
 }
 
-// Custom marshaller for keeping compatibility with older workers.  The
-// serialised format encompasses both the old and new DepsolveJob formats.  It
-// is meant to be temporarily used to transition from the old to the new
-// format.
-func (ds DepsolveJob) MarshalJSON() ([]byte, error) {
-	// NOTE: Common, top level repositories aren't used because in the new
-	// format they don't exist; putting all required repositories on all
-	// package sets as PackageSetsRepos should produce the same behaviour so
-	// there's no need to try and figure out the "common" ones.
-	// This also makes it possible to use old workers for new image types that
-	// are incompatible with having common repos for all package sets (RHEL 7.9).
-	compatJob := struct {
-		// new format
-		GroupedPackageSets map[string][]rpmmd.PackageSet `json:"grouped_package_sets"`
-		ModulePlatformID   string                        `json:"module_platform_id"`
-		Arch               string                        `json:"arch"`
-		Releasever         string                        `json:"releasever"`
-
-		// old format elements
-		PackageSetsChains map[string][]string           `json:"package_sets_chains"`
-		PackageSets       map[string]rpmmd.PackageSet   `json:"package_sets"`
-		PackageSetsRepos  map[string][]rpmmd.RepoConfig `json:"package_sets_repositories,omitempty"`
-	}{
-		// new format substruct
-		GroupedPackageSets: ds.PackageSets,
-		ModulePlatformID:   ds.ModulePlatformID,
-		Arch:               ds.Arch,
-		Releasever:         ds.Releasever,
-	}
-
-	// build equivalent old format substruct
-	pkgSetRepos := make(map[string][]rpmmd.RepoConfig)
-	pkgSets := make(map[string]rpmmd.PackageSet)
-	chains := make(map[string][]string)
-	for chainName, pkgSetChain := range ds.PackageSets {
-		if len(pkgSetChain) == 1 {
-			// single element "chain" (i.e., not a chain)
-			pkgSets[chainName] = rpmmd.PackageSet{
-				Include: pkgSetChain[0].Include,
-				Exclude: pkgSetChain[0].Exclude,
-			}
-			pkgSetRepos[chainName] = pkgSetChain[0].Repositories
-			continue
-		}
-		chain := make([]string, len(pkgSetChain))
-		for idx, set := range pkgSetChain {
-			// the names of the individual sets in the chain don't matter, as long
-			// as they match the keys for the repo configs
-			setName := fmt.Sprintf("%s-%d", chainName, idx)
-			// the package set (without repos)
-			pkgSets[setName] = rpmmd.PackageSet{
-				Include: set.Include,
-				Exclude: set.Exclude,
-			}
-			// set repositories
-			pkgSetRepos[setName] = set.Repositories
-			// add name to the chain
-			chain[idx] = setName
-		}
-		chains[chainName] = chain
-	}
-
-	compatJob.PackageSets = pkgSets
-	compatJob.PackageSetsChains = chains
-	compatJob.PackageSetsRepos = pkgSetRepos
-
-	return json.Marshal(compatJob)
-}
-
 type ErrorType string
 
 const (
diff --git a/osbuild-composer.spec b/osbuild-composer.spec
index 379898619..c01a3e29f 100644
--- a/osbuild-composer.spec
+++ b/osbuild-composer.spec
@@ -12,7 +12,7 @@
 
 %global goipath         github.com/osbuild/osbuild-composer
 
-Version:        103
+Version:        104
 
 %gometa
 
diff --git a/templates/packer/ansible/roles/common/files/worker-initialization-scripts/worker_executor.sh b/templates/packer/ansible/roles/common/files/worker-initialization-scripts/worker_executor.sh
index c31e9b18c..2d1caaf5d 100755
--- a/templates/packer/ansible/roles/common/files/worker-initialization-scripts/worker_executor.sh
+++ b/templates/packer/ansible/roles/common/files/worker-initialization-scripts/worker_executor.sh
@@ -5,14 +5,6 @@ source /etc/os-release
 # /tmp/cloud_init_vars may not exist on the osbuild-executor
 source /tmp/cloud_init_vars || true
 
-# Don't subscribe on fedora
-if [ "$ID" != fedora ]; then
-  /usr/local/bin/aws secretsmanager get-secret-value \
-    --secret-id executor-subscription-manager-command | jq -r ".SecretString" > /tmp/subscription_manager_command.json
-  jq -r ".subscription_manager_command" /tmp/subscription_manager_command.json | bash
-  rm -f /tmp/subscription_manager_command.json
-fi
-
 echo "Writing vector config."
 REGION=$(curl -Ls http://169.254.169.254/latest/dynamic/instance-identity/document | jq -r .region)
 HOSTNAME=$(hostname)


Test patch:
diff --git a/internal/blueprint/blueprint_convert_test.go b/internal/blueprint/blueprint_convert_test.go
index 4422a21f1..20ee9c169 100644
--- a/internal/blueprint/blueprint_convert_test.go
+++ b/internal/blueprint/blueprint_convert_test.go
@@ -170,6 +170,10 @@ func TestConvert(t *testing.T) {
 							Filename:     "repofile",
 						},
 					},
+					Installer: &InstallerCustomization{
+						Unattended:   true,
+						SudoNopasswd: []string{"%group", "user"},
+					},
 				},
 				Distro: "distro",
 			},
@@ -321,6 +325,10 @@ func TestConvert(t *testing.T) {
 							Filename:     "repofile",
 						},
 					},
+					Installer: &iblueprint.InstallerCustomization{
+						Unattended:   true,
+						SudoNopasswd: []string{"%group", "user"},
+					},
 				},
 				Distro: "distro",
 			},
diff --git a/internal/cloudapi/v2/compose_test.go b/internal/cloudapi/v2/compose_test.go
index ae7e4b1ee..9f5a81470 100644
--- a/internal/cloudapi/v2/compose_test.go
+++ b/internal/cloudapi/v2/compose_test.go
@@ -130,6 +130,10 @@ func GetTestBlueprint() blueprint.Blueprint {
 			},
 		},
 		FIPS: common.ToPtr(true),
+		Installer: &blueprint.InstallerCustomization{
+			Unattended:   true,
+			SudoNopasswd: []string{`%wheel`},
+		},
 	}
 
 	return expected
@@ -249,6 +253,10 @@ func TestGetBlueprintFromCustomizations(t *testing.T) {
 			}),
 		},
 		Fips: &FIPS{Enabled: common.ToPtr(true)},
+		Installer: &Installer{
+			Unattended:   common.ToPtr(true),
+			SudoNopasswd: &[]string{`%wheel`},
+		},
 	}}
 
 	bp, err = cr.GetBlueprintFromCustomizations()
@@ -372,6 +380,10 @@ func TestGetBlueprintFromCompose(t *testing.T) {
 				}),
 			},
 			Fips: common.ToPtr(true),
+			Installer: &Installer{
+				Unattended:   common.ToPtr(true),
+				SudoNopasswd: &[]string{`%wheel`},
+			},
 		},
 	}}
 
diff --git a/internal/worker/server_test.go b/internal/worker/server_test.go
index 1ec1f92ee..c683e9e8f 100644
--- a/internal/worker/server_test.go
+++ b/internal/worker/server_test.go
@@ -21,7 +21,6 @@ import (
 	"github.com/osbuild/images/pkg/distro/test_distro"
 	"github.com/osbuild/images/pkg/manifest"
 	"github.com/osbuild/images/pkg/osbuild"
-	"github.com/osbuild/images/pkg/rpmmd"
 	"github.com/osbuild/osbuild-composer/internal/jobqueue/fsjobqueue"
 	"github.com/osbuild/osbuild-composer/internal/target"
 	"github.com/osbuild/osbuild-composer/internal/test"
@@ -651,124 +650,6 @@ func TestDepsolveLegacyErrorConversion(t *testing.T) {
 	require.Equal(t, expectedResult, depsolveJobResult)
 }
 
-// old depsolve job format kept here to test compatibility with older workers
-type oldDepsolveJob struct {
-	PackageSetsChains map[string][]string           `json:"package_sets_chains"`
-	PackageSets       map[string]rpmmd.PackageSet   `json:"package_sets"`
-	Repos             []rpmmd.RepoConfig            `json:"repos"`
-	ModulePlatformID  string                        `json:"module_platform_id"`
-	Arch              string                        `json:"arch"`
-	Releasever        string                        `json:"releasever"`
-	PackageSetsRepos  map[string][]rpmmd.RepoConfig `json:"package_sets_repositories,omitempty"`
-}
-
-func TestDepsolveJobArgsCompat(t *testing.T) {
-	// Test depsolve job argument transition compatibility
-	//
-	// NOTE: This test should be removed once all cloud workers are updated to
-	// use the new structure.
-	assert := assert.New(t)
-
-	// common elements
-	baseos := rpmmd.RepoConfig{
-		Name:     "baseos",
-		BaseURLs: []string{"https://example.com/baseos"},
-	}
-	appstream := rpmmd.RepoConfig{
-		Name:     "appstream",
-		BaseURLs: []string{"https://example.com/appstream"},
-	}
-	user1 := rpmmd.RepoConfig{
-		Name:     "user1",
-		BaseURLs: []string{"https://example.com/user/1"},
-	}
-	user2 := rpmmd.RepoConfig{
-		Name:     "user2",
-		BaseURLs: []string{"https://example.com/user/2"},
-	}
-
-	osIncludes := []string{"os1", "os2", "os3"}
-	bpIncludes := []string{"bp1", "bp2"}
-	buildIncludes := []string{"build1", "build2", "build3"}
-
-	excludes := []string{"nope1", "nope2"}
-
-	newJob := worker.DepsolveJob{
-		PackageSets: map[string][]rpmmd.PackageSet{
-			"os": {
-				{
-					Include:      osIncludes,
-					Exclude:      excludes,
-					Repositories: []rpmmd.RepoConfig{baseos, appstream},
-				},
-				{
-					Include:      bpIncludes,
-					Repositories: []rpmmd.RepoConfig{baseos, appstream, user1, user2},
-				},
-			},
-			"build": {{
-				Include:      buildIncludes,
-				Exclude:      excludes,
-				Repositories: []rpmmd.RepoConfig{baseos, appstream},
-			}},
-		},
-		ModulePlatformID: "el9",
-		Arch:             "x86_64",
-		Releasever:       "9",
-	}
-
-	oldJob := oldDepsolveJob{
-		PackageSetsChains: map[string][]string{
-			"os": {"os-0", "os-1"},
-		},
-		PackageSets: map[string]rpmmd.PackageSet{
-			"os-0": {
-				Include: osIncludes,
-				Exclude: excludes,
-			},
-			"os-1": {
-				Include: bpIncludes,
-			},
-			"build": {
-				Include: buildIncludes,
-				Exclude: excludes,
-			},
-		},
-		ModulePlatformID: "el9",
-		Arch:             "x86_64",
-		Releasever:       "9",
-		PackageSetsRepos: map[string][]rpmmd.RepoConfig{
-			"os-0":  {baseos, appstream},
-			"os-1":  {baseos, appstream, user1, user2},
-			"build": {baseos, appstream},
-		},
-	}
-
-	{ // old in, old out (not really useful, but let's cover all bases)
-		oldArgs, err := json.Marshal(oldJob)
-		assert.NoError(err)
-		var oldJobW oldDepsolveJob
-		assert.NoError(json.Unmarshal(oldArgs, &oldJobW))
-		assert.Equal(oldJob, oldJobW)
-	}
-
-	{ // new in, old out (the important scenario)
-		newArgs, err := json.Marshal(newJob)
-		assert.NoError(err)
-		var oldJobW oldDepsolveJob
-		assert.NoError(json.Unmarshal(newArgs, &oldJobW))
-		assert.Equal(oldJob, oldJobW)
-	}
-
-	{ // new in, new out (check if the serialised format also unmarshals back into the new format)
-		newArgs, err := json.Marshal(newJob)
-		assert.NoError(err)
-		var newJobW worker.DepsolveJob
-		assert.NoError(json.Unmarshal(newArgs, &newJobW))
-		assert.Equal(newJob, newJobW)
-	}
-}
-
 type testJob struct {
 	main   interface{}
 	deps   []testJob
diff --git a/test/cases/installers.sh b/test/cases/installers.sh
index adac0255a..a942a3bf1 100755
--- a/test/cases/installers.sh
+++ b/test/cases/installers.sh
@@ -310,6 +310,10 @@ password = "\$6\$GRmb7S0p8vsYmXzH\$o0E020S.9JQGaHkszoog4ha4AQVs3sk8q0DvLjSMxoxHB
 key = "${SSH_KEY_PUB}"
 home = "/home/${SSH_USER}/"
 groups = ["wheel", "testers"]
+
+[customizations.installer]
+unattended = true
+sudo-nopasswd = ["admin"]
 EOF
 
 greenprint "📄 installer blueprint"
@@ -328,8 +332,16 @@ greenprint "📥 Downloading the installer image"
 sudo composer-cli compose image "${COMPOSE_ID}" > /dev/null
 ISO_FILENAME="${COMPOSE_ID}-installer.iso"
 greenprint "🖥 Modify kickstart file and create new ISO"
-modksiso "${ISO_FILENAME}" "/var/lib/libvirt/images/${ISO_FILENAME}"
-sudo rm "${ISO_FILENAME}"
+
+# in nightly pipelines the feature wont be available for a while, so the
+# customizations will have no effect and we need to modify the kickstart file
+# on the ISO
+if [[ "${NIGHTLY:=false}" == "true" ]] && ! nvrGreaterOrEqual "osbuild-composer" "103"; then
+    modksiso "${ISO_FILENAME}" "/var/lib/libvirt/images/${ISO_FILENAME}"
+    sudo rm "${ISO_FILENAME}"
+else
+    sudo mv "${ISO_FILENAME}" "/var/lib/libvirt/images/${ISO_FILENAME}"
+fi
 
 # Clean compose and blueprints.
 greenprint "🧹 Clean up installer blueprint and compose"
diff --git a/test/cases/ostree-ng.sh b/test/cases/ostree-ng.sh
index 9fa579a0c..d4b42d6a6 100755
--- a/test/cases/ostree-ng.sh
+++ b/test/cases/ostree-ng.sh
@@ -177,26 +177,22 @@ function modksiso {
 
     trap cleanup RETURN
 
-    ksfiles=("${isomount}"/*.ks)
-    ksfile="${ksfiles[0]}"  # there shouldn't be more than one anyway
-    echo "Found kickstart file ${ksfile}"
+    # When sudo-nopasswd is specified, a second kickstart file is added which
+    # includes the %post section for creating sudoers drop-in files. This
+    # kickstart file is called osbuild.ks and it %includes osbuild-base.ks at
+    # the top, which is the main kickstart file created by osbuild. To inject
+    # our extra %post section, lets modify the osbuild.ks file and start that
+    # one so that the %include chain isn't disrupted.
+    ksfile="${isomount}/osbuild.ks"
+    echo "Modifying kickstart file ${ksfile}"
 
     ksbase=$(basename "${ksfile}")
     newksfile="${kspath}/${ksbase}"
     oldks=$(cat "${ksfile}")
     echo "Preparing modified kickstart file"
     cat > "${newksfile}" << EOFKS
-text
-network --bootproto=dhcp --device=link --activate --onboot=on
-zerombr
-clearpart --all --initlabel --disklabel=msdos
-autopart --nohome --noswap --type=plain
 ${oldks}
-poweroff
 %post --log=/var/log/anaconda/post-install.log --erroronfail
-# no sudo password for user admin and installeruser
-echo -e 'admin\tALL=(ALL)\tNOPASSWD: ALL' >> /etc/sudoers
-echo -e 'installeruser\tALL=(ALL)\tNOPASSWD: ALL' >> /etc/sudoers
 # delete local ostree repo and add external prod edge repo
 ostree remote delete ${OSTREE_OSNAME}
 ostree remote add --no-gpg-verify --no-sign-verify ${OSTREE_OSNAME} ${PROD_REPO_URL}
@@ -521,6 +517,10 @@ password = "${EDGE_USER_PASSWORD_SHA512}"
 key = "${SSH_KEY_PUB}"
 home = "/home/installeruser/"
 groups = ["wheel"]
+
+[customizations.installer]
+unattended = true
+sudo-nopasswd = ["admin", "installeruser"]
 EOF
 
 greenprint "📄 installer blueprint"
diff --git a/test/cases/worker-executor.sh b/test/cases/worker-executor.sh
index b5af88c5a..c654bc818 100644
--- a/test/cases/worker-executor.sh
+++ b/test/cases/worker-executor.sh
@@ -17,6 +17,7 @@ BLUEPRINT_FILE=${TEMPDIR}/blueprint.toml
 COMPOSE_START=${TEMPDIR}/compose-start.json
 COMPOSE_INFO=${TEMPDIR}/compose-info.json
 DESCR_INST=${TEMPDIR}/descr-inst.json
+DESCR_SGRULE=${TEMPDIR}/descr-sgrule.json
 KEYPAIR=${TEMPDIR}/keypair.pem
 INSTANCE_ID=$(curl -Ls http://169.254.169.254/latest/meta-data/instance-id)
 
@@ -120,6 +121,12 @@ if [ "$RDY" = 0 ]; then
 fi
 
 greenprint "Setting up executor"
+
+# allow the executor to access the internet for the setup
+SGID=$(jq -r .Reservations[0].Instances[0].SecurityGroups[0].GroupId "$DESCR_INST")
+$AWS_CMD ec2 authorize-security-group-egress --group-id "$SGID" --protocol tcp --cidr 0.0.0.0/0 --port 1-65535 > "$DESCR_SGRULE"
+SGRULEID=$(jq -r .SecurityGroupRules[0].SecurityGroupRuleId "$DESCR_SGRULE")
+
 GIT_COMMIT="${GIT_COMMIT:-${CI_COMMIT_SHA}}"
 OSBUILD_GIT_COMMIT=$(cat Schutzfile | jq -r '.["'"${ID}-${VERSION_ID}"'"].dependencies.osbuild.commit')
 # shellcheck disable=SC2087
@@ -143,6 +150,9 @@ subprocessPIDs+=( $! )
 
 ssh -oStrictHostKeyChecking=no -i "$KEYPAIR" "fedora@$EXECUTOR_IP" sudo dnf install -y osbuild-composer osbuild
 
+# no internet access during the build
+$AWS_CMD ec2 revoke-security-group-egress --group-id "$SGID" --security-group-rule-ids "$SGRULEID"
+
 ssh -oStrictHostKeyChecking=no -i "$KEYPAIR" "fedora@$EXECUTOR_IP" sudo mkdir -p /var/cache/osbuild-builder
 
 ssh -oStrictHostKeyChecking=no -i "$KEYPAIR" "fedora@$EXECUTOR_IP" sudo /usr/libexec/osbuild-composer/osbuild-jobsite-builder -builder-host 0.0.0.0 -build-path /var/cache/osbuild-builder &