Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
6 changes: 0 additions & 6 deletions internal/meta/base_meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -1223,9 +1223,3 @@ func appendToFile(path, content string) error {
return err
}

func resourceNamePattern(p string) (prefix, suffix string) {
if pos := strings.LastIndex(p, "*"); pos != -1 {
return p[:pos], p[pos+1:]
}
return p, ""
}
12 changes: 6 additions & 6 deletions internal/meta/meta_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ type MetaQuery struct {
baseMeta
argPredicate string
recursiveQuery bool
resourceNamePrefix string
resourceNameSuffix string
resourceNameExpander *nameExpander
includeRoleAssignment bool
includeManagedResource bool
includeResourceGroup bool
Expand All @@ -41,7 +40,7 @@ func NewMetaQuery(cfg config.Config) (*MetaQuery, error) {
argTable: cfg.ARGTable,
argAuthenticationScopeFilter: armresourcegraph.AuthorizationScopeFilter(cfg.ARGAuthorizationScopeFilter),
}
meta.resourceNamePrefix, meta.resourceNameSuffix = resourceNamePattern(cfg.ResourceNamePattern)
meta.resourceNameExpander = newNameExpander(cfg.ResourceNamePattern)

return meta, nil
}
Expand Down Expand Up @@ -75,17 +74,18 @@ func (meta *MetaQuery) ListResource(ctx context.Context) (ImportList, error) {
}

