Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .github/workflows/release-upgrade.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -893,11 +893,14 @@ jobs:
# main only, so there was nothing to run
#
# A FAILED gate matches neither and publishes nothing, which is the property
# worth protecting. `skipped` is compared explicitly rather than testing for
# "not success", which would let a failure through.
# worth protecting. resolve must also have succeeded: when the upstream
# release build fails, resolve is skipped and its empty from_main output
# must not look like a release-branch cut. `skipped` is compared explicitly
# rather than testing for "not success", which would let a failure through.
if: >
!cancelled() &&
github.repository == 'Azure/unbounded' &&
needs.resolve.result == 'success' &&
inputs.force_publish != true &&
inputs.force_publish != 'true' &&
(
Expand Down
35 changes: 35 additions & 0 deletions hack/release/release_upgrade_workflow_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0

package release

import (
"os"
"strings"
"testing"

"gopkg.in/yaml.v3"
)

func TestPublishRequiresResolvedRelease(t *testing.T) {
t.Parallel()

data, err := os.ReadFile("../../.github/workflows/release-upgrade.yaml")
if err != nil {
t.Fatal(err)
}

var workflow struct {
Jobs map[string]struct {
If string `yaml:"if"`
} `yaml:"jobs"`
}
if err := yaml.Unmarshal(data, &workflow); err != nil {
t.Fatal(err)
}

condition := workflow.Jobs["publish"].If
if !strings.Contains(condition, "needs.resolve.result == 'success'") {
t.Fatalf("publish condition can run without a resolved release:\n%s", condition)
}
}
15 changes: 7 additions & 8 deletions images/netboot/Containerfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,21 @@ RUN mkdir -p /artifacts && \
KVER=$(ls /lib/modules | head -1) && \
cp "/boot/vmlinuz-${KVER}" /artifacts/vmlinuz && \
if [ "${TARGETARCH}" = "amd64" ]; then \
NETBOOT_BASE="https://releases.ubuntu.com/24.04.2/netboot/amd64"; \
NETBOOT_BASE="https://releases.ubuntu.com/noble/netboot/amd64"; \
curl -fSL --connect-timeout 10 --retry 3 --retry-delay 2 \
-o /artifacts/shimx64.efi "${NETBOOT_BASE}/bootx64.efi" && \
curl -fSL --connect-timeout 10 --retry 3 --retry-delay 2 \
-o /artifacts/grubx64.efi "${NETBOOT_BASE}/grubx64.efi" && \
curl -fSL --connect-timeout 10 --retry 3 --retry-delay 2 \
-o /artifacts/initrd "${NETBOOT_BASE}/initrd"; \
elif [ "${TARGETARCH}" = "arm64" ]; then \
NETBOOT_URL="https://cdimage.ubuntu.com/releases/noble/release/ubuntu-24.04.4-netboot-arm64.tar.gz"; \
NETBOOT_BASE="https://cdimage.ubuntu.com/ubuntu/releases/noble/release/netboot/arm64"; \
curl -fSL --connect-timeout 10 --retry 3 --retry-delay 2 \
-o /tmp/netboot.tar.gz "${NETBOOT_URL}" && \
tar -xzf /tmp/netboot.tar.gz -C /tmp && \
cp /tmp/arm64/bootaa64.efi /artifacts/shimaa64.efi && \
cp /tmp/arm64/grubaa64.efi /artifacts/grubaa64.efi && \
cp /tmp/arm64/initrd /artifacts/initrd && \
rm -rf /tmp/netboot.tar.gz /tmp/arm64; \
-o /artifacts/shimaa64.efi "${NETBOOT_BASE}/bootaa64.efi" && \
curl -fSL --connect-timeout 10 --retry 3 --retry-delay 2 \
-o /artifacts/grubaa64.efi "${NETBOOT_BASE}/grubaa64.efi" && \
curl -fSL --connect-timeout 10 --retry 3 --retry-delay 2 \
-o /artifacts/initrd "${NETBOOT_BASE}/initrd"; \
else \
echo "Unsupported architecture: ${TARGETARCH}" >&2; exit 1; \
fi
Expand Down
60 changes: 0 additions & 60 deletions internal/fsutil/fsutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,11 @@ package fsutil
import (
"crypto/sha256"
"errors"
"fmt"
"io"
"os"
"path/filepath"

"github.com/google/renameio/v2"
"golang.org/x/sys/unix"
)

// SyncDir persists a directory entry so newly written names survive a crash.
Expand Down Expand Up @@ -98,53 +96,6 @@ func InstallFile(source, target string, mode os.FileMode) (err error) {
return pending.CloseAtomicallyReplace()
}

// SyncFilesystems persists every filesystem backing the given paths.
func SyncFilesystems(paths ...string) error {
var files []*os.File

defer func() {
for _, f := range files {
_ = f.Close() //nolint:errcheck // Read-only handle; sync errors are returned.
}
}()

for _, path := range paths {
f, err := os.Open(path)
if err != nil {
return err
}

files = append(files, f)
}

return SyncOpenFilesystems(files, unix.Syncfs)
}

// SyncOpenFilesystems synchronizes each distinct filesystem once, using open
// handles that stay valid after teardown removes their paths.
func SyncOpenFilesystems(files []*os.File, syncfs func(int) error) error {
seen := map[uint64]bool{}

for _, f := range files {
var stat unix.Stat_t
if err := unix.Fstat(int(f.Fd()), &stat); err != nil {
return err
}

if seen[uint64(stat.Dev)] {
continue
}

seen[uint64(stat.Dev)] = true

if err := syncfs(int(f.Fd())); err != nil {
return fmt.Errorf("sync %s: %w", f.Name(), err)
}
}

return nil
}

// FileSHA256 returns the SHA-256 digest of the file at path.
func FileSHA256(path string) ([sha256.Size]byte, error) {
var digest [sha256.Size]byte
Expand All @@ -164,14 +115,3 @@ func FileSHA256(path string) ([sha256.Size]byte, error) {

return digest, nil
}

// MountedNoexec reports whether the filesystem holding path is mounted without
// permission to run programs from it, so a binary placed there cannot run.
func MountedNoexec(path string) (bool, error) {
var fs unix.Statfs_t
if err := unix.Statfs(path, &fs); err != nil {
return false, fmt.Errorf("inspect the filesystem of %s: %w", path, err)
}

return fs.Flags&unix.ST_NOEXEC != 0, nil
}
71 changes: 71 additions & 0 deletions internal/fsutil/fsutil_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0

//go:build linux

package fsutil

import (
"fmt"
"os"

"golang.org/x/sys/unix"
)

// SyncFilesystems persists every filesystem backing the given paths.
func SyncFilesystems(paths ...string) error {
var files []*os.File

defer func() {
for _, f := range files {
_ = f.Close() //nolint:errcheck // Read-only handle; sync errors are returned.
}
}()

for _, path := range paths {
f, err := os.Open(path)
if err != nil {
return err
}

files = append(files, f)
}

return SyncOpenFilesystems(files, unix.Syncfs)
}

// SyncOpenFilesystems synchronizes each distinct filesystem once, using open
// handles that stay valid after teardown removes their paths.
func SyncOpenFilesystems(files []*os.File, syncfs func(int) error) error {
seen := map[uint64]bool{}

for _, f := range files {
var stat unix.Stat_t
if err := unix.Fstat(int(f.Fd()), &stat); err != nil {
return err
}

if seen[uint64(stat.Dev)] {
continue
}

seen[uint64(stat.Dev)] = true

if err := syncfs(int(f.Fd())); err != nil {
return fmt.Errorf("sync %s: %w", f.Name(), err)
}
}

return nil
}

// MountedNoexec reports whether the filesystem holding path is mounted without
// permission to run programs from it, so a binary placed there cannot run.
func MountedNoexec(path string) (bool, error) {
var fs unix.Statfs_t
if err := unix.Statfs(path, &fs); err != nil {
return false, fmt.Errorf("inspect the filesystem of %s: %w", path, err)
}

return fs.Flags&unix.ST_NOEXEC != 0, nil
}
29 changes: 29 additions & 0 deletions internal/fsutil/fsutil_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0

//go:build !linux

package fsutil

import (
"errors"
"fmt"
"os"
)

var errLinuxOnly = errors.New("operation requires Linux")

// SyncFilesystems is unsupported outside Linux.
func SyncFilesystems(...string) error {
return errLinuxOnly
}

// SyncOpenFilesystems is unsupported outside Linux.
func SyncOpenFilesystems([]*os.File, func(int) error) error {
return errLinuxOnly
}

// MountedNoexec is unsupported outside Linux.
func MountedNoexec(path string) (bool, error) {
return false, fmt.Errorf("inspect the filesystem of %s: %w", path, errLinuxOnly)
}
Loading