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
12 changes: 4 additions & 8 deletions doc/container-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,7 @@ provided, but there is no absolute requirement to do so.
`build-library` builds and optionally runs unit tests for either a single library or all libraries
configured within the repository.

Called from CLI commands: `configure`, `generate`, `update-apis`, `update-image-tag`,
`create-release-artifacts`
Called from CLI commands: `configure`, `generate`, `update-apis`, `create-release-artifacts`

Flags:

Expand All @@ -109,10 +108,7 @@ should only be used for pulling dependencies - in the long term, we may restrict
access to only known hosts.

In most cases, the `--library-id` flag is specified, as the CLI performs most operations
on a library-by-library basis. However, the `update-image-tag` CLI command needs to build
all libraries as a validation step, and the current implementation calls the `build-library`
container command once without specifying `--library-id` as this is significantly more
efficient than calling it separately for each library.
on a library-by-library basis.

This command must not modify, add or delete any files within the repo root which are
considered relevant to the repo. In other words, running `git status` on the repo after
Expand Down Expand Up @@ -151,7 +147,7 @@ a good reason to keep the two commands separate.

`clean` removes all generated files related to a given configured library from within a repository.

Called from CLI commands: `configure`, `generate`, `update-apis`, `update-image-tag`
Called from CLI commands: `configure`, `generate`, `update-apis`

Flags:

Expand Down Expand Up @@ -202,7 +198,7 @@ The changes in `generator-input` after `configure` has completed are expected to
`generate-library` generates code for a single library, when provided with the full API specifications and the
language repository's `generator-input` directory.

Called from CLI commands: `configure`, `generate`, `update-apis`, `update-image-tag`
Called from CLI commands: `configure`, `generate`, `update-apis`

Flags:

Expand Down
22 changes: 4 additions & 18 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,6 @@ type Config struct {
// API Path is specified with the -api flag.
API string

// Branch is the branch name to use when working with git repositories. It is
// currently unused.
//
// Branch is specified with the -branch flag.
Branch string

// Build determines whether to build the generated library, and is only
// used in the generate command.
//
Expand Down Expand Up @@ -113,7 +107,7 @@ type Config struct {

// Push determines whether to push changes to GitHub. It is used in
// all commands that create commits in a language repository:
// configure, update-apis and update-image-tag.
// configure and update-apis.
// These commands all create pull requests if they
//
// By default (when Push isn't explicitly specified), commits are created in
Expand All @@ -131,7 +125,7 @@ type Config struct {
// in the format "email,name".
//
// PushConfig is used in all commands that create commits in a language repository:
// create-release-pr, configure, update-apis and update-image-tag.
// create-release-pr, configure and update-apis.
//
// PushConfig is optional. If unspecified, commits will use a default name of
// "Google Cloud SDK" and a default email of [email protected].
Expand Down Expand Up @@ -159,8 +153,7 @@ type Config struct {
// Librarian-created changes with other changes.
//
// Repo is used by all commands which operate on a language repository:
// configure, create-release-artifacts, generate, update-apis,
// update-image-tag.
// configure, create-release-artifacts, generate, update-apis.
//
// When a local directory is specified for the generate command, the repo is checked to
// determine whether the specified API path is configured as a library. See the generate
Expand All @@ -185,19 +178,12 @@ type Config struct {
// When this is not specified, the googleapis repository is cloned
// automatically.
//
// Source is used by generate, update-apis, update-image-tag and configure
// Source is used by generate, update-apis and configure
// commands.
//
// Source is specified with the -source flag.
Source string

// Tag is the new tag for the language-specific Docker image, used only by the
// update-image-tag command. All operations within update-image-tag are performed
// using the new tag.
//
// Tag is specified with the -tag flag.
Tag string

// UserGID is the group ID of the current user. It is used to run Docker
// containers with the same user, so that created files have the correct
// ownership.
Expand Down
33 changes: 0 additions & 33 deletions internal/librarian/clone.go

This file was deleted.

13 changes: 0 additions & 13 deletions internal/librarian/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@ func addFlagAPI(fs *flag.FlagSet, cfg *config.Config) {
fs.StringVar(&cfg.API, "api", "", "path to the API to be configured/generated (e.g., google/cloud/functions/v2)")
}

func addFlagBranch(fs *flag.FlagSet, cfg *config.Config) {
fs.StringVar(&cfg.Branch, "branch", "main", "repository branch")
}

func addFlagBuild(fs *flag.FlagSet, cfg *config.Config) {
fs.BoolVar(&cfg.Build, "build", false, "whether to build the generated code")
}
Expand All @@ -48,11 +44,6 @@ func addFlagProject(fs *flag.FlagSet, cfg *config.Config) {
fs.StringVar(&cfg.Project, "project", "", "Project containing Secret Manager secrets.")
}

func addFlagPushConfig(fs *flag.FlagSet, cfg *config.Config) {
// TODO(https://github.com/googleapis/librarian/issues/724):remove the default for push-config
fs.StringVar(&cfg.PushConfig, "push-config", "[email protected],Google Cloud SDK", "The user and email for Git commits, in the format \"user:email\"")
}

func addFlagReleaseID(fs *flag.FlagSet, cfg *config.Config) {
fs.StringVar(&cfg.ReleaseID, "release-id", "", "The ID of a release PR")
}
Expand All @@ -69,10 +60,6 @@ func addFlagSource(fs *flag.FlagSet, cfg *config.Config) {
fs.StringVar(&cfg.Source, "source", "", "location of googleapis repository. If undefined, googleapis will be cloned to the output")
}

func addFlagTag(fs *flag.FlagSet, cfg *config.Config) {
fs.StringVar(&cfg.Tag, "tag", "", "new tag for the language-specific container image.")
}

func addFlagWorkRoot(fs *flag.FlagSet, cfg *config.Config) {
fs.StringVar(&cfg.WorkRoot, "output", "", "Working directory root. When this is not specified, a working directory will be created in /tmp.")
}
Expand Down
1 change: 0 additions & 1 deletion internal/librarian/librarian.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ func init() {
CmdLibrarian.Init()
CmdLibrarian.Commands = append(CmdLibrarian.Commands,
cmdGenerate,
cmdUpdateImageTag,
cmdCreateReleaseArtifacts,
cmdVersion,
)
Expand Down
113 changes: 0 additions & 113 deletions internal/librarian/pullrequest.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,9 @@
package librarian

import (
"context"
"errors"
"fmt"
"log/slog"
"strings"
"time"

"github.com/googleapis/librarian/internal/config"
"github.com/googleapis/librarian/internal/github"
"github.com/googleapis/librarian/internal/gitrepo"
)

// A PullRequestContent builds up the content of a pull request.
Expand Down Expand Up @@ -54,82 +47,6 @@ func addSuccessToPullRequest(pr *PullRequestContent, text string) {
pr.Successes = append(pr.Successes, text)
}

// createPullRequest creates a GitHub pull request based on the given content,
// with a title prefix (e.g. "feat: API regeneration")
// using a branch with a name of the form "librarian-{branchtype}-{timestamp}".
//
// If content is empty, the pull request is not created and no error is
// returned.
//
// If content only contains errors, the pull request is not created and an
// error is returned (to highlight that everything failed)
// If content contains any successes, a pull request is created and no error is
// returned (if the creation is successful) even if the content includes
// errors.
//
// If the pull request would contain an excessive number of commits (as
// configured in pipeline-config.json).
func createPullRequest(ctx context.Context, content *PullRequestContent, titlePrefix, descriptionSuffix, branchType string, gitHubToken string, push bool, startTime time.Time, languageRepo *gitrepo.Repository, pipelineConfig *config.PipelineConfig) (*github.PullRequestMetadata, error) {
ghClient, err := github.NewClient(gitHubToken)
if err != nil {
return nil, err
}
anySuccesses := len(content.Successes) > 0
anyErrors := len(content.Errors) > 0

excessSuccesses := []string{}
if pipelineConfig != nil {
maxCommits := int(pipelineConfig.MaxPullRequestCommits)
if maxCommits > 0 && len(content.Successes) > maxCommits {
// We've got too many commits. Roll some back locally, and we'll add them to the description.
excessSuccesses = content.Successes[maxCommits:]
content.Successes = content.Successes[:maxCommits]
slog.Info("Excess commits created; winding back language repo", "excess_commits", len(excessSuccesses))
if err := languageRepo.CleanAndRevertCommits(len(excessSuccesses)); err != nil {
return nil, err
}
}
}

var description string
if !anySuccesses && !anyErrors {
slog.Info("No PR to create, and no errors.")
return nil, nil
} else if !anySuccesses && anyErrors {
slog.Error("No PR to create, but errors were logged (and restated below). Aborting.")
for _, error := range content.Errors {
slog.Error(error)
}
return nil, errors.New("errors encountered but no PR to create")
}

successesText := formatListAsMarkdown("Changes in this PR", content.Successes)
errorsText := formatListAsMarkdown("Errors", content.Errors)
excessText := formatListAsMarkdown("Excess changes not included", excessSuccesses)

description = strings.TrimSpace(successesText + errorsText + excessText + "\n" + descriptionSuffix)

title := fmt.Sprintf("%s: %s", titlePrefix, formatTimestamp(startTime))

if !push {
slog.Info("Push not specified; would have created PR", "title", title, "description", description)
return nil, nil
}

gitHubRepo, err := getGitHubRepoFromRemote(languageRepo)
if err != nil {
return nil, err
}

branch := fmt.Sprintf("librarian-%s-%s", branchType, formatTimestamp(startTime))
err = languageRepo.PushBranch(branch, ghClient.Token())
if err != nil {
slog.Info("Received error pushing branch", "err", err)
return nil, err
}
return ghClient.CreatePullRequest(ctx, gitHubRepo, branch, title, description)
}

// formatListAsMarkdown formats the given list as a single Markdown string, with a title preceding the list,
// a "- " at the start of each value and a line break at the end of each value.
// If the list is empty, an empty string is returned instead.
Expand All @@ -147,33 +64,3 @@ func formatListAsMarkdown(title string, list []string) string {
builder.WriteString("\n\n")
return builder.String()
}

// getGitHubRepoFromRemote parses the GitHub repo name from the remote for this repository.
// There must only be a single remote with a GitHub URL (as the first URL), in order to provide an
// unambiguous result.
// Remotes without any URLs, or where the first URL does not start with https://github.com/ are ignored.
func getGitHubRepoFromRemote(repo *gitrepo.Repository) (*github.Repository, error) {
remotes, err := repo.Remotes()
if err != nil {
return nil, err
}
gitHubRemoteNames := []string{}
gitHubUrl := ""
for _, remote := range remotes {
urls := remote.Config().URLs
if len(urls) > 0 && strings.HasPrefix(urls[0], "https://github.com/") {
gitHubRemoteNames = append(gitHubRemoteNames, remote.Config().Name)
gitHubUrl = urls[0]
}
}

if len(gitHubRemoteNames) == 0 {
return nil, fmt.Errorf("no GitHub remotes found")
}

if len(gitHubRemoteNames) > 1 {
joinedRemoteNames := strings.Join(gitHubRemoteNames, ", ")
return nil, fmt.Errorf("can only determine the GitHub repo with a single matching remote; GitHub remotes in repo: %s", joinedRemoteNames)
}
return github.ParseUrl(gitHubUrl)
}
20 changes: 0 additions & 20 deletions internal/librarian/stateandconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
package librarian

import (
"bytes"
"context"
"encoding/json"
"os"
Expand Down Expand Up @@ -65,25 +64,6 @@ func loadPipelineConfigFile(path string) (*config.PipelineConfig, error) {
return parsePipelineConfig(func() ([]byte, error) { return os.ReadFile(path) })
}

func savePipelineState(languageRepo *gitrepo.Repository, pipelineState *config.PipelineState) error {
path := filepath.Join(languageRepo.Dir, config.GeneratorInputDir, pipelineStateFile)
// Marshal the protobuf message as JSON...
unformatted, err := json.Marshal(pipelineState)
if err != nil {
return err
}
// ... then reformat it
var formatted bytes.Buffer
err = json.Indent(&formatted, unformatted, "", " ")
if err != nil {
return err
}
// The file mode is likely to be irrelevant, given that the permissions aren't changed
// if the file exists, which we expect it to anyway.
err = os.WriteFile(path, formatted.Bytes(), os.FileMode(0644))
return err
}

func fetchRemotePipelineState(ctx context.Context, repo *github.Repository, ref string, gitHubToken string) (*config.PipelineState, error) {
ghClient, err := github.NewClient(gitHubToken)
if err != nil {
Expand Down
Loading