Skip to content

Commit c4374ce

Browse files
committed
cmd, pkg/utils: Split distro and release parsing and report better errors
Using a non-supported distribution via `--distro` resulted in a silent fallback to the Fedora image which confuses users. When a faulty release format was used with `--release` a message without any hint about the correct format was shown to the user. This separates distro and release parsing into two chunks that have greater control over error reporting and provides more accurate error reports for the user. Fixes #937 #977
1 parent 2af0b30 commit c4374ce

File tree

9 files changed

+353
-16
lines changed

9 files changed

+353
-16
lines changed

src/cmd/create.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -138,17 +138,23 @@ func create(cmd *cobra.Command, args []string) error {
138138
}
139139
}
140140

141-
var release string
142-
if createFlags.release != "" {
141+
distro, err := utils.ResolveDistro(createFlags.distro)
142+
if err != nil {
143+
err := createErrorInvalidDistro()
144+
return err
145+
}
146+
147+
release := createFlags.release
148+
if release != "" {
143149
var err error
144-
release, err = utils.ParseRelease(createFlags.distro, createFlags.release)
150+
release, err = utils.ParseRelease(distro, release)
145151
if err != nil {
146-
err := createErrorInvalidRelease()
152+
err := createErrorInvalidRelease(distro)
147153
return err
148154
}
149155
}
150156

151-
image, release, err := utils.ResolveImageName(createFlags.distro, createFlags.image, release)
157+
image, release, err := utils.ResolveImageName(distro, createFlags.image, release)
152158
if err != nil {
153159
return err
154160
}

src/cmd/enter.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,19 +104,25 @@ func enter(cmd *cobra.Command, args []string) error {
104104
}
105105
}
106106

107-
var release string
108-
if enterFlags.release != "" {
107+
distro, err := utils.ResolveDistro(enterFlags.distro)
108+
if err != nil {
109+
err := createErrorInvalidDistro()
110+
return err
111+
}
112+
113+
release := enterFlags.release
114+
if release != "" {
109115
defaultContainer = false
110116

111117
var err error
112-
release, err = utils.ParseRelease(enterFlags.distro, enterFlags.release)
118+
release, err = utils.ParseRelease(distro, release)
113119
if err != nil {
114-
err := createErrorInvalidRelease()
120+
err := createErrorInvalidRelease(distro)
115121
return err
116122
}
117123
}
118124

119-
image, release, err := utils.ResolveImageName(enterFlags.distro, "", release)
125+
image, release, err := utils.ResolveImageName(distro, "", release)
120126
if err != nil {
121127
return err
122128
}

src/cmd/run.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -102,14 +102,20 @@ func run(cmd *cobra.Command, args []string) error {
102102
}
103103
}
104104

