Skip to content

Commit d067bb5

Browse files
authored
reactor(sidekick): more testable UpdateRootConfig() (#2467)
1 parent 5e22b80 commit d067bb5

2 files changed

Lines changed: 506 additions & 37 deletions

File tree

internal/sidekick/internal/config/update_root_config.go

Lines changed: 113 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -25,75 +25,151 @@ import (
2525

2626
const (
2727
defaultGitHubApi = "https://api.github.com"
28-
defaultGitHub = "https://github.com"
29-
repo = "googleapis/googleapis"
28+
defaultGitHubDn = "https://github.com"
3029
branch = "master"
30+
defaultRoot = "googleapis"
3131
)
3232

33+
// githubEndpoints defines the endpoints used to access GitHub.
34+
type githubEndpoints struct {
35+
// Api defines the endpoint used to make API calls.
36+
Api string
37+
// Download defines the endpoint to download tarballs.
38+
Download string
39+
}
40+
41+
// githubRepo represents a GitHub repository name.
42+
type githubRepo struct {
43+
// Org defines the GitHub organization (or user), that owns the repository.
44+
Org string
45+
// Repo is the name of the repository, such as `googleapis` or `google-cloud-rust`.
46+
Repo string
47+
}
48+
3349
// UpdateRootConfig updates the root configuration file with the latest SHA from GitHub.
3450
func UpdateRootConfig(rootConfig *Config) error {
35-
gitHubApi, ok := rootConfig.Source["github-api"]
36-
if !ok {
37-
gitHubApi = defaultGitHubApi
38-
}
39-
gitHub, ok := rootConfig.Source["github"]
40-
if !ok {
41-
gitHub = defaultGitHub
51+
endpoints := githubConfig(rootConfig)
52+
repo, err := githubRepoFromTarballLink(rootConfig, defaultRoot)
53+
if err != nil {
54+
return err
4255
}
4356

44-
query := fmt.Sprintf("%s/repos/%s/commits/%s", gitHubApi, repo, branch)
57+
query := fmt.Sprintf("%s/repos/%s/%s/commits/%s", endpoints.Api, repo.Org, repo.Repo, branch)
4558
fmt.Printf("getting latest SHA from %q\n", query)
4659
latestSha, err := getLatestSha(query)
4760
if err != nil {
4861
return err
4962
}
5063

51-
newRoot := fmt.Sprintf("%s/%s/archive/%s.tar.gz", gitHub, repo, latestSha)
52-
fmt.Printf("computing SHA256 for %q\n", newRoot)
53-
newSha256, err := getSha256(newRoot)
64+
newLink := newTarballLink(endpoints, repo, latestSha)
65+
fmt.Printf("computing SHA256 for %q\n", newLink)
66+
newSha256, err := getSha256(newLink)
5467
if err != nil {
5568
return err
5669
}
57-
fmt.Printf("updating .sidekick.toml\n")
70+
fmt.Printf("updating %s\n", configName)
5871

59-
contents, err := os.ReadFile(".sidekick.toml")
72+
contents, err := os.ReadFile(configName)
6073
if err != nil {
6174
return err
6275
}
63-
var newContents []string
64-
for _, line := range strings.Split(string(contents), "\n") {
76+
newContents, err := updateRootConfigContents(defaultRoot, contents, endpoints, repo, latestSha, newSha256)
77+
if err != nil {
78+
return err
79+
}
80+
return os.WriteFile(configName, newContents, 0644)
81+
}
82+
83+
// githubConfig returns the API endpoint the browser endpoint for GitHub.
84+
// In tests, these are replaced with a fake.
85+
func githubConfig(rootConfig *Config) *githubEndpoints {
86+
api, ok := rootConfig.Source["github-api"]
87+
if !ok {
88+
api = defaultGitHubApi
89+
}
90+
download, ok := rootConfig.Source["github"]
91+
if !ok {
92+
download = defaultGitHubDn
93+
}
94+
return &githubEndpoints{
95+
Api: api,
96+
Download: download,
97+
}
98+
}
99+
100+
// githubRepoFromRoot extracts the gitHub account and repository (such as
101+
// `googleapis/googleapis`, or `googleapis/google-cloud-rust`) from the tarball
102+
// link.
103+
func githubRepoFromTarballLink(rootConfig *Config, rootName string) (*githubRepo, error) {
104+
config := githubConfig(rootConfig)
105+
root, ok := rootConfig.Source[fmt.Sprintf("%s-root", rootName)]
106+
if !ok {
107+
return nil, fmt.Errorf("missing %s root configuration", rootName)
108+
}
109+
urlPath := strings.TrimPrefix(root, config.Download)
110+
urlPath = strings.TrimPrefix(urlPath, "/")
111+
components := strings.Split(urlPath, "/")
112+
if len(components) < 2 {
113+
return nil, fmt.Errorf("url path for %s root configuration is missing components", rootName)
114+
}
115+
repo := &githubRepo{
116+
Org: components[0],
117+
Repo: components[1],
118+
}
119+
return repo, nil
120+
}
121+
122+
func newTarballLink(endpoints *githubEndpoints, repo *githubRepo, latestSha string) string {
123+
return fmt.Sprintf("%s/%s/%s/archive/%s.tar.gz", endpoints.Download, repo.Org, repo.Repo, latestSha)
124+
}
125+
126+
func updateRootConfigContents(rootName string, contents []byte, endpoints *githubEndpoints, repo *githubRepo, latestSha, newSha256 string) ([]byte, error) {
127+
newLink := newTarballLink(endpoints, repo, latestSha)
128+
129+
var output strings.Builder
130+
updatedRoot := 0
131+
updatedSha256 := 0
132+
updatedExtractedName := 0
133+
lines := strings.Split(string(contents), "\n")
134+
for idx, line := range lines {
65135
switch {
66-
case strings.HasPrefix(line, "googleapis-root "):
136+
case strings.HasPrefix(line, fmt.Sprintf("%s-root ", rootName)):
67137
s := strings.SplitN(line, "=", 2)
68138
if len(s) != 2 {
69-
return fmt.Errorf("invalid googleapis-root line, expected = separator, got=%q", line)
139+
return nil, fmt.Errorf("invalid %s-root line, expected = separator, got=%q", rootName, line)
70140
}
71-
newContents = append(newContents, fmt.Sprintf("%s= '%s'", s[0], newRoot))
72-
case strings.HasPrefix(line, "googleapis-sha256 "):
141+
fmt.Fprintf(&output, "%s= '%s'\n", s[0], newLink)
142+
updatedRoot += 1
143+
case strings.HasPrefix(line, fmt.Sprintf("%s-sha256 ", rootName)):
73144
s := strings.SplitN(line, "=", 2)
74145
if len(s) != 2 {
75-
return fmt.Errorf("invalid googleapis-sha256 line, expected = separator, got=%q", line)
146+
return nil, fmt.Errorf("invalid %s-sha256 line, expected = separator, got=%q", rootName, line)
76147
}
77-
newContents = append(newContents, fmt.Sprintf("%s= '%s'", s[0], newSha256))
148+
fmt.Fprintf(&output, "%s= '%s'\n", s[0], newSha256)
149+
updatedSha256 += 1
150+
case strings.HasPrefix(line, fmt.Sprintf("%s-extracted-name ", rootName)):
151+
s := strings.SplitN(line, "=", 2)
152+
if len(s) != 2 {
153+
return nil, fmt.Errorf("invalid %s-extracted-name line, expected = separator, got=%q", rootName, line)
154+
}
155+
fmt.Fprintf(&output, "%s= '%s-%s'\n", s[0], repo.Repo, latestSha)
156+
updatedExtractedName += 1
78157
default:
79-
newContents = append(newContents, line)
158+
if idx != len(lines)-1 {
159+
fmt.Fprintf(&output, "%s\n", line)
160+
} else {
161+
fmt.Fprintf(&output, "%s", line)
162+
}
80163
}
81164
}
82-
83-
cwd, _ := os.Getwd()
84-
fmt.Printf("%s\n", cwd)
85-
f, err := os.Create(".sidekick.toml")
86-
if err != nil {
87-
return err
165+
newContents := output.String()
166+
if updatedRoot == 0 && updatedSha256 == 0 {
167+
return []byte(newContents), nil
88168
}
89-
defer f.Close()
90-
for i, line := range newContents {
91-
f.Write([]byte(line))
92-
if i != len(newContents)-1 {
93-
f.Write([]byte("\n"))
94-
}
169+
if updatedRoot != 1 || updatedSha256 != 1 || updatedExtractedName > 1 {
170+
return nil, fmt.Errorf("too many changes to Root or Sha256 for %s", rootName)
95171
}
96-
return f.Close()
172+
return []byte(newContents), nil
97173
}
98174

99175
func getSha256(query string) (string, error) {

0 commit comments

Comments
 (0)