Automatic disk resizing for macOS VMs (#1)

* Automatic disk resizing for macOS VMs

* CI: install Golang via Homebrew

* diskresizer_test.go: fix fixture values for "diskSizeUnused"
This commit is contained in:
Nikolay Edigaryev
2025-04-07 22:54:19 +04:00
committed by GitHub
parent 9685ba8ffc
commit 42e506671f
14 changed files with 787 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
load("github.com/cirrus-modules/golang@main", "lint_task")
def main(ctx):
return [lint_task()]
+52
View File
@@ -0,0 +1,52 @@
task:
name: Test (Linux)
alias: Tests
container:
image: golang:latest
test_script: go test -v ./...
task:
name: Test (macOS)
alias: Tests
macos_instance:
image: ghcr.io/cirruslabs/macos-runner:sequoia
prepare_script: brew install go
test_script: go test -v ./...
task:
name: Release (Dry Run)
only_if: $CIRRUS_TAG == ''
container:
image: golang:latest
memory: 8GB
depends_on:
- Lint
- Tests
install_script:
- echo 'deb [trusted=yes] https://repo.goreleaser.com/apt/ /' | tee /etc/apt/sources.list.d/goreleaser.list
- apt-get update
- apt-get -y install goreleaser-pro
release_script: goreleaser build --snapshot
goreleaser_artifacts:
path: "dist/**"
task:
name: Release
only_if: $CIRRUS_TAG != ''
container:
image: golang:latest
memory: 8GB
depends_on:
- Lint
- Tests
env:
GITHUB_TOKEN: ENCRYPTED[!98ace8259c6024da912c14d5a3c5c6aac186890a8d4819fad78f3e0c41a4e0cd3a2537dd6e91493952fb056fa434be7c!]
FURY_TOKEN: ENCRYPTED[!97fe4497d9aca60a3d64904883b81e21f19706c6aedda625c97f62f67ec46b8efa74c55699956158bbf0a23726e7d9f6!]
GORELEASER_KEY: ENCRYPTED[!9b80b6ef684ceaf40edd4c7af93014ee156c8aba7e6e5795f41c482729887b5c31f36b651491d790f1f668670888d9fd!]
install_script:
- echo 'deb [trusted=yes] https://repo.goreleaser.com/apt/ /' | tee /etc/apt/sources.list.d/goreleaser.list
- apt-get update
- apt-get -y install goreleaser-pro
release_script: goreleaser
+67
View File
@@ -0,0 +1,67 @@
version: 2
run:
timeout: 5m
linters-settings:
# Even in Rust you can get away with partial matching,
# so make sure that the linter respects the programmer's
# intent expressed in the form of "default" case.
exhaustive:
default-signifies-exhaustive: true
gosec:
excludes:
- G115
linters:
enable-all: true
disable:
# We don't have high-performance requirements at this moment, so sacrificing
# the code readability for marginal performance gains is not worth it.
- prealloc
# New linters that require a lot of codebase churn and noise, but perhaps we can enable them in the future.
- nlreturn
- wrapcheck
- errorlint
# Unfortunately, we use globals due to how spf13/cobra works.
- gochecknoglobals
# Style linters that are total nuts.
- wsl
- funlen
# Enough parallelism for now.
- paralleltest
# Ill-based assumptions about identifiers like fmt.Println without taking context into account.
- forbidigo
# Advantages of using t.Helper() are too small to waste developer's cognitive stamina on it.
- thelper
# Too restrictive defaults, plus there's already a gocyclo linter in place.
- cyclop
# Gives false positives for textbook examples[1][2]
# [1]: https://github.com/charithe/durationcheck/issues/7
# [2]: https://golang.org/pkg/time/ (see "To convert an integer number of units to a Duration, multiply:")
- durationcheck
# No way to disable the "exported" check for the whole project[1]
# [1]: https://github.com/mgechev/revive/issues/244#issuecomment-560512162
- revive
# Needs package whitelists
- depguard
# Not all errors need to be checked
- errcheck
issues:
# Don't hide multiple issues that belong to one class since GitHub annotations can handle them all nicely.
max-issues-per-linter: 0
max-same-issues: 0
+43
View File
@@ -0,0 +1,43 @@
project_name: tart-guest-agent
before:
hooks:
- go mod download
builds:
- main: cmd/main.go
ldflags: >
-X github.com/cirruslabs/tart-guest-agent/internal/version.Version={{.Version}}
-X github.com/cirruslabs/tart-guest-agent/internal/version.Commit={{.ShortCommit}}
env:
- CGO_ENABLED=0
goos:
- darwin
- linux
goarch:
- amd64
- arm64
archives:
- id: binary
format: binary
name_template: "{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}"
- id: regular
name_template: "{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}"
release:
prerelease: auto
nfpms:
- package_name: tart-guest-agent
vendor: Cirrus Labs, Inc.
homepage: https://github.com/cirruslabs/tart-guest-agent
maintainer: support@cirruslabs.org
description: Guest agent for Tart VMs
section: misc
formats:
- deb
- rpm
furies:
- account: cirruslabs
+4
View File
@@ -1,3 +1,7 @@
# Guest agent for Tart VMs
A guest agent for Tart VMS is a lightweight background service that runs inside the virtual machine and enables enhanced communication between the host and guest and other useful features, such as automatic disk resizing.
Currently implemented features:
* Automatic disk resizing for macOS VMs with recovery partition removed
+49
View File
@@ -0,0 +1,49 @@
package main
import (
"context"
"fmt"
"github.com/cirruslabs/tart-guest-agent/internal/command"
"github.com/cirruslabs/tart-guest-agent/internal/logginglevel"
"go.uber.org/zap"
"os"
"os/signal"
"syscall"
)
func main() {
if !mainImpl() {
os.Exit(1)
}
}
func mainImpl() bool {
// Set up a signal-interruptible context
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer cancel()
// Initialize logger
loggerConfig := zap.NewProductionConfig()
loggerConfig.Level = logginglevel.Level
logger, err := loggerConfig.Build()
if err != nil {
_, _ = fmt.Fprintln(os.Stderr, err)
return false
}
defer func() {
_ = logger.Sync()
}()
// Replace zap.L() and zap.S() to avoid
// propagating the *zap.Logger by hand
zap.ReplaceGlobals(logger)
if err := command.NewRootCommand().ExecuteContext(ctx); err != nil {
logger.Sugar().Error(err)
return false
}
return true
}
+23
View File
@@ -0,0 +1,23 @@
module github.com/cirruslabs/tart-guest-agent
go 1.23.7
require (
github.com/hashicorp/go-version v1.7.0
github.com/samber/lo v1.49.1
github.com/spf13/cobra v1.9.1
github.com/stretchr/testify v1.10.0
go.uber.org/zap v1.27.0
golang.org/x/sys v0.32.0
howett.net/plist v1.0.1
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spf13/pflag v1.0.6 // indirect
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/text v0.21.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+41
View File
@@ -0,0 +1,41 @@
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
+64
View File
@@ -0,0 +1,64 @@
package command
import (
"errors"
"github.com/cirruslabs/tart-guest-agent/internal/diskresizer"
"github.com/cirruslabs/tart-guest-agent/internal/logginglevel"
"github.com/cirruslabs/tart-guest-agent/internal/tart"
"github.com/cirruslabs/tart-guest-agent/internal/version"
"github.com/spf13/cobra"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"golang.org/x/sys/unix"
"os"
"syscall"
)
var debug bool
func NewRootCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "tart-guest-agent",
Short: "Guest agent for Tart VMs",
Version: version.FullVersion,
SilenceUsage: true,
SilenceErrors: true,
PersistentPreRunE: func(_ *cobra.Command, _ []string) error {
if debug {
logginglevel.Level.SetLevel(zapcore.DebugLevel)
}
return nil
},
RunE: run,
}
return cmd
}
func run(cmd *cobra.Command, args []string) error {
// Terminate to prevent corruption on systems with disk layouts other than Tart's
communicationPoint, ok := tart.LocateCommunicationPoint()
if !ok {
return unix.Kill(os.Getppid(), syscall.SIGTERM)
}
zap.S().Infof("successfully located host communication point %s, proceeding...",
communicationPoint)
// Perform disk resizing
if err := diskresizer.Resize(); err != nil {
if errors.Is(err, diskresizer.ErrUnsupported) || errors.Is(err, diskresizer.ErrAlreadyResized) {
zap.S().Infof("skipping disk resizing: %v", err)
} else {
zap.S().Warnf("failed to resize disk: %v", err)
}
} else {
zap.S().Infof("sucessfully resized the disk")
}
// Wait indefinitely
<-cmd.Context().Done()
return cmd.Context().Err()
}
+156
View File
@@ -0,0 +1,156 @@
package diskresizer
import (
"bytes"
"errors"
"fmt"
"github.com/samber/lo"
"howett.net/plist"
"os/exec"
"runtime"
"strings"
)
type DiskUtilOutput struct {
AllDisksAndPartitions []DiskWithPartitions `plist:"AllDisksAndPartitions"`
}
type DiskWithPartitions struct {
DeviceIdentifier string `plist:"DeviceIdentifier"`
Size int64 `plist:"Size"`
Partitions []Partition `plist:"Partitions"`
}
type Partition struct {
DeviceIdentifier string `plist:"DeviceIdentifier"`
Size int64 `plist:"Size"`
Content string `plist:"Content"`
}
const expectedPartitionContent = "Apple_APFS"
var (
ErrUnsupported = errors.New("disk resizing is only supported on macOS")
ErrAlreadyResized = errors.New("disk already seems to be resized")
)
func Resize() error {
// Only macOS is currently supported because Linux distributions
// already have this functionality
if runtime.GOOS != "darwin" {
return ErrUnsupported
}
// Obtain a list of physical disks with their partitions
cmd := exec.Command("diskutil", "list", "-plist", "physical")
stderrBuf := &bytes.Buffer{}
cmd.Stderr = stderrBuf
diskutilOutput, err := cmd.Output()
if err != nil {
return fmt.Errorf("failed to list physical disks: %w: %s", err,
firstNonEmptyLine(stderrBuf.String()))
}
// Figure out which disk and partition to resize,
// aborts on ambiguity
diskName, partitionName, diskSizeUnused, err := diskAndPartitionNameToResize(diskutilOutput)
if err != nil {
return fmt.Errorf("failed to locate a physical disk to resize: %w", err)
}
if diskSizeUnused <= 4096*10 {
return ErrAlreadyResized
}
// Repair the disk whose partition we're going to resize, just in case
cmd = exec.Command("diskutil", "repairDisk", diskName)
cmd.Stdin = bytes.NewReader([]byte("yes"))
stderrBuf = &bytes.Buffer{}
cmd.Stderr = stderrBuf
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to repair disk %s: %w: %s", diskName, err,
firstNonEmptyLine(stderrBuf.String()))
}
// Finally, resize the partition
cmd = exec.Command("diskutil", "apfs", "resizeContainer", partitionName, "0")
stderrBuf = &bytes.Buffer{}
cmd.Stderr = stderrBuf
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to resize partition %s: %w: %s", partitionName, err,
firstNonEmptyLine(stderrBuf.String()))
}
return nil
}
// diskAndPartitionNameToResize parses "diskutil list -plist" output,
// makes sure there's only one disk on the system and returns its
// name and the name of a single APFS partition or errors otherwise.
func diskAndPartitionNameToResize(input []byte) (string, string, int64, error) {
var diskUtilOutput DiskUtilOutput
_, err := plist.Unmarshal(input, &diskUtilOutput)
if err != nil {
return "", "", 0, fmt.Errorf("failed to parse \"diskutil list -plist\" output: %w", err)
}
type Candidate struct {
DiskName string
DiskSizeUnused int64
PartitionName string
}
var candidates []Candidate
for _, diskWithPartitions := range diskUtilOutput.AllDisksAndPartitions {
unusedSize := diskWithPartitions.Size - lo.SumBy(diskWithPartitions.Partitions, func(partition Partition) int64 {
return partition.Size
})
for _, partition := range diskWithPartitions.Partitions {
if partition.Content != expectedPartitionContent {
continue
}
candidates = append(candidates, Candidate{
DiskName: diskWithPartitions.DeviceIdentifier,
DiskSizeUnused: unusedSize,
PartitionName: partition.DeviceIdentifier,
})
}
}
if len(candidates) == 0 {
return "", "", 0, fmt.Errorf("found no disks on which the partition's \"Content\" "+
"is %q, make sure that the macOS is installed", expectedPartitionContent)
}
if len(candidates) > 1 {
return "", "", 0, fmt.Errorf("found more than one disk on which the partition's \"Content\" "+
"is %q, please only mount a single disk that contains APFS partitions otherwise it's hard "+
"to tell on which disk the macOS is installed", expectedPartitionContent)
}
return candidates[0].DiskName, candidates[0].PartitionName,
candidates[0].DiskSizeUnused, nil
}
func firstNonEmptyLine(outputs ...string) string {
for _, output := range outputs {
for _, line := range strings.Split(output, "\n") {
if line != "" {
return line
}
}
}
return ""
}
+222
View File
@@ -0,0 +1,222 @@
package diskresizer
import (
"github.com/stretchr/testify/require"
"testing"
)
func TestSingleDisk(t *testing.T) {
plistBytes := `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AllDisks</key>
<array>
<string>disk0</string>
<string>disk0s1</string>
<string>disk0s2</string>
<string>disk0s3</string>
</array>
<key>AllDisksAndPartitions</key>
<array>
<dict>
<key>Content</key>
<string>GUID_partition_scheme</string>
<key>DeviceIdentifier</key>
<string>disk0</string>
<key>OSInternal</key>
<false/>
<key>Partitions</key>
<array>
<dict>
<key>Content</key>
<string>Apple_APFS_ISC</string>
<key>DeviceIdentifier</key>
<string>disk0s1</string>
<key>DiskUUID</key>
<string>024D2AE5-891F-4244-81EC-182B88D1AA0B</string>
<key>Size</key>
<integer>524288000</integer>
</dict>
<dict>
<key>Content</key>
<string>Apple_APFS</string>
<key>DeviceIdentifier</key>
<string>disk0s2</string>
<key>DiskUUID</key>
<string>430F1409-D91A-47F4-8418-0876B14AA807</string>
<key>Size</key>
<integer>494384795648</integer>
</dict>
</array>
<key>Size</key>
<integer>500277792768</integer>
</dict>
</array>
<key>VolumesFromDisks</key>
<array/>
<key>WholeDisks</key>
<array>
<string>disk0</string>
</array>
</dict>
</plist>
`
diskName, partitionName, diskSizeUnused, err := diskAndPartitionNameToResize([]byte(plistBytes))
require.NoError(t, err)
require.Equal(t, "disk0", diskName)
require.Equal(t, "disk0s2", partitionName)
require.EqualValues(t, 5368709120, diskSizeUnused)
}
func TestSingleDiskWithRecoveryPartition(t *testing.T) {
plistBytes := `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AllDisks</key>
<array>
<string>disk0</string>
<string>disk0s1</string>
<string>disk0s2</string>
<string>disk0s3</string>
</array>
<key>AllDisksAndPartitions</key>
<array>
<dict>
<key>Content</key>
<string>GUID_partition_scheme</string>
<key>DeviceIdentifier</key>
<string>disk0</string>
<key>OSInternal</key>
<false/>
<key>Partitions</key>
<array>
<dict>
<key>Content</key>
<string>Apple_APFS_ISC</string>
<key>DeviceIdentifier</key>
<string>disk0s1</string>
<key>DiskUUID</key>
<string>22A4F5CC-CB5B-4EF1-B4F5-EBE9F79C0EE6</string>
<key>Size</key>
<integer>524288000</integer>
</dict>
<dict>
<key>Content</key>
<string>Apple_APFS</string>
<key>DeviceIdentifier</key>
<string>disk0s2</string>
<key>DiskUUID</key>
<string>E59D951F-E0B0-42B5-BE26-DF3611AD673C</string>
<key>Size</key>
<integer>44107001856</integer>
</dict>
<dict>
<key>Content</key>
<string>Apple_APFS_Recovery</string>
<key>DeviceIdentifier</key>
<string>disk0s3</string>
<key>DiskUUID</key>
<string>7684137C-F165-45C4-948B-681012742DAC</string>
<key>Size</key>
<integer>5368672256</integer>
</dict>
</array>
<key>Size</key>
<integer>50000000000</integer>
</dict>
</array>
<key>VolumesFromDisks</key>
<array/>
<key>WholeDisks</key>
<array>
<string>disk0</string>
</array>
</dict>
</plist>
`
diskName, partitionName, diskSizeUnused, err := diskAndPartitionNameToResize([]byte(plistBytes))
require.NoError(t, err)
require.Equal(t, "disk0", diskName)
require.Equal(t, "disk0s2", partitionName)
require.EqualValues(t, 37888, diskSizeUnused)
}
func TestMultipleDisks(t *testing.T) {
plistBytes := `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AllDisks</key>
<array>
<string>disk0</string>
<string>disk0s1</string>
<string>disk0s2</string>
<string>disk2</string>
</array>
<key>AllDisksAndPartitions</key>
<array>
<dict>
<key>Content</key>
<string>GUID_partition_scheme</string>
<key>DeviceIdentifier</key>
<string>disk0</string>
<key>OSInternal</key>
<false/>
<key>Partitions</key>
<array>
<dict>
<key>Content</key>
<string>Apple_APFS_ISC</string>
<key>DeviceIdentifier</key>
<string>disk0s1</string>
<key>DiskUUID</key>
<string>D2B79297-879E-4461-8DA2-EEA50EA7319A</string>
<key>Size</key>
<integer>524288000</integer>
</dict>
<dict>
<key>Content</key>
<string>Apple_APFS</string>
<key>DeviceIdentifier</key>
<string>disk0s2</string>
<key>DiskUUID</key>
<string>D5BA624D-182F-40D0-8248-D08508A8D1B3</string>
<key>Size</key>
<integer>89475674112</integer>
</dict>
</array>
<key>Size</key>
<integer>90000000000</integer>
</dict>
<dict>
<key>Content</key>
<string></string>
<key>DeviceIdentifier</key>
<string>disk2</string>
<key>OSInternal</key>
<false/>
<key>Size</key>
<integer>107164426240</integer>
</dict>
</array>
<key>VolumesFromDisks</key>
<array/>
<key>WholeDisks</key>
<array>
<string>disk0</string>
<string>disk2</string>
</array>
</dict>
</plist>
`
diskName, partitionName, diskSizeUnused, err := diskAndPartitionNameToResize([]byte(plistBytes))
require.NoError(t, err)
require.Equal(t, "disk0", diskName)
require.Equal(t, "disk0s2", partitionName)
require.EqualValues(t, 37888, diskSizeUnused)
}
+6
View File
@@ -0,0 +1,6 @@
package logginglevel
import "go.uber.org/zap"
//nolint:gochecknoglobals
var Level = zap.NewAtomicLevel()
+24
View File
@@ -0,0 +1,24 @@
package tart
import (
"os"
"path/filepath"
"strings"
)
const devDirectoryPath = "/dev"
func LocateCommunicationPoint() (string, bool) {
dirEntries, err := os.ReadDir(devDirectoryPath)
if err != nil {
return "", false
}
for _, entry := range dirEntries {
if strings.HasPrefix(entry.Name(), "cu.tart-version-") {
return filepath.Join(devDirectoryPath, entry.Name()), true
}
}
return "", false
}
+32
View File
@@ -0,0 +1,32 @@
package version
import (
"fmt"
goversion "github.com/hashicorp/go-version"
"runtime/debug"
)
var (
Version = "unknown"
Commit = "unknown"
FullVersion = ""
)
func init() {
if Version == "unknown" {
info, ok := debug.ReadBuildInfo()
if ok {
// We parse the version here for two reasons:
// * to weed out the "(devel)" version and fallback to "unknown" instead
// (see https://github.com/golang/go/issues/29228 for details on when this might happen)
// * to remove the "v" prefix from the BuildInfo's version (e.g. "v0.7.0") and thus be consistent
// with the binary builds, where the version string would be "0.7.0" instead
semver, err := goversion.NewSemver(info.Main.Version)
if err == nil {
Version = semver.String()
}
}
}
FullVersion = fmt.Sprintf("%s-%s", Version, Commit)
}