diff --git a/e2e/nomostest/git-server.go b/e2e/nomostest/git-server.go index 1ed2368f3b..382571a11f 100644 --- a/e2e/nomostest/git-server.go +++ b/e2e/nomostest/git-server.go @@ -23,6 +23,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" "kpt.dev/configsync/e2e" + "kpt.dev/configsync/e2e/nomostest/gitproviders" "kpt.dev/configsync/e2e/nomostest/testing" "kpt.dev/configsync/pkg/core" "kpt.dev/configsync/pkg/kinds" @@ -32,7 +33,8 @@ import ( const testGitNamespace = "config-management-system-test" const testGitServer = "test-git-server" -const testGitServerImage = testing.TestInfraArtifactRegistry + "/git-server:v1.0.0" + +const testGitServerImage = testing.TestInfraArtifactRegistry + "/git-server:v1.0.0-68df304c" const testGitHTTPServer = "http-git-server" const testGitHTTPServerImage = testing.TestInfraArtifactRegistry + "/http-git-server:v1.0.0-b3b4984cd" const safeToEvictAnnotation = "cluster-autoscaler.kubernetes.io/safe-to-evict" @@ -213,13 +215,19 @@ func InitGitRepos(nt *NT, repos ...types.NamespacedName) { podName := pod.Name for _, repo := range repos { - nt.MustKubectl("exec", "-n", testGitNamespace, podName, "-c", testGitServer, "--", - "git", "init", "--bare", "--shared", fmt.Sprintf("/git-server/repos/%s/%s", repo.Namespace, repo.Name)) + nt.MustKubectl("exec", "-n", testGitNamespace, podName, + "-c", testGitServer, "--", + "git", "init", "--bare", "--shared", + // Use `main` as the initial branch so that `HEAD` can reference refs/heads/main + "--initial-branch="+gitproviders.MainBranch, + fmt.Sprintf("/git-server/repos/%s/%s", repo.Namespace, repo.Name)) // We set receive.denyNonFastforwards to allow force pushes for legacy test support (bats). In the future we may // need this support for testing GKE clusters since we will likely be re-using the cluster in that case. // Alternatively, we could also run "rm -rf /git-server/repos/*" to clear out the state of the git server and // re-initialize. - nt.MustKubectl("exec", "-n", testGitNamespace, podName, "-c", testGitServer, "--", - "git", "-C", fmt.Sprintf("/git-server/repos/%s", repo), "config", "receive.denyNonFastforwards", "false") + nt.MustKubectl("exec", "-n", testGitNamespace, podName, + "-c", testGitServer, "--", + "git", "-C", fmt.Sprintf("/git-server/repos/%s", repo), + "config", "receive.denyNonFastforwards", "false") } } diff --git a/e2e/testcases/git_sync_test.go b/e2e/testcases/git_sync_test.go new file mode 100644 index 0000000000..c91fb684d0 --- /dev/null +++ b/e2e/testcases/git_sync_test.go @@ -0,0 +1,77 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package e2e + +import ( + "testing" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "kpt.dev/configsync/e2e/nomostest" + "kpt.dev/configsync/e2e/nomostest/gitproviders" + nomostesting "kpt.dev/configsync/e2e/nomostest/testing" + "kpt.dev/configsync/pkg/api/configmanagement" + "kpt.dev/configsync/pkg/api/configsync" + "kpt.dev/configsync/pkg/testing/fake" +) + +func TestMultipleRemoteBranchesOutOfSync(t *testing.T) { + nt := nomostest.New(t, nomostesting.ACMController) + + rs := fake.RootSyncObjectV1Beta1(configsync.RootSyncName) + if err := nt.KubeClient.Get(configsync.RootSyncName, configmanagement.ControllerNamespace, rs); err != nil { + nt.T.Fatal(err) + } + initialSyncedCommit := rs.Status.LastSyncedCommit + + nt.T.Log("Create an extra remote tracking branch") + nt.Must(nt.RootRepos[configsync.RootSyncName].Push("HEAD:refs/remotes/upstream/main")) + nt.T.Cleanup(func() { + // Delete the remote tracking branch in the end so other subsequent tests + // can pull from the latest commit, instead of the HEAD of the remote. + nt.Must(nt.RootRepos[configsync.RootSyncName].Push(":refs/remotes/upstream/main")) + }) + + nt.T.Logf("Update the remote main branch by adding a test namespace") + nt.Must(nt.RootRepos[configsync.RootSyncName].Add("acme/namespaces/hello/ns.yaml", fake.NamespaceObject("hello"))) + nt.Must(nt.RootRepos[configsync.RootSyncName].CommitAndPush("add Namespace")) + + nt.T.Logf("Mitigation: set spec.git.branch to HEAD to pull the latest commit") + nomostest.SetGitBranch(nt, configsync.RootSyncName, "HEAD") + // WatchForAllSyncs validates RootSync's lastSyncedCommit is updated to the + // local HEAD with the DefaultRootSha1Fn function. + if err := nt.WatchForAllSyncs(); err != nil { + nt.T.Fatal(err) + } + if err := nt.Validate("hello", "", &corev1.Namespace{}); err != nil { + nt.T.Fatal(err) + } + + // Apply the mitigation first to validate Config Sync couldn't pull the latest commit. + nt.T.Logf("Verify the issue exist with the default branch and revision") + nomostest.SetGitBranch(nt, configsync.RootSyncName, gitproviders.MainBranch) + if err := nt.WatchForAllSyncs(nomostest.WithRootSha1Func( + // DefaultRootSha1Fn returns the hash with `git rev-parse HEAD`, which is + // different from `git ls-remote ...` + // So, overwrite the root hash with the initial lastSyncedCommit. + func(_ *nomostest.NT, _ types.NamespacedName) (string, error) { + return initialSyncedCommit, nil + })); err != nil { + nt.T.Fatal(err) + } + if err := nt.ValidateNotFound("hello", "", &corev1.Namespace{}); err != nil { + nt.T.Fatal(err) + } +}