Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add debug msg #2

Closed
wants to merge 2 commits into from
Closed
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
11 changes: 9 additions & 2 deletions api/v1beta1/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@ type PushSpec struct {
// Branch specifies that commits should be pushed to the branch
// named. The branch is created using `.spec.checkout.branch` as the
// starting point, if it doesn't already exist.
// +required
Branch string `json:"branch"`
// +optional
Branch string `json:"branch,omitempty"`

// Refspec specifies the Git Refspec to use for a push operation.
// It takes precedence over Branch, i.e. Branch is ignored
// if Refspec is non empty. For more details about Git Refspecs, see:
// https://git-scm.com/book/en/v2/Git-Internals-The-Refspec
// +optional
Refspec string `json:"refspec,omitempty"`
}
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,12 @@ spec:
to the branch named. The branch is created using `.spec.checkout.branch`
as the starting point, if it doesn't already exist.
type: string
required:
- branch
refspec:
description: 'Refspec specifies the Git Refspec to use for
a push operation. It takes precedence over Branch, i.e.
Branch is ignored if Refspec is non empty. For more details
about Git Refspecs, see: https://git-scm.com/book/en/v2/Git-Internals-The-Refspec'
type: string
type: object
required:
- commit
Expand Down
16 changes: 16 additions & 0 deletions docs/api/image-automation.md
Original file line number Diff line number Diff line change
Expand Up @@ -638,11 +638,27 @@ string
</em>
</td>
<td>
<em>(Optional)</em>
<p>Branch specifies that commits should be pushed to the branch
named. The branch is created using <code>.spec.checkout.branch</code> as the
starting point, if it doesn&rsquo;t already exist.</p>
</td>
</tr>
<tr>
<td>
<code>refspec</code><br>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>Refspec specifies the Git Refspec to use for a push operation.
It takes precedence over Branch, i.e. Branch is ignored
if Refspec is non empty. For more details about Git Refspecs, see:
<a href="https://git-scm.com/book/en/v2/Git-Internals-The-Refspec">https://git-scm.com/book/en/v2/Git-Internals-The-Refspec</a></p>
</td>
</tr>
</tbody>
</table>
</div>
Expand Down
34 changes: 31 additions & 3 deletions docs/spec/v1beta1/imageupdateautomations.md
Original file line number Diff line number Diff line change
Expand Up @@ -406,8 +406,15 @@ type PushSpec struct {
// Branch specifies that commits should be pushed to the branch
// named. The branch is created using `.spec.checkout.branch` as the
// starting point, if it doesn't already exist.
// +required
Branch string `json:"branch"`
// +optional
Branch string `json:"branch,omitempty"`

// Refspec specifies the Git Refspec to use for a push operation.
// It takes precedence over Branch, i.e. Branch is ignored
// if Refspec is non empty. For more details about Git Refspecs, see:
// https://git-scm.com/book/en/v2/Git-Internals-The-Refspec
// +optional
Refspec string `json:"refspec,omitempty"`
}
```

Expand All @@ -416,7 +423,11 @@ pushed to the same branch at the origin. If `.spec.git.checkout` is not present,
to the branch given in the `GitRepository` referenced by `.spec.sourceRef`. If none of these yield a
branch name, the automation will fail.

When `push` is present, the `branch` field specifies a branch to push to at the origin. The branch
If `push.refspec` is present, the refspec specified is used to perform the push operation.
An example of a valid refspec is `refs/heads/branch:refs/heads/branch`. This allows users to push to an
arbitary destination reference.

If `push.branch` is present, the specified branch is pushed to at the origin. The branch
will be created locally if it does not already exist, starting from the checkout branch. If it does
already exist, it will be overwritten with the cloned version plus the changes made by the
controller. Alternatively, force push can be disabled by starting the controller with `--feature-gates=GitForcePushBranch=false`,
Expand All @@ -425,6 +436,8 @@ Note that without force push in push branches, if the target branch is stale, th
be able to conclude the operation and will consistently fail until the branch is either deleted or
refreshed.

**Note:** If `push.refspec` is specified, then `push.branch` is ignored.

In the following snippet, updates will be pushed as commits to the branch `auto`, and when that
branch does not exist at the origin, it will be created locally starting from the branch `main`, and
pushed:
Expand All @@ -439,6 +452,21 @@ spec:
branch: auto
```

