Skip to content

Commit 42fb617

Browse files
authored
phase 1 towards decoupling from filesystem ops: OverlayFS and retire api.CustomTemplate (#15881)
1 parent e2539cf commit 42fb617

25 files changed

+392
-184
lines changed

mmv1/api/resource.go

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
package api
1414

1515
import (
16-
"bytes"
1716
"fmt"
1817
"log"
1918
"maps"
@@ -22,9 +21,6 @@ import (
2221
"slices"
2322
"sort"
2423
"strings"
25-
"text/template"
26-
27-
"github.com/golang/glog"
2824

2925
"github.com/GoogleCloudPlatform/magic-modules/mmv1/api/product"
3026
"github.com/GoogleCloudPlatform/magic-modules/mmv1/api/resource"
@@ -1976,46 +1972,6 @@ func (r Resource) FormatDocDescription(desc string, indent bool) string {
19761972
return strings.TrimSuffix(returnString, "\n")
19771973
}
19781974

1979-
func (r Resource) CustomTemplate(templatePath string, appendNewline bool) string {
1980-
output := ExecuteTemplate(&r, templatePath, appendNewline)
1981-
if !appendNewline {
1982-
output = strings.TrimSuffix(output, "\n")
1983-
}
1984-
return output
1985-
}
1986-
1987-
func ExecuteTemplate(e any, templatePath string, appendNewline bool) string {
1988-
templates := []string{
1989-
templatePath,
1990-
"templates/terraform/expand_resource_ref.tmpl",
1991-
"templates/terraform/custom_flatten/bigquery_table_ref.go.tmpl",
1992-
"templates/terraform/flatten_property_method.go.tmpl",
1993-
"templates/terraform/expand_property_method.go.tmpl",
1994-
"templates/terraform/update_mask.go.tmpl",
1995-
"templates/terraform/nested_query.go.tmpl",
1996-
"templates/terraform/unordered_list_customize_diff.go.tmpl",
1997-
}
1998-
templateFileName := filepath.Base(templatePath)
1999-
2000-
tmpl, err := template.New(templateFileName).Funcs(google.TemplateFunctions).ParseFiles(templates...)
2001-
if err != nil {
2002-
glog.Exit(err)
2003-
}
2004-
2005-
contents := bytes.Buffer{}
2006-
if err = tmpl.ExecuteTemplate(&contents, templateFileName, e); err != nil {
2007-
glog.Exit(err)
2008-
}
2009-
2010-
rs := contents.String()
2011-
2012-
if !strings.HasSuffix(rs, "\n") && appendNewline {
2013-
rs = fmt.Sprintf("%s\n", rs)
2014-
}
2015-
2016-
return rs
2017-
}
2018-
20191975
// Returns the key of the list of resources in the List API response
20201976
// Used to get the list of resources to sweep
20211977
func (r Resource) ResourceListKey() string {

mmv1/api/type.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1374,10 +1374,6 @@ func (t Type) NamespaceProperty() string {
13741374
return fmt.Sprintf("%s%s%s", google.Camelize(t.ResourceMetadata.ProductMetadata.ApiName, "lower"), t.ResourceMetadata.Name, name)
13751375
}
13761376

1377-
func (t Type) CustomTemplate(templatePath string, appendNewline bool) string {
1378-
return ExecuteTemplate(&t, templatePath, appendNewline)
1379-
}
1380-
13811377
func (t *Type) GetIdFormat() string {
13821378
return t.ResourceMetadata.GetIdFormat()
13831379
}

mmv1/google/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ load("@rules_go//go:def.bzl", "go_library", "go_test")
33
go_library(
44
name = "google",
55
srcs = [
6+
"fs.go",
67
"slice_utils.go",
78
"string_utils.go",
89
"template_utils.go",
@@ -19,6 +20,7 @@ go_library(
1920
go_test(
2021
name = "google_test",
2122
srcs = [
23+
"fs_test.go",
2224
"slice_utils_test.go",
2325
"string_utils_test.go",
2426
],

mmv1/google/fs.go

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
package google
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"io"
7+
"io/fs"
8+
"os"
9+
)
10+
11+
// internal interface supporting ReadDirFS and ReadFileFS
12+
//
13+
// Most sensible FS implementations support these, as an example both `embed.FS`
14+
// and `os.DirFS(...)` implement these.
15+
type ReadDirReadFileFS interface {
16+
fs.ReadDirFS
17+
fs.ReadFileFS
18+
}
19+
20+
// overlayFS is an fs.FS implementation which supports the concept of overlays.
21+
//
22+
// Given two fs.FS, `overlay` and `base`, NewOverlayFS(overlay, base) builds an FS
23+
// which prioritizes files in `overlay` over files in `base`.
24+
//
25+
// As an example, given:
26+
// - overlay = {
27+
// "a/b": "foo",
28+
// "c": "c",
29+
// }
30+
// - base = {
31+
// "a/b": "bar",
32+
// "d": "d",
33+
// }
34+
//
35+
// Then:
36+
//
37+
// ofs.ReadFile("a/b") -> "foo"
38+
// ofs.ReadFile("d") -> "d"
39+
// ofs.ReadDir(".") -> {"a/b":"foo", "c":"c", "d":"d"}
40+
type overlayFS struct {
41+
overlay, base ReadDirReadFileFS
42+
}
43+
44+
func dirAsReadDirReadFileFS(path string) (ReadDirReadFileFS, error) {
45+
fsys, ok := os.DirFS(path).(ReadDirReadFileFS)
46+
if !ok {
47+
return nil, fmt.Errorf("Golang documentations claim that DirFS implements ReadDirFS and ReadFileFS")
48+
}
49+
return fsys, nil
50+
}
51+
52+
// NewOverlayFS create an overlay FS from two directories.
53+
// overlayDirectory may be empty to
54+
func NewOverlayFS(overlayDirectory, baseDirectory string) (ReadDirReadFileFS, error) {
55+
base, err := dirAsReadDirReadFileFS(baseDirectory)
56+
if err != nil {
57+
return nil, err
58+
}
59+
if overlayDirectory == "" {
60+
return base, nil
61+
}
62+
o, err := dirAsReadDirReadFileFS(overlayDirectory)
63+
if err != nil {
64+
return nil, err
65+
}
66+
return overlayFS{overlay: o, base: base}, nil
67+
}
68+
69+
// Open implements the main FS interface.
70+
func (o overlayFS) Open(name string) (fs.File, error) {
71+
f, err := o.overlay.Open(name)
72+
f2, err2 := o.base.Open(name)
73+
if err != nil {
74+
return f2, err2
75+
}
76+
if err2 != nil {
77+
return f, err
78+
}
79+
overlay, ok := f.(fs.ReadDirFile)
80+
if !ok {
81+
// not a directory, we can return the unmerged overlay file
82+
return f, err
83+
}
84+
base, ok := f2.(fs.ReadDirFile)
85+
if !ok {
86+
// inconsistency between the two FSes, surprising.
87+
return nil, fmt.Errorf("Open(%q)'s base did not return ReadDirFile values", name)
88+
}
89+
// As a note, here we could have taken shortcuts and not implemented this:
90+
// implementations will realize that OverlayFS implements ReadDirFS and
91+
// call overylayfs.ReadDir(dir) instead of overlayfs.Open(dir)+d.ReadDir(int).
92+
//
93+
// That being said we do so to be a compliant FS and pass the fstest.TestFS
94+
// test battery.
95+
return &overlayDirFile{overlay: overlay, base: base}, nil
96+
}
97+
98+
// ReadFile implements the ReadFileFS interface.
99+
func (o overlayFS) ReadFile(name string) ([]byte, error) {
100+
b, err := o.overlay.ReadFile(name)
101+
if err == nil {
102+
return b, nil
103+
}
104+
return o.base.ReadFile(name)
105+
}
106+
107+
// ReadDir implements the ReadDirFS interface.
108+
func (o overlayFS) ReadDir(name string) ([]fs.DirEntry, error) {
109+
a, err1 := o.overlay.ReadDir(name)
110+
b, err2 := o.base.ReadDir(name)
111+
return mergeReadDirs(a, b, err1, err2)
112+
}
113+
114+
func mergeReadDirs(overlay, base []fs.DirEntry, errOverlay, errBase error) ([]fs.DirEntry, error) {
115+
if errOverlay != nil {
116+
// No need to merge (and handle both fs errors case).
117+
return base, errBase
118+
}
119+
var merged []fs.DirEntry
120+
seen := make(map[string]bool)
121+
for _, e := range overlay {
122+
seen[e.Name()] = true
123+
merged = append(merged, e)
124+
}
125+
for _, e := range base {
126+
if _, ok := seen[e.Name()]; !ok {
127+
merged = append(merged, e)
128+
}
129+
}
130+
return merged, nil
131+
}
132+
133+
// ReadDirFile implementation when both overlay and base have an existing such
134+
// directory.
135+
type overlayDirFile struct {
136+
overlay, base fs.ReadDirFile
137+
initialized bool
138+
entries []fs.DirEntry
139+
offset int
140+
}
141+
142+
func (f *overlayDirFile) Stat() (fs.FileInfo, error) {
143+
return f.overlay.Stat()
144+
}
145+
146+
func (f *overlayDirFile) Read(b []byte) (int, error) {
147+
// Will be an error: one can't read directories.
148+
return f.overlay.Read(b)
149+
}
150+
151+
func (f *overlayDirFile) Close() error {
152+
err := f.overlay.Close()
153+
err2 := f.base.Close()
154+
return errors.Join(err, err2)
155+
}
156+
157+
func (f *overlayDirFile) ReadDir(count int) ([]fs.DirEntry, error) {
158+
if !f.initialized {
159+
a, err1 := f.overlay.ReadDir(-1)
160+
b, err2 := f.base.ReadDir(-1)
161+
if err1 != nil || err2 != nil {
162+
panic("unexpected error")
163+
}
164+
var err error
165+
f.entries, err = mergeReadDirs(a, b, err1, err2)
166+
if err != nil {
167+
panic("unexpected error")
168+
}
169+
f.initialized = true
170+
}
171+
n := len(f.entries) - f.offset
172+
if n == 0 {
173+
if count <= 0 {
174+
return nil, nil
175+
}
176+
return nil, io.EOF
177+
}
178+
if count > 0 && n > count {
179+
n = count
180+
}
181+
list := make([]fs.DirEntry, n)
182+
for i := range list {
183+
list[i] = f.entries[f.offset+i]
184+
}
185+
f.offset += n
186+
return list, nil
187+
}
188+
189+
// Verifying interface implementations
190+
var _ ReadDirReadFileFS = (*overlayFS)(nil)
191+
var _ fs.ReadDirFile = (*overlayDirFile)(nil)

mmv1/google/fs_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package google
2+
3+
import (
4+
"testing"
5+
"testing/fstest"
6+
)
7+
8+
func TestSimple(t *testing.T) {
9+
o := overlayFS{
10+
overlay: fstest.MapFS{
11+
"a/b": {Data: []byte("foo")},
12+
"c": {Data: []byte("c")},
13+
},
14+
base: fstest.MapFS{
15+
"a/b": {Data: []byte("bar")},
16+
"d": {Data: []byte("d")},
17+
"a/d": {Data: []byte("ad")},
18+
}}
19+
// TestFS checks for existence of files, but on top of it
20+
// runs fairly extensive validation tests on FS implementation.
21+
if err := fstest.TestFS(o, "a", "a/b", "c", "d", "a/d"); err != nil {
22+
t.Error(err)
23+
}
24+
contents, err := o.ReadFile("a/b")
25+
if err != nil {
26+
t.Fatal(err)
27+
}
28+
if string(contents) != "foo" {
29+
t.Errorf("Unexpected contents for a/b, wanted 'foo', got %q", contents)
30+
}
31+
}

0 commit comments

Comments
 (0)