105-
var release string
106-
if runFlags.release != "" {
105+
distro, err := utils.ResolveDistro(runFlags.distro)
106+
if err != nil {
107+
err := createErrorInvalidDistro()
108+
return err
109+
}
110+
111+
release := runFlags.release
112+
if release != "" {
107113
defaultContainer = false
108114

109115
var err error
110-
release, err = utils.ParseRelease(runFlags.distro, runFlags.release)
116+
release, err = utils.ParseRelease(distro, release)
111117
if err != nil {
112-
err := createErrorInvalidRelease()
118+
err := createErrorInvalidRelease(distro)
113119
return err
114120
}
115121
}
@@ -125,7 +131,7 @@ func run(cmd *cobra.Command, args []string) error {
125131

126132
command := args
127133

128-
image, release, err := utils.ResolveImageName(runFlags.distro, "", release)
134+
image, release, err := utils.ResolveImageName(distro, "", release)
129135
if err != nil {
130136
return err
131137
}

src/cmd/utils.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import (
2323
"os/exec"
2424
"strings"
2525
"syscall"
26+
27+
"github.com/containers/toolbox/pkg/utils"
2628
)
2729

2830
// askForConfirmation prints prompt to stdout and waits for response from the
@@ -69,9 +71,20 @@ func createErrorContainerNotFound(container string) error {
6971
return errors.New(errMsg)
7072
}
7173

72-
func createErrorInvalidRelease() error {
74+
func createErrorInvalidDistro() error {
75+
var builder strings.Builder
76+
fmt.Fprintf(&builder, "invalid argument for '--distro'\n")
77+
fmt.Fprintf(&builder, "Supported values are: %s\n", strings.Join(utils.GetSupportedDistros(), " "))
78+
fmt.Fprintf(&builder, "Run '%s --help' for usage.", executableBase)
79+
80+
errMsg := builder.String()
81+
return errors.New(errMsg)
82+
}
83+
84+
func createErrorInvalidRelease(distro string) error {
7385
var builder strings.Builder
7486
fmt.Fprintf(&builder, "invalid argument for '--release'\n")
87+
fmt.Fprintf(&builder, "Supported values for distribution %s are in format: %s\n", distro, utils.GetReleaseFormat(distro))
7588
fmt.Fprintf(&builder, "Run '%s --help' for usage.", executableBase)
7689

7790
errMsg := builder.String()

src/pkg/utils/utils.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ type Distro struct {
4444
ContainerNamePrefix string
4545
ImageBasename string
4646
ParseRelease ParseReleaseFunc
47+
ReleaseFormat string
4748
Registry string
4849
Repository string
4950
RepositoryNeedsRelease bool
@@ -60,6 +61,10 @@ const (
6061
ContainerNameRegexp = "[a-zA-Z0-9][a-zA-Z0-9_.-]*"
6162
)
6263

64+
var (
65+
ErrUnsupportedDistro = errors.New("linux distribution is not supported")
66+
)
67+
6368
var (
6469
containerNamePrefixDefault = "fedora-toolbox"
6570

@@ -98,6 +103,7 @@ var (
98103
"fedora-toolbox",
99104
"fedora-toolbox",
100105
parseReleaseFedora,
106+
"<release>/f<release>",
101107
"registry.fedoraproject.org",
102108
"",
103109
false,
@@ -106,6 +112,7 @@ var (
106112
"rhel-toolbox",
107113
"toolbox",
108114
parseReleaseRHEL,
115+
"<major.minor>",
109116
"registry.access.redhat.com",
110117
"ubi8",
111118
false,
@@ -407,6 +414,20 @@ func GetMountOptions(target string) (string, error) {
407414
return mountOptions, nil
408415
}
409416

417+
// GetReleaseFormat returns the format string signifying supported release
418+
// version formats.
419+
//
420+
// distro should be value found under ID in os-release.
421+
//
422+
// If distro is unsupported an empty string is returned.
423+
func GetReleaseFormat(distro string) string {
424+
if !IsDistroSupported(distro) {
425+
return ""
426+
}
427+
428+
return supportedDistros[distro].ReleaseFormat
429+
}
430+
410431
func GetRuntimeDirectory(targetUser *user.User) (string, error) {
411432
gid, err := strconv.Atoi(targetUser.Gid)
412433
if err != nil {
@@ -446,6 +467,15 @@ func GetRuntimeDirectory(targetUser *user.User) (string, error) {
446467
return toolboxRuntimeDirectory, nil
447468
}
448469

470+
// GetSupportedDistros returns a list of supported distributions
471+
func GetSupportedDistros() []string {
472+
var distros []string
473+
for d := range supportedDistros {
474+
distros = append(distros, d)
475+
}
476+
return distros
477+
}
478+
449479
// HumanDuration accepts a Unix time value and converts it into a human readable
450480
// string.
451481
//
@@ -454,6 +484,14 @@ func HumanDuration(duration int64) string {
454484
return units.HumanDuration(time.Since(time.Unix(duration, 0))) + " ago"
455485
}
456486

487+
// IsDistroSupported signifies if a distribution has a toolbx image for it.
488+
//
489+
// distro should be value found under ID in os-release.
490+
func IsDistroSupported(distro string) bool {
491+
_, ok := supportedDistros[distro]
492+
return ok
493+
}
494+
457495
// ImageReferenceCanBeID checks if 'image' might be the ID of an image
458496
func ImageReferenceCanBeID(image string) bool {
459497
matched, err := regexp.MatchString("^[a-f0-9]{6,64}$", image)
@@ -598,6 +636,11 @@ func ShortID(id string) string {
598636
return id
599637
}
600638

639+
// ParseRelease assesses if the requested version of a distribution is in
640+
// the correct format.
641+
//
642+
// If distro is an empty string, a default value (value under the
643+
// 'general.distro' key in a config file or 'fedora') is assumed.
601644
func ParseRelease(distro, release string) (string, error) {
602645
if distro == "" {
603646
distro = distroDefault
@@ -712,6 +755,33 @@ func ResolveContainerName(container, image, release string) (string, error) {
712755
return container, nil
713756
}
714757

758+
// ResolveDistro assess if the requested distribution is supported and provides
759+
// a default value if none is requested.
760+
//
761+
// If distro is empty, and the "general.distro" key in a config file is set,
762+
// the value is read from the config file. If the key is not set, the default
763+
// value ('fedora') is used instead.
764+
func ResolveDistro(distro string) (string, error) {
765+
logrus.Debug("Resolving distribution")
766+
logrus.Debugf("Distribution: %s", distro)
767+
768+
if distro == "" {
769+
distro = distroDefault
770+
if viper.IsSet("general.distro") {
771+
distro = viper.GetString("general.distro")
772+
}
773+
}
774+
775+
if !IsDistroSupported(distro) {
776+
return "", ErrUnsupportedDistro
777+
}
778+
779+
logrus.Debug("Resolved distribution")
780+
logrus.Debugf("Distribution: %s", distro)
781+
782+
return distro, nil
783+
}
784+
715785
// ResolveImageName standardizes the name of an image.
716786
//
717787
// If no image name is specified then the base image will reflect the platform of the host (even the version).

src/pkg/utils/utils_test.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,45 @@ import (
2020
"strconv"
2121
"testing"
2222

23+
"github.com/spf13/viper"
2324
"github.com/stretchr/testify/assert"
2425
)
2526

27+
func TestGetReleaseFormat(t *testing.T) {
28+
testCases := []struct {
29+
name string
30+
distro string
31+
expected string
32+
}{
33+
{
34+
"Unknown distro",
35+
"foobar",
36+
"",
37+
},
38+
{
39+
"Known distro (fedora)",
40+
"fedora",
41+
supportedDistros["fedora"].ReleaseFormat,
42+
},
43+
}
44+
45+
for _, tc := range testCases {
46+
t.Run(tc.name, func(t *testing.T) {
47+
res := GetReleaseFormat(tc.distro)
48+
assert.Equal(t, tc.expected, res)
49+
})
50+
}
51+
}
52+
53+
func TestGetSupportedDistros(t *testing.T) {
54+
refDistros := []string{"fedora", "rhel"}
55+
56+
distros := GetSupportedDistros()
57+
for _, d := range distros {
58+
assert.Contains(t, refDistros, d)
59+
}
60+
}
61+
2662
func TestImageReferenceCanBeID(t *testing.T) {
2763
testCases := []struct {
2864
name string
@@ -74,6 +110,92 @@ func TestImageReferenceCanBeID(t *testing.T) {
74110
}
75111
}
76112

113+
func TestIsDistroSupport(t *testing.T) {
114+
testCases := []struct {
115+
name string
116+
distro string
117+
ok bool
118+
}{
119+
{
120+
"Unsupported distro",
121+
"foobar",
122+
false,
123+
},
124+
{
125+
"Supported distro (fedora)",
126+
"fedora",
127+
true,
128+
},
129+
}
130+
131+
for _, tc := range testCases {
132+
t.Run(tc.name, func(t *testing.T) {
133+
res := IsDistroSupported(tc.distro)
134+
assert.Equal(t, tc.ok, res)
135+
})
136+
}
137+
}
138+
139+
func TestResolveDistro(t *testing.T) {
140+
testCases := []struct {
141+
name string
142+
distro string
143+
expected string
144+
configValue string
145+
err bool
146+
}{
147+
{
148+
"Default - no distro provided; config unset",
149+
"",
150+
distroDefault,
151+
"",
152+
false,
153+
},
154+
{
155+
"Default - no distro provided; config set",
156+
"",
157+
"rhel",
158+
"rhel",
159+
false,
160+
},
161+
{
162+
"Fedora",
163+
"fedora",
164+
"fedora",
165+
"",
166+
false,
167+
},
168+
{
169+
"RHEL",
170+
"rhel",
171+
"rhel",
172+
"",
173+
false,
174+
},
175+
{
176+
"FooBar; wrong distro",
177+
"foobar",
178+
"",
179+
"",
180+
true,
181+
},
182+
}
183+
184+
for _, tc := range testCases {
185+
t.Run(tc.name, func(t *testing.T) {
186+
if tc.configValue != "" {
187+
viper.Set("general.distro", tc.configValue)
188+
}
189+
190+
res, err := ResolveDistro(tc.distro)
191+
assert.Equal(t, tc.expected, res)
192+
if tc.err {
193+
assert.NotNil(t, err)
194+
}
195+
})
196+
}
197+
}
198+
77199
func TestParseRelease(t *testing.T) {
78200
testCases := []struct {
79201
name string

0 commit comments

Comments
 (0)