In the following snippet, updates and commits will be made on the `main` branch locally
and then pushed to the `qa/main` branch. Note that, the `auto` branch specified in
`spec.git.push.branch` is ignored.

```yaml
spec:
git:
checkout:
ref:
branch: main
push:
branch: auto
refspec: refs/heads/main:refs/heads/qa/main
```

## Update strategy

The `.spec.update` field specifies how to carry out updates on the git repository. There is one
Expand Down
156 changes: 98 additions & 58 deletions internal/controller/imageupdateautomation_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,30 +214,15 @@ func (r *ImageUpdateAutomationReconciler) Reconcile(ctx context.Context, req ctr
}

// validate the git spec and default any values needed later, before proceeding
var ref *sourcev1.GitRepositoryRef
var checkoutRef *sourcev1.GitRepositoryRef
if gitSpec.Checkout != nil {
ref = &gitSpec.Checkout.Reference
tracelog.Info("using git repository ref from .spec.git.checkout", "ref", ref)
checkoutRef = &gitSpec.Checkout.Reference
tracelog.Info("using git repository ref from .spec.git.checkout", "ref", checkoutRef)
} else if r := origin.Spec.Reference; r != nil {
ref = r
tracelog.Info("using git repository ref from GitRepository spec", "ref", ref)
checkoutRef = r
tracelog.Info("using git repository ref from GitRepository spec", "ref", checkoutRef)
} // else remain as `nil` and git.DefaultBranch will be used.

var pushBranch string
if gitSpec.Push != nil {
pushBranch = gitSpec.Push.Branch
tracelog.Info("using push branch from .spec.push.branch", "branch", pushBranch)
} else {
// Here's where it gets constrained. If there's no push branch
// given, then the checkout ref must include a branch, and
// that can be used.
if ref == nil || ref.Branch == "" {
return failWithError(fmt.Errorf("Push branch not given explicitly, and cannot be inferred from .spec.git.checkout.ref or GitRepository .spec.ref"))
}
pushBranch = ref.Branch
tracelog.Info("using push branch from $ref.branch", "branch", pushBranch)
}

tmp, err := os.MkdirTemp("", fmt.Sprintf("%s-%s", originName.Namespace, originName.Name))
if err != nil {
return failWithError(err)
Expand All @@ -248,42 +233,44 @@ func (r *ImageUpdateAutomationReconciler) Reconcile(ctx context.Context, req ctr
}
}()

debuglog.Info("attempting to clone git repository", "gitrepository", originName, "ref", ref, "working", tmp)

authOpts, err := r.getAuthOpts(ctx, &origin)
if err != nil {
return failWithError(err)
}