var l ImportList
for i, res := range rl {
for _, res := range rl {
name := meta.resourceNameExpander.Expand(res)
item := ImportItem{
AzureResourceID: res.AzureId,
TFResourceId: res.TFId,
TFAddr: tfaddr.TFAddr{
Type: "",
Name: fmt.Sprintf("%s%d%s", meta.resourceNamePrefix, i, meta.resourceNameSuffix),
Name: name,
},
TFAddrCache: tfaddr.TFAddr{
Type: "",
Name: fmt.Sprintf("%s%d%s", meta.resourceNamePrefix, i, meta.resourceNameSuffix),
Name: name,
},
}
if res.TFType != "" {
Expand Down
29 changes: 14 additions & 15 deletions internal/meta/meta_res.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,10 @@ import (

type MetaResource struct {
baseMeta
AzureIds []armid.ResourceId
ResourceName string
ResourceType string
resourceNamePrefix string
resourceNameSuffix string
AzureIds []armid.ResourceId
ResourceName string
ResourceType string
resourceNameExpander *nameExpander

includeRoleAssignment bool
includeManagedResource bool
Expand Down Expand Up @@ -54,7 +53,7 @@ func NewMetaResource(cfg config.Config) (*MetaResource, error) {
includeResourceGroup: cfg.IncludeResourceGroup,
}

meta.resourceNamePrefix, meta.resourceNameSuffix = resourceNamePattern(cfg.ResourceNamePattern)
meta.resourceNameExpander = newNameExpander(cfg.ResourceNamePattern)

return meta, nil
}
Expand Down Expand Up @@ -87,8 +86,8 @@ func (meta *MetaResource) ListResource(ctx context.Context) (ImportList, error)
tfl = rset.ToTFAzureRMResources(meta.Logger(), meta.parallelism, meta.azureSDKCred, meta.azureSDKClientOpt)
}

// Split the specified resources and the extension resources
var tfrl, tfel []resourceset.TFResource
// Split the specified resources and the property-liked/associated resources
var tfrl, tfpl []resourceset.TFResource
for _, tfres := range tfl {
rmap := map[string]bool{}
for _, r := range rl {
Expand All @@ -97,7 +96,7 @@ func (meta *MetaResource) ListResource(ctx context.Context) (ImportList, error)
if rmap[tfres.AzureId.String()] {
tfrl = append(tfrl, tfres)
} else {
tfel = append(tfel, tfres)
tfpl = append(tfpl, tfres)
}
}

Expand All @@ -110,7 +109,7 @@ func (meta *MetaResource) ListResource(ctx context.Context) (ImportList, error)
// Honor the ResourceName
name := meta.ResourceName
if name == "" {
name = fmt.Sprintf("%s%d%s", meta.resourceNamePrefix, 0, meta.resourceNameSuffix)
name = meta.resourceNameExpander.Expand(res)
}

// Honor the ResourceType
Expand Down Expand Up @@ -146,20 +145,20 @@ func (meta *MetaResource) ListResource(ctx context.Context) (ImportList, error)
}
l = append(l, item)
} else {
l = append(l, meta.toImportList(tfrl, 0)...)
l = append(l, meta.toImportList(tfrl)...)
}
l = append(l, meta.toImportList(tfel, len(tfrl))...)
l = append(l, meta.toImportList(tfpl)...)

l = meta.excludeImportList(l)
return l, nil
}

func (meta MetaResource) toImportList(rl []resourceset.TFResource, fromIdx int) ImportList {
func (meta MetaResource) toImportList(rl []resourceset.TFResource) ImportList {
var l ImportList
for idx, res := range rl {
for _, res := range rl {
tfAddr := tfaddr.TFAddr{
Type: "",
Name: fmt.Sprintf("%s%d%s", meta.resourceNamePrefix, idx+fromIdx, meta.resourceNameSuffix),
Name: meta.resourceNameExpander.Expand(res),
}
item := ImportItem{
AzureResourceID: res.AzureId,
Expand Down
9 changes: 4 additions & 5 deletions internal/meta/meta_rg.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ import (
type MetaResourceGroup struct {
baseMeta
resourceGroup string
resourceNamePrefix string
resourceNameSuffix string
resourceNameExpander *nameExpander
includeRoleAssignment bool
includeManagedResource bool
}
Expand All @@ -32,7 +31,7 @@ func NewMetaResourceGroup(cfg config.Config) (*MetaResourceGroup, error) {
includeRoleAssignment: cfg.IncludeRoleAssignment,
includeManagedResource: cfg.IncludeManagedResource,
}
meta.resourceNamePrefix, meta.resourceNameSuffix = resourceNamePattern(cfg.ResourceNamePattern)
meta.resourceNameExpander = newNameExpander(cfg.ResourceNamePattern)

return meta, nil
}
Expand Down Expand Up @@ -62,10 +61,10 @@ func (meta *MetaResourceGroup) ListResource(ctx context.Context) (ImportList, er
}

var l ImportList
for i, res := range rl {
for _, res := range rl {
tfAddr := tfaddr.TFAddr{
Type: "",
Name: fmt.Sprintf("%s%d%s", meta.resourceNamePrefix, i, meta.resourceNameSuffix),
Name: meta.resourceNameExpander.Expand(res),
}
item := ImportItem{
AzureResourceID: res.AzureId,
Expand Down
165 changes: 165 additions & 0 deletions internal/meta/name_pattern.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package meta

import (
"fmt"
"strings"
"unicode"

"github.com/Azure/aztfexport/internal/resourceset"
"github.com/magodo/armid"
)

const (
phType = "{type}" // last Azure resource type segment, e.g. "virtual_machines"
phRP = "{rp}" // Azure resource provider namespace, e.g. "microsoft_compute"
phName = "{name}" // last name segment of the Azure resource id
phRootScope = "{root_scope}" // last name of the root scope (e.g. resource group name)
)
Comment thread
magodo marked this conversation as resolved.

// nameExpander turns a resource name pattern (with placeholders and `*`) into
// concrete resource names. It is stateful: it tracks per-prefix counts so the
// indices produced via `*` are unique per expanded prefix/suffix pair.
type nameExpander struct {
pattern string
counts map[string]int
}

func newNameExpander(pattern string) *nameExpander {
return &nameExpander{pattern: pattern, counts: map[string]int{}}
}

// Expand returns the resource name produced by applying the pattern to the
// given TF resource.
func (e *nameExpander) Expand(res resourceset.TFResource) string {
expanded := expandPlaceholders(e.pattern, res)

var name string
if pos := strings.LastIndex(expanded, "*"); pos != -1 {
prefix, suffix := expanded[:pos], expanded[pos+1:]
key := prefix + "\x00" + suffix
idx := e.counts[key]
e.counts[key] = idx + 1
name = fmt.Sprintf("%s%d%s", prefix, idx, suffix)
} else {
idx := e.counts[expanded]
e.counts[expanded] = idx + 1
name = fmt.Sprintf("%s%d", expanded, idx)
}
return ensureValidTFName(name)
}

func expandPlaceholders(pattern string, res resourceset.TFResource) string {
id := res.AzureId

out := pattern
if strings.Contains(out, phType) {
out = strings.ReplaceAll(out, phType, snakeCase(lastSegment(id.Types())))
}
if strings.Contains(out, phRP) {
out = strings.ReplaceAll(out, phRP, snakeCase(id.Provider()))
}
if strings.Contains(out, phName) {
out = strings.ReplaceAll(out, phName, snakeCase(lastSegment(id.Names())))
}
if strings.Contains(out, phRootScope) {
out = strings.ReplaceAll(out, phRootScope, snakeCase(rootScopeName(id)))
}
return out
}

func lastSegment(segs []string) string {
if len(segs) == 0 {
return ""
}
return segs[len(segs)-1]
}

// rootScopeName returns a short, identifier-friendly representation of the
// root scope of the resource id (e.g. the resource group name, the
// subscription id, or the management group name).
func rootScopeName(id armid.ResourceId) string {
if id == nil {
return ""
}
root := id.RootScope()
if root == nil {
return ""
}
names := root.Names()
if len(names) == 0 {
return ""
}
return names[len(names)-1]
}

// snakeCase converts a string (potentially CamelCase / dotted / mixed) to a
// lowercase, underscore-separated identifier. Non-alphanumeric characters
// become underscores; runs of underscores are collapsed; leading/trailing
// underscores are trimmed.
func snakeCase(s string) string {
if s == "" {
return ""
}
var b strings.Builder
b.Grow(len(s) + 4)
runes := []rune(s)
for i, r := range runes {
switch {
case unicode.IsUpper(r):
// Insert `_` before an uppercase letter when:
// - it follows a lowercase / digit, or
// - it is followed by a lowercase letter and preceded by another uppercase
// (so that "HTTPServer" -> "http_server")
if i > 0 {
prev := runes[i-1]
switch {
case unicode.IsLower(prev) || unicode.IsDigit(prev):
b.WriteByte('_')
case unicode.IsUpper(prev) && i+1 < len(runes) && unicode.IsLower(runes[i+1]):
b.WriteByte('_')
}
}
b.WriteRune(unicode.ToLower(r))
case unicode.IsLower(r) || unicode.IsDigit(r):
b.WriteRune(r)
default:
b.WriteByte('_')
}
}
// Collapse runs of underscores and trim.
out := b.String()
for strings.Contains(out, "__") {
out = strings.ReplaceAll(out, "__", "_")
}
return strings.Trim(out, "_")
}

// ensureValidTFName makes sure the final name is a valid Terraform identifier.
// Terraform identifiers must start with a letter or underscore and may then
// contain letters, digits, underscores and dashes. We restrict ourselves to
// the conservative subset [A-Za-z0-9_].
func ensureValidTFName(s string) string {
if s == "" {
return "res"
}
var b strings.Builder
for _, r := range s {
switch {
case r >= 'a' && r <= 'z',
r >= 'A' && r <= 'Z',
r >= '0' && r <= '9',
r == '_', r == '-':
b.WriteRune(r)
default:
b.WriteByte('_')
}
}
out := b.String()
if out == "" {
return "res"
}
if c := out[0]; c >= '0' && c <= '9' {
out = "_" + out
}
return out
}
Loading
Loading