Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ jobs:
languages: ${{ matrix.language }}

- name: Set up Go
uses: actions/setup-go@v1
uses: actions/setup-go@v6
with:
go-version: 1.22
go-version: 1.25

- name: Build
run: |
Expand Down
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