-
Notifications
You must be signed in to change notification settings - Fork 77
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix installflags change detection (#784)
* Fix installFlags change detection Signed-off-by: Kimmo Lehto <klehto@mirantis.com> * Add tests Signed-off-by: Kimmo Lehto <klehto@mirantis.com> --------- Signed-off-by: Kimmo Lehto <klehto@mirantis.com>
- Loading branch information
Showing
9 changed files
with
417 additions
and
164 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package shell | ||
|
||
// this is borrowed as-is from rig v2 until k0sctl is updated to use it | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
) | ||
|
||
// Split splits the input string respecting shell-like quoted segments. | ||
func Split(input string) ([]string, error) { //nolint:cyclop | ||
var segments []string | ||
|
||
currentSegment, ok := builderPool.Get().(*strings.Builder) | ||
if !ok { | ||
currentSegment = &strings.Builder{} | ||
} | ||
defer builderPool.Put(currentSegment) | ||
defer currentSegment.Reset() | ||
|
||
var inDoubleQuotes, inSingleQuotes, isEscaped bool | ||
|
||
for i := range len(input) { | ||
currentChar := input[i] | ||
|
||
if isEscaped { | ||
currentSegment.WriteByte(currentChar) | ||
isEscaped = false | ||
continue | ||
} | ||
|
||
switch { | ||
case currentChar == '\\' && !inSingleQuotes: | ||
isEscaped = true | ||
case currentChar == '"' && !inSingleQuotes: | ||
inDoubleQuotes = !inDoubleQuotes | ||
case currentChar == '\'' && !inDoubleQuotes: | ||
inSingleQuotes = !inSingleQuotes | ||
case currentChar == ' ' && !inDoubleQuotes && !inSingleQuotes: | ||
// Space outside quotes; delimiter for a new segment | ||
segments = append(segments, currentSegment.String()) | ||
currentSegment.Reset() | ||
default: | ||
currentSegment.WriteByte(currentChar) | ||
} | ||
} | ||
|
||
if inDoubleQuotes || inSingleQuotes { | ||
return nil, fmt.Errorf("split `%q`: %w", input, ErrMismatchedQuotes) | ||
} | ||
|
||
if isEscaped { | ||
return nil, fmt.Errorf("split `%q`: %w", input, ErrTrailingBackslash) | ||
} | ||
|
||
// Add the last segment if present | ||
if currentSegment.Len() > 0 { | ||
segments = append(segments, currentSegment.String()) | ||
} | ||
|
||
return segments, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package shell | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"strings" | ||
"sync" | ||
) | ||
|
||
// This is borrowed from rig v2 until k0sctl is updated to use it | ||
|
||
var ( | ||
builderPool = sync.Pool{ | ||
New: func() interface{} { | ||
return &strings.Builder{} | ||
}, | ||
} | ||
|
||
// ErrMismatchedQuotes is returned when the input string has mismatched quotes when unquoting. | ||
ErrMismatchedQuotes = errors.New("mismatched quotes") | ||
|
||
// ErrTrailingBackslash is returned when the input string ends with a trailing backslash. | ||
ErrTrailingBackslash = errors.New("trailing backslash") | ||
) | ||
|
||
// Unquote is a mostly POSIX compliant implementation of unquoting a string the same way a shell would. | ||
// Variables and command substitutions are not handled. | ||
func Unquote(input string) (string, error) { //nolint:cyclop | ||
sb, ok := builderPool.Get().(*strings.Builder) | ||
if !ok { | ||
sb = &strings.Builder{} | ||
} | ||
defer builderPool.Put(sb) | ||
defer sb.Reset() | ||
|
||
var inDoubleQuotes, inSingleQuotes, isEscaped bool | ||
|
||
for i := range len(input) { | ||
currentChar := input[i] | ||
|
||
if isEscaped { | ||
sb.WriteByte(currentChar) | ||
isEscaped = false | ||
continue | ||
} | ||
|
||
switch currentChar { | ||
case '\\': | ||
if !inSingleQuotes { // Escape works in double quotes or outside any quotes | ||
isEscaped = true | ||
} else { | ||
sb.WriteByte(currentChar) // Treat as a regular character within single quotes | ||
} | ||
case '"': | ||
if !inSingleQuotes { // Toggle double quotes only if not in single quotes | ||
inDoubleQuotes = !inDoubleQuotes | ||
} else { | ||
sb.WriteByte(currentChar) // Treat as a regular character within single quotes | ||
} | ||
case '\'': | ||
if !inDoubleQuotes { // Toggle single quotes only if not in double quotes | ||
inSingleQuotes = !inSingleQuotes | ||
} else { | ||
sb.WriteByte(currentChar) // Treat as a regular character within double quotes | ||
} | ||
default: | ||
sb.WriteByte(currentChar) | ||
} | ||
} | ||
|
||
if inDoubleQuotes || inSingleQuotes { | ||
return "", fmt.Errorf("unquote `%q`: %w", input, ErrMismatchedQuotes) | ||
} | ||
|
||
if isEscaped { | ||
return "", fmt.Errorf("unquote `%q`: %w", input, ErrTrailingBackslash) | ||
} | ||
|
||
return sb.String(), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package shell_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/k0sproject/k0sctl/internal/shell" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestUnquote(t *testing.T) { | ||
t.Run("no quotes", func(t *testing.T) { | ||
out, err := shell.Unquote("foo bar") | ||
require.NoError(t, err) | ||
require.Equal(t, "foo bar", out) | ||
}) | ||
|
||
t.Run("simple quotes", func(t *testing.T) { | ||
out, err := shell.Unquote("\"foo\" 'bar'") | ||
require.NoError(t, err) | ||
require.Equal(t, "foo bar", out) | ||
}) | ||
|
||
t.Run("mid-word quotes", func(t *testing.T) { | ||
out, err := shell.Unquote("f\"o\"o b'a'r") | ||
require.NoError(t, err) | ||
require.Equal(t, "foo bar", out) | ||
}) | ||
|
||
t.Run("complex quotes", func(t *testing.T) { | ||
out, err := shell.Unquote(`'"'"'foo'"'"'`) | ||
require.NoError(t, err) | ||
require.Equal(t, `"'foo'"`, out) | ||
}) | ||
|
||
t.Run("escaped quotes", func(t *testing.T) { | ||
out, err := shell.Unquote("\\'foo\\' 'bar'") | ||
require.NoError(t, err) | ||
require.Equal(t, "'foo' bar", out) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.