Skip to content

Commit

Permalink
feat: a few ARM template string functions
Browse files Browse the repository at this point in the history
  • Loading branch information
Craig Furman committed Jun 29, 2023
1 parent e04dd34 commit 4941d3b
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 7 deletions.
3 changes: 3 additions & 0 deletions changes/unreleased/Added-20230629-110316.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
kind: Added
body: Add support for a few ARM template string functions
time: 2023-06-29T11:03:16.471829+01:00
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ require (
github.com/spf13/cobra v1.6.1
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.0
github.com/vincent-petithory/dataurl v1.0.0
github.com/zclconf/go-cty v1.12.1
github.com/zclconf/go-cty-yaml v1.0.2
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,8 @@ github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BG
github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k=
github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/vincent-petithory/dataurl v1.0.0 h1:cXw+kPto8NLuJtlMsI152irrVw9fRDX8AbShPRpg2CI=
github.com/vincent-petithory/dataurl v1.0.0/go.mod h1:FHafX5vmDzyP+1CQATJn7WFKc9CvnvxyvZy6I1MrG/U=
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/vmihailenco/msgpack/v4 v4.3.12 h1:07s4sz9IReOgdikxLTKNbBdqDMLsjPKXwvCazn8G65U=
github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4=
Expand Down
13 changes: 9 additions & 4 deletions pkg/input/arm/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,15 @@ type Function func(e *EvaluationContext, args ...interface{}) (interface{}, erro

func BuiltinFunctions() map[string]Function {
return map[string]Function{
"concat": concatImpl,
"resourceGroup": resourceGroupImpl,
"resourceId": resourceIDImpl,
"variables": variablesImpl,
"base64": oneStringArg(base64Impl),
"base64ToString": oneStringArg(base64ToStringImpl),
"concat": concatImpl,
"dataUri": oneStringArg(dataURIImpl),
"dataUriToString": oneStringArg(dataURIToStringImpl),
"first": oneStringArg(firstImpl),
"resourceGroup": resourceGroupImpl,
"resourceId": resourceIDImpl,
"variables": variablesImpl,
}
}

Expand Down
67 changes: 64 additions & 3 deletions pkg/input/arm/functions_string.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,45 @@

package arm

import "fmt"
import (
"encoding/base64"
"fmt"

// Note that concat can operate on arrays too, we just haven't implemented
// support for this yet.
"github.com/vincent-petithory/dataurl"
)

// Functions from https://learn.microsoft.com/en-us/azure/azure-resource-manager/templates/template-functions-string#base64

func oneStringArg(f func(string) (interface{}, error)) Function {
return func(e *EvaluationContext, args ...interface{}) (interface{}, error) {
strargs, err := assertAllType[string](args...)
if err != nil {
return nil, err
}
if len(strargs) != 1 {
return nil, fmt.Errorf("expected 1 arg, got %d", len(strargs))
}
return f(strargs[0])
}
}

func base64Impl(arg string) (interface{}, error) {
return base64.StdEncoding.EncodeToString([]byte(arg)), nil
}

func base64ToStringImpl(arg string) (interface{}, error) {
decoded, err := base64.StdEncoding.DecodeString(arg)
if err != nil {
return nil, fmt.Errorf("error decoding base64: %w", err)
}
return string(decoded), nil
}

// TODO base64ToJson returns an object. We can implement this when we introduce
// object variable support.

// TODO concat can operate on arrays too, we just haven't implemented support
// for this yet.
func concatImpl(e *EvaluationContext, args ...interface{}) (interface{}, error) {
res := ""
for _, arg := range args {
Expand All @@ -29,3 +64,29 @@ func concatImpl(e *EvaluationContext, args ...interface{}) (interface{}, error)
}
return res, nil
}

// TODO contains can operate on arrays and objects, and returns a boolean. We
// haven't implemented support for these types yet.

func dataURIImpl(arg string) (interface{}, error) {
return dataurl.EncodeBytes([]byte(arg)), nil
}

func dataURIToStringImpl(arg string) (interface{}, error) {
decoded, err := dataurl.DecodeString(arg)
if err != nil {
return nil, fmt.Errorf("error decoding dataUri: %w", err)
}
return string(decoded.Data), nil
}

// TODO empty returns a boolean, a type we haven't implemented support for yet
// TODO endsWith returns a boolean, a type we haven't implemented support for yet

// TODO first can also operate on arrays, a type we haven't implemented support
// for yet
func firstImpl(arg string) (interface{}, error) {
return string([]rune(arg)[0]), nil
}

// TODO implement format after adding integer and boolean type support
62 changes: 62 additions & 0 deletions pkg/input/arm/functions_string_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// © 2022-2023 Snyk Limited All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package arm

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestBase64(t *testing.T) {
res, err := base64Impl("foo")
require.NoError(t, err)
require.Equal(t, "Zm9v", res)
}

func TestBase64ToString(t *testing.T) {
res, err := base64ToStringImpl("Zm9v")
require.NoError(t, err)
require.Equal(t, "foo", res)
}

func TestConcat(t *testing.T) {
res, err := concatImpl(nil, "foo", "bar")
require.NoError(t, err)
require.Equal(t, "foobar", res)
}

func TestDataURI(t *testing.T) {
res, err := dataURIImpl("Hello")
require.NoError(t, err)

// Behaves slightly differently than the example in https://learn.microsoft.com/en-us/azure/azure-resource-manager/templates/template-functions-string#TestDataURI
// Note the absence of a hyphen in "data:text/plain;charset=utf8;base64,SGVsbG8="
// It's not clear whether or not this is a problem, it depends on how tolerant
// of different representations of charset names Azure is.
require.Equal(t, "data:text/plain;charset=utf-8;base64,SGVsbG8=", res)
}

func TestDataURIToString(t *testing.T) {
res, err := dataURIToStringImpl("data:;base64,SGVsbG8sIFdvcmxkIQ==")
require.NoError(t, err)
require.Equal(t, "Hello, World!", res)
}

func TestFirst(t *testing.T) {
res, err := firstImpl("Hello")
require.NoError(t, err)
require.Equal(t, "H", res)
}

0 comments on commit 4941d3b

Please sign in to comment.