Skip to content

Commit 69d48f2

Browse files
authored
feat(internal/libarian): add update command to update sources in config (#3143)
Introduce `update` command that updates commit and SHA256 for `sources` to the latest. User runs `librarian update [--all | source]`, we iterate through all of the sources in librarian.yaml to get the latest commit and sha and write back to librarian.yaml. For #3068
1 parent 14e162f commit 69d48f2

File tree

5 files changed

+408
-0
lines changed

5 files changed

+408
-0
lines changed

internal/fetch/fetch.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ import (
3030
"time"
3131
)
3232

33+
const branch = "master"
34+
3335
var (
3436
errChecksumMismatch = errors.New("checksum mismatch")
3537
defaultBackoff = 10 * time.Second
@@ -114,6 +116,23 @@ func LatestSha(query string) (string, error) {
114116
return string(contents), nil
115117
}
116118

119+
// LatestCommitAndChecksum fetches the latest commit SHA and the SHA256 of the tarball for that
120+
// commit from the GitHub API for the given repository.
121+
func LatestCommitAndChecksum(endpoints *Endpoints, repo *Repo) (commit, sha256 string, err error) {
122+
apiURL := fmt.Sprintf("%s/repos/%s/%s/commits/%s", endpoints.API, repo.Org, repo.Repo, branch)
123+
commit, err = LatestSha(apiURL)
124+
if err != nil {
125+
return "", "", err
126+
}
127+
128+
tarballURL := TarballLink(endpoints.Download, repo, commit)
129+
sha256, err = Sha256(tarballURL)
130+
if err != nil {
131+
return "", "", err
132+
}
133+
return commit, sha256, nil
134+
}
135+
117136
// TarballLink constructs a GitHub tarball download URL for the given
118137
// repository and commit SHA.
119138
func TarballLink(githubDownload string, repo *Repo, sha string) string {

internal/fetch/fetch_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,58 @@ func TestDownloadTarballErrors(t *testing.T) {
529529
}
530530
}
531531

532+
func TestLatestCommitAndChecksum(t *testing.T) {
533+
const (
534+
expectedCommit = "testcommit123"
535+
expectedTarballContents = "mock tarball content for checksum"
536+
testOrg = "testorg"
537+
testRepo = "testrepo"
538+
)
539+
// Calculate the expected SHA256 for the tarball contents.
540+
hasher := sha256.New()
541+
hasher.Write([]byte(expectedTarballContents))
542+
expectedTarballSHA256 := fmt.Sprintf("%x", hasher.Sum(nil))
543+
544+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
545+
switch r.URL.Path {
546+
case fmt.Sprintf("/repos/%s/%s/commits/%s", testOrg, testRepo, branch):
547+
// Mock response for LatestSha call
548+
w.Header().Set("Accept", "application/vnd.github.VERSION.sha")
549+
w.WriteHeader(http.StatusOK)
550+
w.Write([]byte(expectedCommit))
551+
case fmt.Sprintf("/%s/%s/archive/%s.tar.gz", testOrg, testRepo, expectedCommit):
552+
// Mock response for Sha256 call (tarball download)
553+
w.WriteHeader(http.StatusOK)
554+
w.Write([]byte(expectedTarballContents))
555+
default:
556+
t.Errorf("unexpected request path: %s", r.URL.Path)
557+
http.NotFound(w, r)
558+
}
559+
}))
560+
defer server.Close()
561+
562+
endpoints := &Endpoints{
563+
API: server.URL,
564+
Download: server.URL,
565+
}
566+
repo := &Repo{
567+
Org: testOrg,
568+
Repo: testRepo,
569+
}
570+
571+
gotCommit, gotSha256, err := LatestCommitAndChecksum(endpoints, repo)
572+
if err != nil {
573+
t.Fatalf("LatestCommitAndChecksum() error = %v, wantErr %v", err, nil)
574+
}
575+
576+
if gotCommit != expectedCommit {
577+
t.Errorf("LatestCommitAndChecksum() gotCommit = %q, want %q", gotCommit, expectedCommit)
578+
}
579+
if gotSha256 != expectedTarballSHA256 {
580+
t.Errorf("LatestCommitAndChecksum() gotSha256 = %q, want %q", gotSha256, expectedTarballSHA256)
581+
}
582+
}
583+
532584
func TestDownloadTarballRetry(t *testing.T) {
533585
t.Run("succeeds after a few retries", func(t *testing.T) {
534586
// Set a short backoff for this test to speed up retries.

internal/librarian/librarian.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ func Run(ctx context.Context, args ...string) error {
3636
generateCommand(),
3737
releaseCommand(),
3838
tidyCommand(),
39+
updateCommand(),
3940
versionCommand(),
4041
},
4142
}

internal/librarian/update.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package librarian
16+
17+
import (
18+
"context"
19+
"fmt"
20+
"strings"
21+
22+
"github.com/googleapis/librarian/internal/config"
23+
"github.com/googleapis/librarian/internal/fetch"
24+
"github.com/googleapis/librarian/internal/yaml"
25+
"github.com/urfave/cli/v3"
26+
)
27+
28+
var (
29+
githubAPI = "https://api.github.com"
30+
githubDownload = "https://github.com"
31+
sourceRepos = map[string]*fetch.Repo{
32+
"googleapis": {Org: "googleapis", Repo: "googleapis"},
33+
"discovery": {Org: "googleapis", Repo: "discovery-artifact-manager"},
34+
}
35+
)
36+
37+
// updateCommand returns the `update` subcommand.
38+
func updateCommand() *cli.Command {
39+
return &cli.Command{
40+
Name: "update",
41+
Usage: "update sources to the latest version",
42+
UsageText: "librarian update [--all | source]",
43+
Flags: []cli.Flag{
44+
&cli.BoolFlag{
45+
Name: "all",
46+
Usage: "update all sources",
47+
},
48+
},
49+
Action: func(ctx context.Context, cmd *cli.Command) error {
50+
all := cmd.Bool("all")
51+
source := cmd.Args().First()
52+
53+
if all && source != "" {
54+
return fmt.Errorf("cannot specify a source when --all is set")
55+
}
56+
if !all && source == "" {
57+
return fmt.Errorf("a source must be specified, or use the --all flag")
58+
}
59+
return runUpdate(all, source)
60+
},
61+
}
62+
}
63+
64+
func runUpdate(all bool, sourceName string) error {
65+
cfg, err := yaml.Read[config.Config](librarianConfigPath)
66+
if err != nil {
67+
return err
68+
}
69+
if cfg.Sources == nil {
70+
return errEmptySources
71+
}
72+
73+
endpoints := &fetch.Endpoints{
74+
API: githubAPI,
75+
Download: githubDownload,
76+
}
77+
78+
sourcesMap := map[string]*config.Source{
79+
"googleapis": cfg.Sources.Googleapis,
80+
"discovery": cfg.Sources.Discovery,
81+
}
82+
83+
var sourceNamesToProcess []string
84+
if all {
85+
for name := range sourceRepos {
86+
sourceNamesToProcess = append(sourceNamesToProcess, name)
87+
}
88+
} else {
89+
lowerSourceName := strings.ToLower(sourceName)
90+
if _, ok := sourceRepos[lowerSourceName]; !ok {
91+
return fmt.Errorf("unknown source: %s", sourceName)
92+
}
93+
sourceNamesToProcess = []string{lowerSourceName}
94+
}
95+
96+
for _, name := range sourceNamesToProcess {
97+
source := sourcesMap[name]
98+
if err := updateSource(endpoints, name, source, cfg); err != nil {
99+
return err
100+
}
101+
}
102+
return nil
103+
}
104+
105+
func updateSource(endpoints *fetch.Endpoints, name string, source *config.Source, cfg *config.Config) error {
106+
if source == nil {
107+
return nil
108+
}
109+
110+
repo, ok := sourceRepos[name]
111+
if !ok {
112+
return fmt.Errorf("unknown source: %s", name)
113+
}
114+
115+
oldCommit := source.Commit
116+
oldSHA256 := source.SHA256
117+
118+
commit, sha256, err := fetch.LatestCommitAndChecksum(endpoints, repo)
119+
if err != nil {
120+
return err
121+
}
122+
123+
if oldCommit != commit || oldSHA256 != sha256 {
124+
source.Commit = commit
125+
source.SHA256 = sha256
126+
if err := yaml.Write(librarianConfigPath, cfg); err != nil {
127+
return err
128+
}
129+
}
130+
return nil
131+
}

0 commit comments

Comments
 (0)