Skip to content

Commit f5839d8

Browse files
authored
chore(internal/postprocessor): remove .repo-metadata-full.json processing from postprocessor main (googleapis#13129)
Add new manifest command to postprocessor for updating .repo-metadata-full.json
1 parent 57a2e80 commit f5839d8

File tree

3 files changed

+247
-9
lines changed

3 files changed

+247
-9
lines changed

internal/postprocessor/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,28 @@ To add a new module, add the directory name of the module to `modules` in
8282
`google-cloud-go/internal/postprocessor/config.yaml`. Please maintain
8383
alphabetical ordering of the module names.
8484

85+
## Updating the Repo Metadata Manifest
86+
87+
The `manifest` command regenerates the `internal/.repo-metadata-full.json` file.
88+
This file contains metadata about all of the modules in the repository.
89+
90+
There are two modes of operation:
91+
1. **Full Regeneration (default):** This mode regenerates the entire file from scratch based on the existing `config.yaml` and `.OwlBot.yaml` files. It should be run anytime there are changes to those configuration files. To prevent accidental data loss, this mode automatically preserves existing entries for modules listed in the `skip-module-scan-paths` section of the configuration.
92+
2. **Targeted Update (`-modules` flag):** This mode allows you to add or refresh one or more specific modules without regenerating the entire file. This is useful for adding a newly-generated module to the manifest.
93+
94+
To run a full regeneration, from the **repository root**, run the following:
95+
96+
```
97+
go run ./internal/postprocessor manifest -googleapis-dir=$GOOGLEAPIS
98+
```
99+
100+
To add or refresh only the `accessapproval` and `asset` modules:
101+
```
102+
go run ./internal/postprocessor manifest -googleapis-dir=$GOOGLEAPIS -modules=accessapproval,asset
103+
```
104+
105+
Note: `$GOOGLEAPIS` should be an absolute path to a local clone of `googleapis/googleapis`.
106+
85107
## Validating your config changes
86108

87109
The `validate` command is run as a presubmit on changes to either the

internal/postprocessor/main.go

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ var (
6767
"build": true,
6868
"ci": true,
6969
}
70+
71+
manifestCmd *flag.FlagSet
72+
modulesFlag string
7073
)
7174