clientOpts := []gogit.ClientOption{gogit.WithDiskStorage()}
if authOpts.Transport == git.HTTP {
clientOpts = append(clientOpts, gogit.WithInsecureCredentialsOverHTTP())
var pushBranch string
var switchBranch bool
if gitSpec.Push != nil {
// We only need to switch branches when a branch has been specified in
// the push spec and a refspec has not. Furthermore, the branch needs to
// be different than the one in the checkout ref.
if gitSpec.Push.Branch != "" && gitSpec.Push.Branch != checkoutRef.Branch && gitSpec.Push.Refspec == "" {
pushBranch = gitSpec.Push.Branch
switchBranch = true
tracelog.Info("using push branch from .spec.push.branch", "branch", pushBranch)
}
} else {
// Here's where it gets constrained. If there's no push branch
// given, then the checkout ref must include a branch, and
// that can be used.
if checkoutRef == nil || checkoutRef.Branch == "" {
return failWithError(
fmt.Errorf("Push spec not provided, and cannot be inferred from .spec.git.checkout.ref or GitRepository .spec.ref"),
)
}
pushBranch = checkoutRef.Branch
tracelog.Info("using push branch from $ref.branch", "branch", pushBranch)
}

// If the push branch is different from the checkout ref, we need to
// have all the references downloaded at clone time, to ensure that
// SwitchBranch will have access to the target branch state. fluxcd/flux2#3384
//
// To always overwrite the push branch, the feature gate
// GitAllBranchReferences can be set to false, which will cause
// the SwitchBranch operation to ignore the remote branch state.
allReferences := r.features[features.GitAllBranchReferences]
if pushBranch != ref.Branch {
clientOpts = append(clientOpts, gogit.WithSingleBranch(!allReferences))
}
debuglog.Info("attempting to clone git repository", "gitrepository", originName, "ref", checkoutRef, "working", tmp)

gitClient, err := gogit.NewClient(tmp, authOpts, clientOpts...)
gitClient, err := r.constructGitClient(ctx, &origin, tmp, switchBranch)
if err != nil {
return failWithError(err)
}
defer gitClient.Close()

opts := repository.CloneConfig{}
if ref != nil {
opts.Tag = ref.Tag
opts.SemVer = ref.SemVer
opts.Commit = ref.Commit
opts.Branch = ref.Branch
if checkoutRef != nil {
opts.Tag = checkoutRef.Tag
opts.SemVer = checkoutRef.SemVer
opts.Commit = checkoutRef.Commit
opts.Branch = checkoutRef.Branch
}

if enabled, _ := r.features[features.GitShallowClone]; enabled {
Expand All @@ -297,9 +284,9 @@ func (r *ImageUpdateAutomationReconciler) Reconcile(ctx context.Context, req ctr
return failWithError(err)
}

// When there's a push spec, the pushed-to branch is where commits
// When there's a push branch specified, the pushed-to branch is where commits
// shall be made
if gitSpec.Push != nil && !(ref != nil && ref.Branch == pushBranch) {
if switchBranch {
// Use the git operations timeout for the repo.
fetchCtx, cancel := context.WithTimeout(ctx, origin.Spec.Timeout.Duration)
defer cancel()
Expand Down Expand Up @@ -352,7 +339,6 @@ func (r *ImageUpdateAutomationReconciler) Reconcile(ctx context.Context, req ctr

debuglog.Info("ran updates to working dir", "working", tmp)

var statusMessage string
var signingEntity *openpgp.Entity
if gitSpec.Commit.SigningKey != nil {
if signingEntity, err = r.getSigningEntity(ctx, auto); err != nil {
Expand All @@ -361,10 +347,12 @@ func (r *ImageUpdateAutomationReconciler) Reconcile(ctx context.Context, req ctr
}

// construct the commit message from template and values
message, err := templateMsg(gitSpec.Commit.MessageTemplate, &templateValues)
template := gitSpec.Commit.MessageTemplate
message, err := templateMsg(template, &templateValues)
if err != nil {
return failWithError(err)
}
debuglog.Info("commit message templated", "template", template, "commitMessage", message)

var rev string
if len(templateValues.Updated.Files) > 0 {
Expand All @@ -386,6 +374,7 @@ func (r *ImageUpdateAutomationReconciler) Reconcile(ctx context.Context, req ctr
err = extgogit.ErrEmptyCommit
}

var statusMessage string
if err != nil {
if !errors.Is(err, git.ErrNoStagedFiles) && !errors.Is(err, extgogit.ErrEmptyCommit) {
return failWithError(err)
Expand All @@ -401,20 +390,39 @@ func (r *ImageUpdateAutomationReconciler) Reconcile(ctx context.Context, req ctr
// Use the git operations timeout for the repo.
pushCtx, cancel := context.WithTimeout(ctx, origin.Spec.Timeout.Duration)
defer cancel()
opts := repository.PushConfig{}
var pushConfig repository.PushConfig

// Use push refspec if provided.
if gitSpec.Push != nil && gitSpec.Push.Refspec != "" {
pushConfig.Refspecs = []string{gitSpec.Push.Refspec}
}

// If the force push feature flag is true and we are pushing to a
// different branch than the one we checked out to, then force push
// these changes.
forcePush := r.features[features.GitForcePushBranch]
if forcePush && pushBranch != ref.Branch {
opts.Force = true
if forcePush && switchBranch {
pushConfig.Force = true
}
if err := gitClient.Push(pushCtx, opts); err != nil {

if err := gitClient.Push(pushCtx, pushConfig); err != nil {
return failWithError(err)
}

r.event(ctx, auto, eventv1.EventSeverityInfo, fmt.Sprintf("Committed and pushed change %s to %s\n%s", rev, pushBranch, message))
log.Info("pushed commit to origin", "revision", rev, "branch", pushBranch)
if len(pushConfig.Refspecs) > 0 {
r.event(ctx, auto, eventv1.EventSeverityInfo,
fmt.Sprintf("Committed and pushed change %s using refspec %s\n%s", rev, gitSpec.Push.Refspec, message))
log.Info("pushed commit to origin", "revision", rev, "refspec", gitSpec.Push.Refspec)
statusMessage = "committed and pushed " + rev + " using refspec " + gitSpec.Push.Refspec
} else {
r.event(ctx, auto, eventv1.EventSeverityInfo,
fmt.Sprintf("Committed and pushed change %s to %s\n%s", rev, pushBranch, message))
log.Info("pushed commit to origin", "revision", rev, "branch", pushBranch)
statusMessage = "committed and pushed " + rev + " to " + pushBranch
}

auto.Status.LastPushCommit = rev
auto.Status.LastPushTime = &metav1.Time{Time: start}
statusMessage = "committed and pushed " + rev + " to " + pushBranch
}

// Getting to here is a successful run.
Expand Down Expand Up @@ -545,6 +553,38 @@ func (r *ImageUpdateAutomationReconciler) getAuthOpts(ctx context.Context, repos
return opts, nil
}

// constructGitClient constructs and returns a new gogit client.
func (r *ImageUpdateAutomationReconciler) constructGitClient(ctx context.Context,
origin *sourcev1.GitRepository, repoDir string, switchBranch bool) (*gogit.Client, error) {
authOpts, err := r.getAuthOpts(ctx, origin)
if err != nil {
return nil, err
}

clientOpts := []gogit.ClientOption{gogit.WithDiskStorage()}
if authOpts.Transport == git.HTTP {
clientOpts = append(clientOpts, gogit.WithInsecureCredentialsOverHTTP())
}

// If the push branch is different from the checkout ref, we need to
// have all the references downloaded at clone time, to ensure that
// SwitchBranch will have access to the target branch state. fluxcd/flux2#3384
//
// To always overwrite the push branch, the feature gate
// GitAllBranchReferences can be set to false, which will cause
// the SwitchBranch operation to ignore the remote branch state.
allReferences := r.features[features.GitAllBranchReferences]
if switchBranch {
clientOpts = append(clientOpts, gogit.WithSingleBranch(!allReferences))
}

gitClient, err := gogit.NewClient(repoDir, authOpts, clientOpts...)
if err != nil {
return nil, err
}
return gitClient, nil
}

// getSigningEntity retrieves an OpenPGP entity referenced by the
// provided imagev1.ImageUpdateAutomation for git commit signing
func (r *ImageUpdateAutomationReconciler) getSigningEntity(ctx context.Context, auto imagev1.ImageUpdateAutomation) (*openpgp.Entity, error) {
Expand Down
Loading