7275
var (
@@ -78,13 +81,20 @@ var (
7881
internalVersionTmpl string
7982
)
8083

84+
func init() {
85+
manifestCmd = flag.NewFlagSet("manifest", flag.ExitOnError)
86+
manifestCmd.StringVar(&modulesFlag, "modules", "", "Comma-separated list of module names to update. If empty, regenerates entire manifest based on OwlBot config files.")
87+
}
88+
8189
func main() {
8290
clientRoot := flag.String("client-root", "/workspace/google-cloud-go", "Path to clients.")
8391
googleapisDir := flag.String("googleapis-dir", "", "Path to googleapis/googleapis repo.")
8492
directories := flag.String("dirs", "", "Comma-separated list of module names to run (not paths).")
8593
branchOverride := flag.String("branch", "", "The branch that should be processed by this code")
8694
githubUsername := flag.String("gh-user", "googleapis", "GitHub username where repo lives.")
8795
prFilepath := flag.String("pr-file", "/workspace/new_pull_request_text.txt", "Path at which to write text file if changing PR title or body.")
96+
manifestCmd.StringVar(clientRoot, "client-root", "/workspace/google-cloud-go", "Path to clients.")
97+
manifestCmd.StringVar(googleapisDir, "googleapis-dir", "", "Path to googleapis/googleapis repo.")
8898

8999
if len(os.Args) > 1 {
90100
switch os.Args[1] {
@@ -95,6 +105,36 @@ func main() {
95105
}
96106
log.Println("Validation complete.")
97107
return
108+
case "manifest":
109+
manifestCmd.Parse(os.Args[2:])
110+
log.Println("Starting manifest generation.")
111+
if *googleapisDir == "" {
112+
log.Println("creating temp dir")
113+
tmpDir, err := os.MkdirTemp("", "update-postprocessor")
114+
if err != nil {
115+
log.Fatal(err)
116+
}
117+
defer os.RemoveAll(tmpDir)
118+
119+
log.Printf("working out %s\n", tmpDir)
120+
*googleapisDir = filepath.Join(tmpDir, "googleapis")
121+
122+
if err := DeepClone("https://github.com/googleapis/googleapis", *googleapisDir); err != nil {
123+
log.Fatal(err)
124+
}
125+
}
126+
p := &postProcessor{
127+
googleapisDir: *googleapisDir,
128+
googleCloudDir: *clientRoot,
129+
}
130+
if err := p.loadConfig(); err != nil {
131+
log.Fatal(err)
132+
}
133+
if err := p.UpdateManifest(modulesFlag); err != nil {
134+
log.Fatal(err)
135+
}
136+
log.Println("Manifest generation complete.")
137+
return
98138
}
99139
}
100140
flag.Parse()
@@ -174,7 +214,7 @@ func (p *postProcessor) run(ctx context.Context) error {
174214
return nil
175215
}
176216

177-
manifest, err := p.Manifest()
217+
manifest, err := p.readManifest()
178218
if err != nil {
179219
return err
180220
}
@@ -228,6 +268,9 @@ func (p *postProcessor) InitializeNewModules(manifest map[string]ManifestEntry)
228268
}
229269
// serviceImportPath here should be a valid ImportPath from a MicrogenGapicConfigs
230270
apiName := manifest[serviceImportPath].Description
271+
if apiName == "" {
272+
return fmt.Errorf("no ManifestEntry.Description found for serviceImportPath %s. Cannot generate min required files", serviceImportPath)
273+
}
231274
if err := p.generateMinReqFilesNewMod(moduleName, modulePath, importPath, apiName); err != nil {
232275
return err
233276
}

internal/postprocessor/manifest.go

Lines changed: 181 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@ package main
1616

1717
import (
1818
"bufio"
19+
"bytes"
1920
"encoding/json"
2021
"fmt"
2122
"log"
2223
"os"
2324
"path/filepath"
25+
"sort"
2426
"strings"
2527

2628
"cloud.google.com/go/internal/postprocessor/execv/gocmd"
@@ -51,15 +53,167 @@ const (
5153
otherLibraryType libraryType = "OTHER"
5254
)
5355

54-
// Manifest writes a manifest file with info about all of the confs.
55-
func (p *postProcessor) Manifest() (map[string]ManifestEntry, error) {
56-
log.Println("updating gapic manifest")
57-
entries := map[string]ManifestEntry{} // Key is the package name.
58-
f, err := os.Create(filepath.Join(p.googleCloudDir, "internal", ".repo-metadata-full.json"))
56+
func (p *postProcessor) readManifest() (map[string]ManifestEntry, error) {
57+
log.Println("reading gapic manifest")
58+
// Read existing manifest to preserve entries from skipped modules.
59+
entries := map[string]ManifestEntry{}
60+
manifestPath := filepath.Join(p.googleCloudDir, "internal", ".repo-metadata-full.json")
61+
b, err := os.ReadFile(manifestPath)
5962
if err != nil {
6063
return nil, err
6164
}
65+
if err := json.Unmarshal(b, &entries); err != nil {
66+
return nil, err
67+
}
68+
return entries, nil
69+
}
70+
71+
// UpdateManifest regenerates or updates the `internal/.repo-metadata-full.json` file.
72+
// There are two modes of operation:
73+
// 1. **Full Regeneration (default):** This mode regenerates the entire file from scratch
74+
// based on the existing `config.yaml` and `.OwlBot.yaml` files. To prevent accidental
75+
// data loss, this mode automatically preserves existing entries for modules listed in
76+
// the `skip-module-scan-paths` section of the configuration.
77+
// 2. **Targeted Update (non-empty `modules` param):** This mode allows you to add or
78+
// refresh one or more specific modules without regenerating the entire file. This i
79+
// useful for adding a newly-generated module to the manifest.
80+
func (p *postProcessor) UpdateManifest(modules string) error {
81+
entries := make(map[string]ManifestEntry)
82+
manifestPath := filepath.Join(p.googleCloudDir, "internal", ".repo-metadata-full.json")
83+
if modules == "" {
84+
log.Println("updating gapic manifest")
85+
var err error
86+
entries, err = p.generateManifestEntries()
87+
if err != nil {
88+
return err
89+
}
90+
} else {
91+
log.Println("updating gapic manifest for", modules)
92+
b, err := os.ReadFile(manifestPath)
93+
if err != nil && !os.IsNotExist(err) {
94+
return err
95+
}
96+
if err == nil {
97+
if err := json.Unmarshal(b, &entries); err != nil {
98+
return err
99+
}
100+
}
101+
modList := strings.Split(modules, ",")
102+
for _, mod := range modList {
103+
modFound := false
104+
for inputDir, li := range p.config.GoogleapisToImportPath {
105+
if !strings.Contains(li.ImportPath, mod) {
106+
continue
107+
}
108+
if li.ServiceConfig == "" {
109+
continue
110+
}
111+
yamlPath := filepath.Join(p.googleapisDir, inputDir, li.ServiceConfig)
112+
yamlFile, err := os.Open(yamlPath)
113+
if err != nil {
114+
return err
115+
}
116+
yamlConfig := struct {
117+
Title string `yaml:"title"` // We only need the title and name.
118+
NameFull string `yaml:"name"` // We only need the title and name.
119+
}{}
120+
if err := yaml.NewDecoder(yamlFile).Decode(&yamlConfig); err != nil {
121+
return fmt.Errorf("decode: %v", err)
122+
}
123+
docURL, err := docURL(p.googleCloudDir, li.ImportPath, li.RelPath)
124+
if err != nil {
125+
return fmt.Errorf("unable to build docs URL: %v", err)
126+
}
127+
128+
releaseLevel, err := releaseLevel(p.googleCloudDir, li)
129+
if err != nil {
130+
return fmt.Errorf("unable to calculate release level for %v: %v", inputDir, err)
131+
}
132+
133+
apiShortname, err := apiShortname(yamlConfig.NameFull)
134+
if err != nil {
135+
return fmt.Errorf("unable to determine api_shortname from %v: %v", yamlConfig.NameFull, err)
136+
}
137+
138+
entry := ManifestEntry{
139+
APIShortname: apiShortname,
140+
DistributionName: li.ImportPath,
141+
Description: yamlConfig.Title,
142+
Language: "go",
143+
ClientLibraryType: "generated",
144+
ClientDocumentation: docURL,
145+
ReleaseLevel: releaseLevel,
146+
LibraryType: gapicAutoLibraryType,
147+
}
148+
entries[li.ImportPath] = entry
149+
modFound = true
150+
}
151+
if !modFound {
152+
return fmt.Errorf("configuration not found for %q", mod)
153+
}
154+
}
155+
}
156+
157+
// Sort and write the manifest file.
158+
keys := make([]string, 0, len(entries))
159+
for k := range entries {
160+
keys = append(keys, k)
161+
}
162+
sort.Strings(keys)
163+
164+
f, err := os.Create(manifestPath)
165+
if err != nil {
166+
return err
167+
}
62168
defer f.Close()
169+
170+
buf := &bytes.Buffer{}
171+
buf.WriteString("{\n")
172+
173+
for i, key := range keys {
174+
entry := entries[key]
175+
// The prefix for MarshalIndent should be " " to indent the members of
176+
// the entry, and the indent should be " " for sub-members.
177+
entryBytes, err := json.MarshalIndent(entry, " ", " ")
178+
if err != nil {
179+
return err
180+
}
181+
182+
s := string(entryBytes)
183+
firstBrace := strings.Index(s, "{")
184+
// Write the key and then the marshaled entry starting from the brace.
185+
buf.WriteString(fmt.Sprintf(" %q: ", key))
186+
buf.WriteString(s[firstBrace:])
187+
188+
if i < len(keys)-1 {
189+
buf.WriteString(",")
190+
}
191+
buf.WriteString("\n")
192+
}
193+
194+
buf.WriteString("}\n")
195+
if _, err := f.Write(buf.Bytes()); err != nil {
196+
return err
197+
}
198+
return nil
199+
}
200+
201+
// generateManifestEntries gathers info about all of the confs.
202+
func (p *postProcessor) generateManifestEntries() (map[string]ManifestEntry, error) {
203+
// Read existing manifest to preserve entries from skipped modules.
204+
existingEntries := map[string]ManifestEntry{}
205+
manifestPath := filepath.Join(p.googleCloudDir, "internal", ".repo-metadata-full.json")
206+
b, err := os.ReadFile(manifestPath)
207+
if err != nil && !os.IsNotExist(err) {
208+
return nil, err
209+
}
210+
if err == nil {
211+
if err := json.Unmarshal(b, &existingEntries); err != nil {
212+
return nil, err
213+
}
214+
}
215+
216+
entries := map[string]ManifestEntry{} // Key is the package name.
63217
for _, manual := range p.config.ManualClientInfo {
64218
entries[manual.DistributionName] = *manual
65219
}
@@ -106,11 +260,30 @@ func (p *postProcessor) Manifest() (map[string]ManifestEntry, error) {
106260
}
107261
entries[li.ImportPath] = entry
108262
}
263+
264+
// Add back entries from skipped modules that were in the old manifest.
265+
for distName, entry := range existingEntries {
266+
isSkipped := false
267+
for _, skippedPath := range p.config.SkipModuleScanPaths {
268+
prefix := "cloud.google.com/go/" + skippedPath
269+
// Add a trailing slash to prefix to avoid matching substrings, e.g.
270+
// `.../go/a` matching `.../go/abc`. Except when the distName is
271+
// exactly the prefix without a slash.
272+
if strings.HasPrefix(distName, prefix+"/") || distName == prefix {
273+
isSkipped = true
274+
break
275+
}
276+
}
277+
if isSkipped {
278+
if _, ok := entries[distName]; !ok {
279+
entries[distName] = entry
280+
}
281+
}
282+
}
283+
109284
// Remove base module entry
110285
delete(entries, "")
111-
enc := json.NewEncoder(f)
112-
enc.SetIndent("", " ")
113-
return entries, enc.Encode(entries)
286+
return entries, nil
114287
}
115288

116289
// Name is of form secretmanager.googleapis.com api_shortname

0 commit comments

Comments
 (0)