mirror of https://github.com/fluxcd/flux2.git
Move tests to seperate repository
parent
90f0d81532
commit
271281e651
@ -0,0 +1,5 @@
|
||||
## Azure
|
||||
export TF_VAR_azure_devops_org=
|
||||
export TF_VAR_azuredevops_pat=
|
||||
export TF_VAR_azuredevops_id_rsa=
|
||||
export TF_VAR_azuredevops_id_rsa_pub=
|
@ -0,0 +1,9 @@
|
||||
GO_TEST_ARGS ?=
|
||||
PROVIDER_ARG ?=
|
||||
TEST_TIMEOUT ?= 60m
|
||||
|
||||
test:
|
||||
go test -timeout $(TEST_TIMEOUT) -v ./ $(GO_TEST_ARGS) $(PROVIDER_ARG) --tags=integration
|
||||
|
||||
test-azure:
|
||||
$(MAKE) test PROVIDER_ARG="-provider azure"
|
@ -0,0 +1,39 @@
|
||||
## Azure E2E
|
||||
|
||||
E2E tests for Azure are needed to mitigate introduction of new bugs in dependencies like libgit2. The goal is to verify that Flux integration with
|
||||
Azure services are actually working now and in the future.
|
||||
|
||||
## Architecture
|
||||
|
||||
The [aks](./terraform/aks) Terraform creates the AKS cluster and related resources to run the tests. It creates the AKS cluster, Azure DevOps
|
||||
repositories, Key Vault Key for Sops, Azure container registries and Azure EventHub. The resources should be created and destroyed before and after every test run.
|
||||
|
||||
## Requirements
|
||||
- Azure account with an active subscription to be able to create AKS and ACR, and permission to assign roles. Role assignment is required for allowing AKS workloads to access ACR.
|
||||
- Azure CLI, need to be logged in using az login.
|
||||
- Docker CLI for registry login.
|
||||
- kubectl for applying certain install manifests.
|
||||
|
||||
## Tests
|
||||
|
||||
Each test run is initiated by running `terraform apply` on the aks Terraform, it does this by using the [tftestenv package](https://github.com/fluxcd/test-infra/blob/main/tftestenv/testenv.go) within the `fluxcd/test-infra` repository.
|
||||
It then reads the output of the Terraform to get credentials and ssh keys, this means that a lot of the communication with the Azure API is offset to
|
||||
Terraform instead of requiring it to be implemented in the test.
|
||||
|
||||
The following tests are currently implemented:
|
||||
|
||||
- [x] Flux can be successfully installed on AKS using the CLI e.g.:
|
||||
- [x] source-controller can clone Azure DevOps repositories (https+ssh)
|
||||
- [x] image-reflector-controller can list tags from Azure Container Registry image repositories
|
||||
- [x] kustomize-controller can decrypt secrets using SOPS and Azure Key Vault
|
||||
- [x] image-automation-controller can create branches and push to Azure DevOps repositories (https+ssh)
|
||||
- [x] notification-controller can send commit status to Azure DevOps
|
||||
- [x] notification-controller can forward events to Azure Event Hub
|
||||
- [x] source-controller can pull charts from Azure Container Registry Helm repositories
|
||||
|
||||
## Running these tests locally
|
||||
|
||||
1. Copy `.env.sample` to `.env` and add the values for the different variables which includes. - your Azure DevOps org, personal access tokens and ssh keys for accessing repositories on Azure DevOps org.
|
||||
Run `source .env`.
|
||||
2. Run `make test-azure`
|
||||
|
@ -0,0 +1,348 @@
|
||||
/*
|
||||
Copyright 2022 The Flux authors
|
||||
|
||||
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 test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
eventhub "github.com/Azure/azure-event-hubs-go/v3"
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
"github.com/microsoft/azure-devops-go-api/azuredevops"
|
||||
"github.com/microsoft/azure-devops-go-api/azuredevops/git"
|
||||
. "github.com/onsi/gomega"
|
||||
giturls "github.com/whilp/git-urls"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
|
||||
notiv1beta1 "github.com/fluxcd/notification-controller/api/v1beta1"
|
||||
"github.com/fluxcd/pkg/runtime/events"
|
||||
)
|
||||
|
||||
func TestEventHubNotification(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
// Currently, only azuredevops is supported
|
||||
if infraOpts.Provider != "azure" {
|
||||
fmt.Printf("Skipping event notification for %s as it is not supported.\n", infraOpts.Provider)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.TODO()
|
||||
name := "event-hub"
|
||||
|
||||
// Start listening to eventhub with latest offset
|
||||
hub, err := eventhub.NewHubFromConnectionString(cfg.eventHubSas)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
c := make(chan string, 10)
|
||||
handler := func(ctx context.Context, event *eventhub.Event) error {
|
||||
c <- string(event.Data)
|
||||
return nil
|
||||
}
|
||||
runtimeInfo, err := hub.GetRuntimeInformation(ctx)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(len(runtimeInfo.PartitionIDs)).To(Equal(1))
|
||||
listenerHandler, err := hub.Receive(ctx, runtimeInfo.PartitionIDs[0], handler)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Setup Flux resources
|
||||
repoUrl := cfg.applicationRepository.http
|
||||
manifest, err := executeTemplate("testdata/cm.yaml", map[string]string{
|
||||
"ns": name,
|
||||
})
|
||||
|
||||
repo, err := cloneRepository(repoUrl, name, cfg.pat)
|
||||
repoDir := repo.Path()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
err = addFile(repoDir, "configmap.yaml", manifest)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
err = commitAndPushAll(ctx, repo, name, cfg.pat)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
err = setupNamespace(ctx, name, nsConfig{})
|
||||
secret := corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: name,
|
||||
},
|
||||
}
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, cfg.client, &secret, func() error {
|
||||
secret.StringData = map[string]string{
|
||||
"address": cfg.eventHubSas,
|
||||
}
|
||||
return nil
|
||||
})
|
||||
provider := notiv1beta1.Provider{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: name,
|
||||
},
|
||||
}
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, cfg.client, &provider, func() error {
|
||||
provider.Spec = notiv1beta1.ProviderSpec{
|
||||
Type: "azureeventhub",
|
||||
Address: repoUrl,
|
||||
SecretRef: &meta.LocalObjectReference{
|
||||
Name: name,
|
||||
},
|
||||
}
|
||||
return nil
|
||||
})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
alert := notiv1beta1.Alert{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: name,
|
||||
},
|
||||
}
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, cfg.client, &alert, func() error {
|
||||
alert.Spec = notiv1beta1.AlertSpec{
|
||||
ProviderRef: meta.LocalObjectReference{
|
||||
Name: provider.Name,
|
||||
},
|
||||
EventSources: []notiv1beta1.CrossNamespaceObjectReference{
|
||||
{
|
||||
Kind: "Kustomization",
|
||||
Name: name,
|
||||
Namespace: name,
|
||||
},
|
||||
},
|
||||
}
|
||||
return nil
|
||||
})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
modifyKsSpec := func(spec *kustomizev1.KustomizationSpec) {
|
||||
spec.HealthChecks = []meta.NamespacedObjectKindReference{
|
||||
{
|
||||
APIVersion: "v1",
|
||||
Kind: "ConfigMap",
|
||||
Name: "foobar",
|
||||
Namespace: name,
|
||||
},
|
||||
}
|
||||
}
|
||||
err = setupNamespace(ctx, name, nsConfig{
|
||||
repoURL: repoUrl,
|
||||
path: "./",
|
||||
modifyKsSpec: modifyKsSpec,
|
||||
})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Eventually(func() bool {
|
||||
err := verifyGitAndKustomization(ctx, cfg.client, name, name)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}, 60*time.Second, 5*time.Second)
|
||||
|
||||
// Wait to read even from event hub
|
||||
g.Eventually(func() bool {
|
||||
select {
|
||||
case eventJson := <-c:
|
||||
event := &events.Event{}
|
||||
err := json.Unmarshal([]byte(eventJson), event)
|
||||
if err != nil {
|
||||
t.Logf("the received event type does not match Flux format, error: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
if event.InvolvedObject.Kind == kustomizev1.KustomizationKind &&
|
||||
strings.Contains(event.Message, "Health check passed") {
|
||||
return true
|
||||
}
|
||||
|
||||
t.Logf("event received from '%s/%s': %s",
|
||||
event.InvolvedObject.Kind, event.InvolvedObject.Name, event.Message)
|
||||
return false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}, 60*time.Second, 1*time.Second)
|
||||
err = listenerHandler.Close(ctx)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
err = hub.Close(ctx)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
}
|
||||
|
||||
func TestAzureDevOpsCommitStatus(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
// Currently, only azuredevops is supported
|
||||
if infraOpts.Provider != "azure" {
|
||||
fmt.Printf("Skipping commit status test for %s as it is not supported.\n", infraOpts.Provider)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.TODO()
|
||||
name := "commit-status"
|
||||
repoUrl := cfg.applicationRepository.http
|
||||
manifest, err := executeTemplate("testdata/cm.yaml", map[string]string{
|
||||
"ns": name,
|
||||
})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
c, err := cloneRepository(repoUrl, name, cfg.pat)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
repoDir := c.Path()
|
||||
err = addFile(repoDir, "configmap.yaml", manifest)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
err = commitAndPushAll(ctx, c, name, cfg.pat)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
modifyKsSpec := func(spec *kustomizev1.KustomizationSpec) {
|
||||
spec.HealthChecks = []meta.NamespacedObjectKindReference{
|
||||
{
|
||||
APIVersion: "v1",
|
||||
Kind: "ConfigMap",
|
||||
Name: "foobar",
|
||||
Namespace: name,
|
||||
},
|
||||
}
|
||||
}
|
||||
err = setupNamespace(ctx, name, nsConfig{
|
||||
repoURL: repoUrl,
|
||||
path: "./",
|
||||
modifyKsSpec: modifyKsSpec,
|
||||
})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Eventually(func() bool {
|
||||
err := verifyGitAndKustomization(ctx, cfg.client, name, name)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}, 10*time.Second, 1*time.Second)
|
||||
|
||||
secret := corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "azuredevops-token",
|
||||
Namespace: name,
|
||||
},
|
||||
}
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, cfg.client, &secret, func() error {
|
||||
secret.StringData = map[string]string{
|
||||
"token": cfg.pat,
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
provider := notiv1beta1.Provider{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "azuredevops",
|
||||
Namespace: name,
|
||||
},
|
||||
}
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, cfg.client, &provider, func() error {
|
||||
provider.Spec = notiv1beta1.ProviderSpec{
|
||||
Type: "azuredevops",
|
||||
Address: repoUrl,
|
||||
SecretRef: &meta.LocalObjectReference{
|
||||
Name: "azuredevops-token",
|
||||
},
|
||||
}
|
||||
return nil
|
||||
})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
alert := notiv1beta1.Alert{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "azuredevops",
|
||||
Namespace: name,
|
||||
},
|
||||
}
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, cfg.client, &alert, func() error {
|
||||
alert.Spec = notiv1beta1.AlertSpec{
|
||||
ProviderRef: meta.LocalObjectReference{
|
||||
Name: provider.Name,
|
||||
},
|
||||
EventSources: []notiv1beta1.CrossNamespaceObjectReference{
|
||||
{
|
||||
Kind: "Kustomization",
|
||||
Name: name,
|
||||
Namespace: name,
|
||||
},
|
||||
},
|
||||
}
|
||||
return nil
|
||||
})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
url, err := ParseGitAddress(repoUrl)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
rev, err := c.Head()
|
||||
connection := azuredevops.NewPatConnection(url.OrgURL, cfg.pat)
|
||||
client, err := git.NewClient(ctx, connection)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
getArgs := git.GetStatusesArgs{
|
||||
Project: &url.Project,
|
||||
RepositoryId: &url.Repo,
|
||||
CommitId: &rev,
|
||||
}
|
||||
g.Eventually(func() bool {
|
||||
statuses, err := client.GetStatuses(ctx, getArgs)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if len(*statuses) != 1 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}, 500*time.Second, 5*time.Second)
|
||||
}
|
||||
|
||||
type AzureDevOpsURL struct {
|
||||
OrgURL string
|
||||
Project string
|
||||
Repo string
|
||||
}
|
||||
|
||||
// TODO(somtochiama): move this into fluxcd/pkg and reuse in NC
|
||||
func ParseGitAddress(s string) (AzureDevOpsURL, error) {
|
||||
var args AzureDevOpsURL
|
||||
u, err := giturls.Parse(s)
|
||||
if err != nil {
|
||||
return args, nil
|
||||
}
|
||||
|
||||
scheme := u.Scheme
|
||||
if u.Scheme == "ssh" {
|
||||
scheme = "https"
|
||||
}
|
||||
|
||||
id := strings.TrimLeft(u.Path, "/")
|
||||
id = strings.TrimSuffix(id, ".git")
|
||||
|
||||
comp := strings.Split(id, "/")
|
||||
if len(comp) != 4 {
|
||||
return args, fmt.Errorf("invalid repository id %q", id)
|
||||
}
|
||||
|
||||
args = AzureDevOpsURL{
|
||||
OrgURL: fmt.Sprintf("%s://%s/%s", scheme, u.Host, comp[0]),
|
||||
Project: comp[1],
|
||||
Repo: comp[3],
|
||||
}
|
||||
|
||||
return args, nil
|
||||
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
/*
|
||||
Copyright 2021 The Flux authors
|
||||
|
||||
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 test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
tfjson "github.com/hashicorp/terraform-json"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
"github.com/fluxcd/test-infra/tftestenv"
|
||||
)
|
||||
|
||||
const (
|
||||
azureDevOpsKnownHosts = "ssh.dev.azure.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7Hr1oTWqNqOlzGJOfGJ4NakVyIzf1rXYd4d7wo6jBlkLvCA4odBlL0mDUyZ0/QUfTTqeu+tm22gOsv+VrVTMk6vwRU75gY/y9ut5Mb3bR5BV58dKXyq9A9UeB5Cakehn5Zgm6x1mKoVyf+FFn26iYqXJRgzIZZcZ5V6hrE0Qg39kZm4az48o0AUbf6Sp4SLdvnuMa2sVNwHBboS7EJkm57XQPVU3/QpyNLHbWDdzwtrlS+ez30S3AdYhLKEOxAG8weOnyrtLJAUen9mTkol8oII1edf7mWWbWVf0nBmly21+nZcmCTISQBtdcyPaEno7fFQMDD26/s0lfKob4Kw8H"
|
||||
)
|
||||
|
||||
// createKubeConfigAKS constructs kubeconfig for an AKS cluster from the
|
||||
// terraform state output at the given kubeconfig path.
|
||||
func createKubeConfigAKS(ctx context.Context, state map[string]*tfjson.StateOutput, kcPath string) error {
|
||||
kubeconfigYaml, ok := state["aks_kube_config"].Value.(string)
|
||||
if !ok || kubeconfigYaml == "" {
|
||||
return fmt.Errorf("failed to obtain kubeconfig from tf output")
|
||||
}
|
||||
return tftestenv.CreateKubeconfigAKS(ctx, kubeconfigYaml, kcPath)
|
||||
}
|
||||
|
||||
func createAzureSPSecret(ctx context.Context, state map[string]*tfjson.StateOutput) (map[client.Object]controllerutil.MutateFn, string, error) {
|
||||
objMap := make(map[client.Object]controllerutil.MutateFn)
|
||||
fluxAzureSp := state["flux_azure_sp"].Value.(map[string]interface{})
|
||||
|
||||
azureSp := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "azure-sp", Namespace: "flux-system"}}
|
||||
mutateFn := func() error {
|
||||
azureSp.StringData = map[string]string{
|
||||
// "AZURE_TENANT_ID": fluxAzureSp["tenant_id"].(string),
|
||||
"AZURE_CLIENT_ID": fluxAzureSp["client_id"].(string),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
objMap[azureSp] = mutateFn
|
||||
|
||||
tmpl := map[string]string{
|
||||
"secretName": azureSp.Name,
|
||||
}
|
||||
kustomizeYaml, err := executeTemplate("testdata/azure-kustomization.yaml", tmpl)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return objMap, kustomizeYaml, nil
|
||||
}
|
||||
|
||||
func getTestConfigAKS(ctx context.Context, outputs map[string]*tfjson.StateOutput) (*testConfig, error) {
|
||||
fluxAzureSp := outputs["flux_azure_sp"].Value.(map[string]interface{})
|
||||
|
||||
fleetInfraRepository := outputs["fleet_infra_repository"].Value.(map[string]interface{})
|
||||
applicationRepository := outputs["application_repository"].Value.(map[string]interface{})
|
||||
|
||||
acr := outputs["acr"].Value.(map[string]interface{})
|
||||
eventHubSas := outputs["event_hub_sas"].Value.(string)
|
||||
|
||||
sharedSopsId := outputs["sops_id"].Value.(string)
|
||||
|
||||
config := &testConfig{
|
||||
pat: outputs["shared_pat"].Value.(string),
|
||||
idRsa: outputs["shared_id_rsa"].Value.(string),
|
||||
idRsaPub: outputs["shared_id_rsa_pub"].Value.(string),
|
||||
knownHosts: azureDevOpsKnownHosts,
|
||||
fleetInfraRepository: repoConfig{
|
||||
http: fleetInfraRepository["http"].(string),
|
||||
ssh: fleetInfraRepository["ssh"].(string),
|
||||
},
|
||||
applicationRepository: repoConfig{
|
||||
http: applicationRepository["http"].(string),
|
||||
ssh: applicationRepository["ssh"].(string),
|
||||
},
|
||||
dockerCred: dockerCred{
|
||||
url: acr["url"].(string),
|
||||
username: acr["username"].(string),
|
||||
password: acr["password"].(string),
|
||||
},
|
||||
eventHubSas: eventHubSas,
|
||||
sopsArgs: fmt.Sprintf("--azure-kv %s", sharedSopsId),
|
||||
sopsSecretData: map[string]string{
|
||||
"sops.azure-kv": fmt.Sprintf(`clientId: %s`, fluxAzureSp["client_id"].(string)),
|
||||
},
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// registryLoginACR logs into the container/artifact registries using the
|
||||
// provider's CLI tools and returns a list of test repositories.
|
||||
func registryLoginACR(ctx context.Context, output map[string]*tfjson.StateOutput) (map[string]string, error) {
|
||||
// NOTE: ACR registry accept dynamic repository creation by just pushing a
|
||||
// new image with a new repository name.
|
||||
testRepos := map[string]string{}
|
||||
|
||||
acr := output["acr"].Value.(map[string]interface{})
|
||||
registryURL := acr["url"].(string)
|
||||
if err := tftestenv.RegistryLoginACR(ctx, registryURL); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
testRepos["acr"] = registryURL
|
||||
|
||||
return testRepos, nil
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
/*
|
||||
Copyright 2022 The Flux authors
|
||||
|
||||
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 test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||
"github.com/fluxcd/test-infra/tftestenv"
|
||||
)
|
||||
|
||||
func TestFluxInstallation(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
ctx := context.TODO()
|
||||
g.Eventually(func() bool {
|
||||
err := verifyGitAndKustomization(ctx, cfg.client, "flux-system", "flux-system")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}, 60*time.Second, 5*time.Second)
|
||||
}
|
||||
|
||||
func TestRepositoryCloning(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
branchName := "feature/branch"
|
||||
tagName := "v1"
|
||||
|
||||
g := NewWithT(t)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
refType string
|
||||
cloneType string
|
||||
}{
|
||||
{
|
||||
name: "https-feature-branch",
|
||||
refType: "branch",
|
||||
cloneType: "http",
|
||||
},
|
||||
{
|
||||
name: "https-v1",
|
||||
refType: "tag",
|
||||
cloneType: "http",
|
||||
},
|
||||
{
|
||||
name: "ssh-feature-branch",
|
||||
refType: "branch",
|
||||
cloneType: "ssh",
|
||||
},
|
||||
{
|
||||
name: "ssh-v1",
|
||||
refType: "tag",
|
||||
cloneType: "ssh",
|
||||
},
|
||||
}
|
||||
|
||||
t.Log("Creating application sources")
|
||||
repo, err := cloneRepository(cfg.applicationRepository.http, branchName, cfg.pat)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
repoDir := repo.Path()
|
||||
|
||||
for _, tt := range tests {
|
||||
manifest, err := executeTemplate("testdata/cm.yaml", map[string]string{
|
||||
"ns": tt.name,
|
||||
})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
err = tftestenv.RunCommand(ctx, repoDir, fmt.Sprintf("mkdir -p ./cloning-test/%s", tt.name), tftestenv.RunCommandOptions{})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
err = tftestenv.RunCommand(ctx, repoDir, fmt.Sprintf("echo '%s' > ./cloning-test/%s/configmap.yaml", manifest, tt.name), tftestenv.RunCommandOptions{})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
}
|
||||
err = commitAndPushAll(ctx, repo, branchName, cfg.pat)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
err = createTagAndPush(repo, branchName, tagName, cfg.pat)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
t.Log("Verifying application-gitops namespaces")
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ref := &sourcev1.GitRepositoryRef{
|
||||
Branch: branchName,
|
||||
}
|
||||
if tt.refType == "tag" {
|
||||
ref = &sourcev1.GitRepositoryRef{
|
||||
Tag: tagName,
|
||||
}
|
||||
}
|
||||
|
||||
url := cfg.applicationRepository.http
|
||||
if tt.cloneType == "ssh" {
|
||||
url = cfg.applicationRepository.ssh
|
||||
}
|
||||
|
||||
err := setupNamespace(ctx, tt.name, nsConfig{
|
||||
repoURL: url,
|
||||
protocol: tt.cloneType,
|
||||
objectName: tt.name,
|
||||
path: fmt.Sprintf("./cloning-test/%s", tt.name),
|
||||
modifyGitSpec: func(spec *sourcev1.GitRepositorySpec) {
|
||||
spec.Reference = ref
|
||||
},
|
||||
})
|
||||
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Wait for configmap to be deployed
|
||||
g.Eventually(func() bool {
|
||||
err := verifyGitAndKustomization(ctx, cfg.client, tt.name, tt.name)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
nn := types.NamespacedName{Name: "foobar", Namespace: tt.name}
|
||||
cm := &corev1.ConfigMap{}
|
||||
err = cfg.client.Get(ctx, nn, cm)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}, 120*time.Second, 5*time.Second)
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,152 @@
|
||||
module github.com/fluxcd/flux2/tests/azure
|
||||
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/Azure/azure-event-hubs-go/v3 v3.3.18
|
||||
github.com/fluxcd/helm-controller/api v0.24.0
|
||||
github.com/fluxcd/image-automation-controller/api v0.25.0
|
||||
github.com/fluxcd/image-reflector-controller/api v0.21.0
|
||||
github.com/fluxcd/kustomize-controller/api v0.28.0
|
||||
github.com/fluxcd/notification-controller/api v0.26.0
|
||||
github.com/fluxcd/pkg/apis/meta v0.15.0
|
||||
github.com/fluxcd/pkg/git v0.6.0
|
||||
github.com/fluxcd/pkg/git/libgit2 v0.1.0
|
||||
github.com/fluxcd/pkg/runtime v0.18.0
|
||||
github.com/fluxcd/source-controller/api v0.29.0
|
||||
github.com/libgit2/git2go/v33 v33.0.9
|
||||
github.com/microsoft/azure-devops-go-api/azuredevops v1.0.0-b5
|
||||
github.com/onsi/gomega v1.20.0
|
||||
github.com/whilp/git-urls v1.0.0
|
||||
go.uber.org/multierr v1.8.0
|
||||
k8s.io/api v0.25.0
|
||||
k8s.io/apimachinery v0.25.0
|
||||
k8s.io/client-go v0.25.0
|
||||
sigs.k8s.io/controller-runtime v0.12.3
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Masterminds/semver/v3 v3.1.1 // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895 // indirect
|
||||
github.com/cloudflare/circl v1.1.0 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
|
||||
github.com/fluxcd/pkg/gitutil v0.2.0 // indirect
|
||||
github.com/fluxcd/pkg/http/transport v0.0.1 // indirect
|
||||
github.com/fluxcd/pkg/ssh v0.6.0 // indirect
|
||||
github.com/fluxcd/pkg/version v0.2.0 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.3.1 // indirect
|
||||
github.com/hashicorp/hc-install v0.3.2 // indirect
|
||||
github.com/hashicorp/terraform-exec v0.16.1 // indirect
|
||||
github.com/klauspost/compress v1.15.8 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
|
||||
)
|
||||
|
||||
// Fix CVE-2022-28948
|
||||
replace gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.1
|
||||
|
||||
require (
|
||||
github.com/Azure/azure-amqp-common-go/v3 v3.2.3 // indirect
|
||||
github.com/Azure/azure-sdk-for-go v51.1.0+incompatible // indirect
|
||||
github.com/Azure/go-amqp v0.17.0 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.27 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.20 // indirect
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
|
||||
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
|
||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.12.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/devigned/tab v0.1.1 // indirect
|
||||
github.com/docker/cli v20.10.17+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||
github.com/docker/docker v20.10.17+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.6.4 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.8.0 // indirect
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
|
||||
github.com/fluxcd/pkg/apis/acl v0.1.0 // indirect
|
||||
github.com/fluxcd/pkg/apis/kustomize v0.5.0 // indirect
|
||||
github.com/fluxcd/test-infra/tftestenv v0.0.0-20220726140458-65e1a901cbb9
|
||||
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-logr/zapr v1.2.3 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.5 // indirect
|
||||
github.com/go-openapi/swag v0.19.14 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/gnostic v0.5.7-v3refs // indirect
|
||||
github.com/google/go-cmp v0.5.8 // indirect
|
||||
github.com/google/go-containerregistry v0.11.0
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.1 // indirect
|
||||
github.com/hashicorp/go-version v1.6.0 // indirect
|
||||
github.com/hashicorp/terraform-json v0.14.0
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/jpillora/backoff v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/prometheus/client_golang v1.13.0 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect; indirectct
|
||||
github.com/vbatts/tar-split v0.11.2 // indirect
|
||||
github.com/zclconf/go-cty v1.11.0 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/zap v1.23.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220824171710-5757bc0c5503 // indirect
|
||||
golang.org/x/net v0.0.0-20220822230855-b0a4917ee28c // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20220718184931-c8730f7fcb92 // indirect
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.25.0 // indirect
|
||||
k8s.io/component-base v0.25.0 // indirect
|
||||
k8s.io/klog/v2 v2.70.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect
|
||||
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||
)
|
||||
|
||||
// Fix for CVE-2020-29652: https://github.com/golang/crypto/commit/8b5274cf687fd9316b4108863654cc57385531e8
|
||||
// Fix for CVE-2021-43565: https://github.com/golang/crypto/commit/5770296d904e90f15f38f77dfc2e43fdf5efc083
|
||||
replace golang.org/x/crypto => golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
|
||||
|
||||
replace github.com/fluxcd/test-infra/tftestenv => github.com/somtochiama/test-infra/tftestenv v0.0.0-20220902234215-0a583c0d3b76
|
||||
|
||||
// This lets us use `go-billy/util.Walk()`, as this function hasn't been released
|
||||
// in a tagged version yet:
|
||||
// https://github.com/go-git/go-billy/blob/e0768be422ff616fc042d1d62bfa65962f716ad8/util/walk.go#L59
|
||||
replace github.com/go-git/go-billy/v5 => github.com/go-git/go-billy/v5 v5.3.2-0.20210603175951-e0768be422ff
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,189 @@
|
||||
/*
|
||||
Copyright 2022 The Flux authors
|
||||
|
||||
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 test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
b64 "encoding/base64"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
automationv1beta1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
|
||||
reflectorv1beta1 "github.com/fluxcd/image-reflector-controller/api/v1beta1"
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||
)
|
||||
|
||||
func TestImageRepositoryAndAutomation(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
ctx := context.TODO()
|
||||
name := "image-repository-acr"
|
||||
repoUrl := cfg.applicationRepository.http
|
||||
oldVersion := "1.0.0"
|
||||
newVersion := "1.0.1"
|
||||
|
||||
imageURL := fmt.Sprintf("%s/container/podinfo", cfg.dockerCred.url)
|
||||
// push the podinfo image to the container registry
|
||||
err := pushImagesFromURL(imageURL, "ghcr.io/stefanprodan/podinfo", []string{oldVersion, newVersion})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
tmpl := map[string]string{
|
||||
"ns": name,
|
||||
"name": name,
|
||||
"version": oldVersion,
|
||||
"url": cfg.dockerCred.url,
|
||||
}
|
||||
manifest, err := executeTemplate("testdata/automation-deploy.yaml", tmpl)
|
||||
|
||||
c, err := cloneRepository(repoUrl, name, cfg.pat)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
repoDir := c.Path()
|
||||
err = addFile(repoDir, "podinfo.yaml", manifest)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
err = commitAndPushAll(ctx, c, name, cfg.pat)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
err = setupNamespace(ctx, name, nsConfig{
|
||||
repoURL: repoUrl,
|
||||
path: "./",
|
||||
})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
g.Eventually(func() bool {
|
||||
err := verifyGitAndKustomization(ctx, testEnv.Client, name, name)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}, 60*time.Second, 5*time.Second)
|
||||
|
||||
acrSecret := corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "acr-docker", Namespace: name}}
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, cfg.client, &acrSecret, func() error {
|
||||
acrSecret.Type = corev1.SecretTypeDockerConfigJson
|
||||
acrSecret.StringData = map[string]string{
|
||||
".dockerconfigjson": fmt.Sprintf(`
|
||||
{
|
||||
"auths": {
|
||||
"%s": {
|
||||
"auth": "%s"
|
||||
}
|
||||
}
|
||||
}
|
||||
`, cfg.dockerCred.url, b64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", cfg.dockerCred.username, cfg.dockerCred.password)))),
|
||||
}
|
||||
return nil
|
||||
})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
imageRepository := reflectorv1beta1.ImageRepository{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "podinfo",
|
||||
Namespace: name,
|
||||
},
|
||||
}
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, cfg.client, &imageRepository, func() error {
|
||||
imageRepository.Spec = reflectorv1beta1.ImageRepositorySpec{
|
||||
Image: fmt.Sprintf("%s/container/podinfo", cfg.dockerCred.url),
|
||||
Interval: metav1.Duration{
|
||||
Duration: 1 * time.Minute,
|
||||
},
|
||||
}
|
||||
return nil
|
||||
})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
imagePolicy := reflectorv1beta1.ImagePolicy{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "podinfo",
|
||||
Namespace: name,
|
||||
},
|
||||
}
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, cfg.client, &imagePolicy, func() error {
|
||||
imagePolicy.Spec = reflectorv1beta1.ImagePolicySpec{
|
||||
ImageRepositoryRef: meta.NamespacedObjectReference{
|
||||
Name: imageRepository.Name,
|
||||
},
|
||||
Policy: reflectorv1beta1.ImagePolicyChoice{
|
||||
SemVer: &reflectorv1beta1.SemVerPolicy{
|
||||
Range: "1.0.x",
|
||||
},
|
||||
},
|
||||
}
|
||||
return nil
|
||||
})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
imageAutomation := automationv1beta1.ImageUpdateAutomation{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "podinfo",
|
||||
Namespace: name,
|
||||
},
|
||||
}
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, cfg.client, &imageAutomation, func() error {
|
||||
imageAutomation.Spec = automationv1beta1.ImageUpdateAutomationSpec{
|
||||
Interval: metav1.Duration{
|
||||
Duration: 1 * time.Minute,
|
||||
},
|
||||
SourceRef: automationv1beta1.CrossNamespaceSourceReference{
|
||||
Kind: "GitRepository",
|
||||
Name: name,
|
||||
},
|
||||
GitSpec: &automationv1beta1.GitSpec{
|
||||
Checkout: &automationv1beta1.GitCheckoutSpec{
|
||||
Reference: sourcev1.GitRepositoryRef{
|
||||
Branch: name,
|
||||
},
|
||||
},
|
||||
Commit: automationv1beta1.CommitSpec{
|
||||
Author: automationv1beta1.CommitUser{
|
||||
Email: "imageautomation@example.com",
|
||||
Name: "imageautomation",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return nil
|
||||
})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Wait for image repository to be ready
|
||||
g.Eventually(func() bool {
|
||||
c, err := cloneRepository(repoUrl, name, cfg.pat)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
repoDir := c.Path()
|
||||
b, err := os.ReadFile(filepath.Join(repoDir, "podinfo.yaml"))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if bytes.Contains(b, []byte(newVersion)) == false {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}, 120*time.Second, 5*time.Second)
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
/*
|
||||
Copyright 2022 The Flux authors
|
||||
|
||||
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 test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||
)
|
||||
|
||||
func TestACRHelmRelease(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
ctx := context.TODO()
|
||||
|
||||
// Create namespace for test
|
||||
namespace := corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "acr-helm-release",
|
||||
},
|
||||
}
|
||||
_, err := controllerutil.CreateOrUpdate(ctx, testEnv.Client, &namespace, func() error {
|
||||
return nil
|
||||
})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
repoURL := fmt.Sprintf("%s/charts/podinfo", cfg.dockerCred.url)
|
||||
err = pushImagesFromURL(repoURL, "ghcr.io/stefanprodan/charts/podinfo:6.2.0", []string{"v0.0.1"})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Create HelmRepository and wait for it to sync
|
||||
helmRepository := sourcev1.HelmRepository{ObjectMeta: metav1.ObjectMeta{Name: "acr", Namespace: namespace.Name}}
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, cfg.client, &helmRepository, func() error {
|
||||
helmRepository.Spec = sourcev1.HelmRepositorySpec{
|
||||
URL: fmt.Sprintf("oci://%s", repoURL),
|
||||
Interval: metav1.Duration{
|
||||
Duration: 5 * time.Minute,
|
||||
},
|
||||
Provider: "azure",
|
||||
PassCredentials: true,
|
||||
Type: "oci",
|
||||
}
|
||||
return nil
|
||||
})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
g.Eventually(func() bool {
|
||||
obj := &sourcev1.HelmRepository{}
|
||||
nn := types.NamespacedName{Name: helmRepository.Name, Namespace: helmRepository.Namespace}
|
||||
err := testEnv.Client.Get(ctx, nn, obj)
|
||||
if err != nil {
|
||||
log.Printf("error getting helm repository %s\n", err.Error())
|
||||
return false
|
||||
}
|
||||
if apimeta.IsStatusConditionPresentAndEqual(obj.Status.Conditions, meta.ReadyCondition, metav1.ConditionTrue) == false {
|
||||
log.Println("helm repository condition not ready")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}, 30*time.Second, 5*time.Second)
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
/*
|
||||
Copyright 2022 The Flux authors
|
||||
|
||||
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 test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
"github.com/fluxcd/test-infra/tftestenv"
|
||||
)
|
||||
|
||||
func TestKeyVaultSops(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
ctx := context.TODO()
|
||||
name := "key-vault-sops"
|
||||
repoUrl := cfg.applicationRepository.http
|
||||
secretYaml, err := executeTemplate("testdata/secret.yaml", map[string]string{
|
||||
"ns": name,
|
||||
})
|
||||
|
||||
c, err := cloneRepository(repoUrl, name, cfg.pat)
|
||||
tmpDir := c.Path()
|
||||
err = tftestenv.RunCommand(ctx, tmpDir, "mkdir -p ./key-vault-sops", tftestenv.RunCommandOptions{})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
err = tftestenv.RunCommand(ctx, tmpDir, fmt.Sprintf("echo \"%s\" > ./key-vault-sops/secret.enc.yaml", secretYaml), tftestenv.RunCommandOptions{})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
err = tftestenv.RunCommand(ctx, tmpDir, fmt.Sprintf("sops --encrypt --encrypted-regex '^(data|stringData)$' %s --in-place ./key-vault-sops/secret.enc.yaml", cfg.sopsArgs), tftestenv.RunCommandOptions{})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
err = commitAndPushAll(ctx, c, name, "Add sops files")
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
if cfg.sopsSecretData != nil {
|
||||
secret := corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "sops-keys",
|
||||
Namespace: name,
|
||||
},
|
||||
}
|
||||
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, testEnv.Client, &secret, func() error {
|
||||
secret.StringData = cfg.sopsSecretData
|
||||
return nil
|
||||
})
|
||||
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
}
|
||||
|
||||
modifyKsSpec := func(spec *kustomizev1.KustomizationSpec) {
|
||||
spec.Decryption = &kustomizev1.Decryption{
|
||||
Provider: "sops",
|
||||
}
|
||||
if cfg.sopsSecretData != nil {
|
||||
spec.Decryption.SecretRef = &meta.LocalObjectReference{
|
||||
Name: "sops-keys",
|
||||
}
|
||||
}
|
||||
}
|
||||
err = setupNamespace(ctx, name, nsConfig{
|
||||
repoURL: repoUrl,
|
||||
path: "./key-vault-sops",
|
||||
modifyKsSpec: modifyKsSpec,
|
||||
})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
g.Eventually(func() bool {
|
||||
err := verifyGitAndKustomization(ctx, testEnv.Client, name, name)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
nn := types.NamespacedName{Name: "test", Namespace: name}
|
||||
secret := &corev1.Secret{}
|
||||
err = testEnv.Client.Get(ctx, nn, secret)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if string(secret.Data["foo"]) == "bar" {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}, 120*time.Second, 5*time.Second)
|
||||
}
|
@ -0,0 +1,238 @@
|
||||
/*
|
||||
Copyright 2021 The Flux authors
|
||||
|
||||
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 test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
tfjson "github.com/hashicorp/terraform-json"
|
||||
"go.uber.org/multierr"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
"github.com/fluxcd/pkg/git/libgit2/transport"
|
||||
"github.com/fluxcd/test-infra/tftestenv"
|
||||
)
|
||||
|
||||
const (
|
||||
// aksTerraformPath is the path to the folder containg the
|
||||
// terraform files for azure infra
|
||||
aksTerraformPath = "./terraform/azure"
|
||||
)
|
||||
|
||||
var (
|
||||
cfg *testConfig
|
||||
kubeconfigPath string
|
||||
infraOpts tftestenv.Options
|
||||
testRepos map[string]string
|
||||
testEnv *tftestenv.Environment
|
||||
)
|
||||
|
||||
// testConfig hold different variable that will be needed by the different test functions.
|
||||
type testConfig struct {
|
||||
client client.Client
|
||||
|
||||
// secret data for git repositories
|
||||
pat string
|
||||
idRsa string
|
||||
idRsaPub string
|
||||
|
||||
// Generate known host? Use flux cli?
|
||||
knownHosts string
|
||||
fleetInfraRepository repoConfig
|
||||
applicationRepository repoConfig
|
||||
|
||||
dockerCred dockerCred
|
||||
eventHubSas string
|
||||
|
||||
// cloud provider dependent argument to pass to the sops cli
|
||||
sopsArgs string
|
||||
// sops secret data
|
||||
sopsSecretData map[string]string
|
||||
}
|
||||
|
||||
// repoConfig contains the http/ssh urls for the created git repositories
|
||||
// on the various cloud providers.
|
||||
type repoConfig struct {
|
||||
http string
|
||||
ssh string
|
||||
}
|
||||
|
||||
// dockerCred contains credentials for the container repository
|
||||
type dockerCred struct {
|
||||
url string
|
||||
username string
|
||||
password string
|
||||
}
|
||||
|
||||
// getTestConfig gets the test configuration that contains different variables for running the tests
|
||||
type getTestConfig func(ctx context.Context, output map[string]*tfjson.StateOutput) (*testConfig, error)
|
||||
|
||||
// getFluxModification returns a map objects(like secrets) that should be created/updated for Flux to run well and a kustomization.yaml
|
||||
// that can be used to customize Flux
|
||||
type getFluxKustomization func(ctx context.Context, output map[string]*tfjson.StateOutput) (map[client.Object]controllerutil.MutateFn, string, error)
|
||||
|
||||
// registryLoginFunc is used to perform registry login against a provider based
|
||||
// on the terraform state output values. It returns a map of registry common
|
||||
// name and test repositories to test against, read from the terraform state
|
||||
// output.
|
||||
type registryLoginFunc func(ctx context.Context, output map[string]*tfjson.StateOutput) (map[string]string, error)
|
||||
|
||||
// ProviderConfig contains the test configurations for the different cloud providers
|
||||
type ProviderConfig struct {
|
||||
terraformPath string
|
||||
createKubeconfig tftestenv.CreateKubeconfig
|
||||
getTestConfig getTestConfig
|
||||
getFluxKustomization getFluxKustomization
|
||||
// registryLogin is used to perform registry login.
|
||||
registryLogin registryLoginFunc
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
infraOpts.Bindflags(flag.CommandLine)
|
||||
flag.Parse()
|
||||
|
||||
err := infraOpts.Validate()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// TODO(somtochiama): remove when tests have been updated to support GCP and AWS
|
||||
if infraOpts.Provider != "azure" {
|
||||
log.Fatal("only azure e2e tests are currently supported.")
|
||||
}
|
||||
|
||||
exitVal, err := setup(m)
|
||||
if err != nil {
|
||||
log.Printf("Received an error while running setup: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(exitVal)
|
||||
}
|
||||
|
||||
func setup(m *testing.M) (exitVal int, err error) {
|
||||
ctx := context.TODO()
|
||||
|
||||
// get provider specific configuration
|
||||
providerCfg, err := getProviderConfig(infraOpts.Provider)
|
||||
|
||||
// Setup Terraform binary and init state
|
||||
log.Printf("Setting up %s e2e test infrastructure", infraOpts.Provider)
|
||||
envOpts := []tftestenv.EnvironmentOption{
|
||||
tftestenv.WithExisting(infraOpts.Existing),
|
||||
tftestenv.WithRetain(infraOpts.Retain),
|
||||
tftestenv.WithVerbose(infraOpts.Verbose),
|
||||
tftestenv.WithCreateKubeconfig(providerCfg.createKubeconfig),
|
||||
}
|
||||
|
||||
tmpDir, err := os.MkdirTemp("", "*-e2e")
|
||||
kubeconfigPath = fmt.Sprintf("%s/kubeconfig", tmpDir)
|
||||
defer func() {
|
||||
if ferr := os.RemoveAll(filepath.Dir(kubeconfigPath)); ferr != nil {
|
||||
err = multierr.Append(fmt.Errorf("could not clean up kubeconfig file: %v", ferr), err)
|
||||
}
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Create terraform infrastructure
|
||||
testEnv, err = tftestenv.New(context.Background(), scheme.Scheme, providerCfg.terraformPath, kubeconfigPath, envOpts...)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// get terrraform infrastructure
|
||||
outputs, err := testEnv.StateOutput(context.Background())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// get provider specific test configuration
|
||||
cfg, err = providerCfg.getTestConfig(context.Background(), outputs)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
cfg.client = testEnv.Client
|
||||
|
||||
testRepos, err = providerCfg.registryLogin(ctx, outputs)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
objectMap, kustomizeYaml, err := providerCfg.getFluxKustomization(context.Background(), outputs)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
err = setupScheme()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// setup managed transport so the `pkg/libgit2` package can be used
|
||||
// for interacting repositories
|
||||
err = transport.InitManagedTransport()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("could not init managed transport")
|
||||
}
|
||||
|
||||
// Setup Kubernetes clients for test cluster
|
||||
err = installFlux(ctx, cfg.client, fluxConfig{
|
||||
kubeconfigPath: kubeconfigPath,
|
||||
repoURL: cfg.fleetInfraRepository.http,
|
||||
password: cfg.pat,
|
||||
objects: objectMap,
|
||||
kustomizeYaml: kustomizeYaml,
|
||||
})
|
||||
if err != nil {
|
||||
return 1, fmt.Errorf("error installing Flux: %v", err)
|
||||
}
|
||||
|
||||
// Run tests
|
||||
log.Println("Running e2e tests")
|
||||
result := m.Run()
|
||||
|
||||
if err := testEnv.Stop(ctx); err != nil {
|
||||
log.Printf("Failed to stop environment: %v", err)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func getProviderConfig(provider string) (*ProviderConfig, error) {
|
||||
switch provider {
|
||||
case "azure":
|
||||
return &ProviderConfig{
|
||||
terraformPath: aksTerraformPath,
|
||||
createKubeconfig: createKubeConfigAKS,
|
||||
getTestConfig: getTestConfigAKS,
|
||||
getFluxKustomization: createAzureSPSecret,
|
||||
registryLogin: registryLoginACR,
|
||||
}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("provider '%s' is not supported", provider)
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
resource "azurerm_container_registry" "this" {
|
||||
name = "acrapps${random_pet.suffix.id}"
|
||||
resource_group_name = azurerm_resource_group.this.name
|
||||
location = azurerm_resource_group.this.location
|
||||
sku = "Standard"
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
resource "azurerm_kubernetes_cluster" "this" {
|
||||
name = "aks-${local.name_suffix}"
|
||||
location = azurerm_resource_group.this.location
|
||||
resource_group_name = azurerm_resource_group.this.name
|
||||
|
||||
dns_prefix = "aks${local.name_suffix}"
|
||||
|
||||
default_node_pool {
|
||||
name = "default"
|
||||
node_count = 2
|
||||
vm_size = "Standard_B2s"
|
||||
os_disk_size_gb = 30
|
||||
}
|
||||
|
||||
identity {
|
||||
type = "SystemAssigned"
|
||||
}
|
||||
|
||||
role_based_access_control_enabled = true
|
||||
|
||||
network_profile {
|
||||
network_plugin = "kubenet"
|
||||
network_policy = "calico"
|
||||
}
|
||||
|
||||
tags = {
|
||||
environment = "e2e"
|
||||
}
|
||||
}
|
||||
|
||||
resource "azurerm_role_assignment" "aks_acr_pull" {
|
||||
scope = azurerm_container_registry.this.id
|
||||
role_definition_name = "AcrPull"
|
||||
principal_id = azurerm_kubernetes_cluster.this.kubelet_identity[0].object_id
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
data "azuredevops_project" "e2e" {
|
||||
name = "e2e"
|
||||
}
|
||||
|
||||
resource "azuredevops_git_repository" "fleet_infra" {
|
||||
project_id = data.azuredevops_project.e2e.id
|
||||
name = "fleet-infra-${local.name_suffix}"
|
||||
default_branch = "refs/heads/main"
|
||||
initialization {
|
||||
init_type = "Clean"
|
||||
}
|
||||
}
|
||||
|
||||
resource "azuredevops_git_repository" "application" {
|
||||
project_id = data.azuredevops_project.e2e.id
|
||||
name = "application-${local.name_suffix}"
|
||||
default_branch = "refs/heads/main"
|
||||
initialization {
|
||||
init_type = "Clean"
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
resource "azurerm_eventhub_namespace" "this" {
|
||||
name = "ehns-${local.name_suffix}"
|
||||
location = azurerm_resource_group.this.location
|
||||
resource_group_name = azurerm_resource_group.this.name
|
||||
sku = "Standard"
|
||||
capacity = 1
|
||||
}
|
||||
|
||||
|
||||
resource "azurerm_eventhub" "this" {
|
||||
name = "eh-${local.name_suffix}"
|
||||
namespace_name = azurerm_eventhub_namespace.this.name
|
||||
resource_group_name = azurerm_resource_group.this.name
|
||||
partition_count = 1
|
||||
message_retention = 1
|
||||
}
|
||||
|
||||
resource "azurerm_eventhub_authorization_rule" "this" {
|
||||
name = "flux"
|
||||
resource_group_name = azurerm_resource_group.this.name
|
||||
namespace_name = azurerm_eventhub_namespace.this.name
|
||||
eventhub_name = azurerm_eventhub.this.name
|
||||
listen = true
|
||||
send = true
|
||||
manage = false
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
resource "azurerm_key_vault" "this" {
|
||||
name = "kv-credentials-${random_pet.suffix.id}"
|
||||
resource_group_name = azurerm_resource_group.this.name
|
||||
location = azurerm_resource_group.this.location
|
||||
tenant_id = data.azurerm_client_config.current.tenant_id
|
||||
sku_name = "standard"
|
||||
}
|
||||
|
||||
resource "azurerm_key_vault_access_policy" "admin" {
|
||||
key_vault_id = azurerm_key_vault.this.id
|
||||
tenant_id = data.azurerm_client_config.current.tenant_id
|
||||
object_id = data.azurerm_client_config.current.object_id
|
||||
|
||||
key_permissions = [
|
||||
"Backup",
|
||||
"Create",
|
||||
"Decrypt",
|
||||
"Delete",
|
||||
"Encrypt",
|
||||
"Get",
|
||||
"Import",
|
||||
"List",
|
||||
"Purge",
|
||||
"Recover",
|
||||
"Restore",
|
||||
"Sign",
|
||||
"UnwrapKey",
|
||||
"Update",
|
||||
"Verify",
|
||||
"WrapKey",
|
||||
]
|
||||
|
||||
secret_permissions = [
|
||||
"Backup",
|
||||
"Delete",
|
||||
"Get",
|
||||
"List",
|
||||
"Purge",
|
||||
"Recover",
|
||||
"Restore",
|
||||
"Set",
|
||||
]
|
||||
}
|
||||
|
||||
resource "azurerm_key_vault_access_policy" "cluster" {
|
||||
key_vault_id = azurerm_key_vault.this.id
|
||||
tenant_id = data.azurerm_client_config.current.tenant_id
|
||||
object_id = azurerm_kubernetes_cluster.this.kubelet_identity[0].object_id
|
||||
|
||||
key_permissions = [
|
||||
"Decrypt",
|
||||
"Encrypt",
|
||||
]
|
||||
}
|
||||
|
||||
resource "azurerm_key_vault_key" "sops" {
|
||||
depends_on = [azurerm_key_vault_access_policy.admin]
|
||||
|
||||
name = "sops"
|
||||
key_vault_id = azurerm_key_vault.this.id
|
||||
key_type = "RSA"
|
||||
key_size = 2048
|
||||
|
||||
key_opts = [
|
||||
"decrypt",
|
||||
"encrypt",
|
||||
]
|
||||
}
|
||||
|
||||
resource "azurerm_key_vault_secret" "pat" {
|
||||
depends_on = [azurerm_key_vault_access_policy.admin]
|
||||
|
||||
name = "pat"
|
||||
value = var.azuredevops_pat
|
||||
key_vault_id = azurerm_key_vault.this.id
|
||||
}
|
||||
|
||||
resource "azurerm_key_vault_secret" "id_rsa" {
|
||||
depends_on = [azurerm_key_vault_access_policy.admin]
|
||||
|
||||
name = "id-rsa"
|
||||
value = var.azuredevops_id_rsa
|
||||
key_vault_id = azurerm_key_vault.this.id
|
||||
}
|
||||
|
||||
resource "azurerm_key_vault_secret" "id_rsa_pub" {
|
||||
depends_on = [azurerm_key_vault_access_policy.admin]
|
||||
|
||||
name = "id-rsa-pub"
|
||||
value = var.azuredevops_id_rsa_pub
|
||||
key_vault_id = azurerm_key_vault.this.id
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
terraform {
|
||||
|
||||
required_version = "1.2.8"
|
||||
|
||||
required_providers {
|
||||
azurerm = {
|
||||
source = "hashicorp/azurerm"
|
||||
version = "3.20.0"
|
||||
}
|
||||
azuread = {
|
||||
source = "hashicorp/azuread"
|
||||
version = "2.28.0"
|
||||
}
|
||||
azuredevops = {
|
||||
source = "microsoft/azuredevops"
|
||||
version = "0.2.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "azurerm" {
|
||||
features {}
|
||||
}
|
||||
|
||||
provider "azuredevops" {
|
||||
org_service_url = "https://dev.azure.com/${local.azure_devops_org}"
|
||||
personal_access_token = var.azuredevops_pat
|
||||
}
|
||||
|
||||
data "azurerm_client_config" "current" {}
|
||||
|
||||
resource "random_pet" "suffix" {
|
||||
length = 1
|
||||
separator = ""
|
||||
}
|
||||
|
||||
locals {
|
||||
azure_devops_org = var.azure_devops_org
|
||||
name_suffix = "e2e-${random_pet.suffix.id}"
|
||||
}
|
||||
|
||||
resource "azurerm_resource_group" "this" {
|
||||
name = "rg-${local.name_suffix}"
|
||||
location = "West Europe"
|
||||
|
||||
tags = {
|
||||
environment = "e2e"
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
output "aks_kube_config" {
|
||||
sensitive = true
|
||||
value = azurerm_kubernetes_cluster.this.kube_config_raw
|
||||
}
|
||||
|
||||
output "aks_host" {
|
||||
value = azurerm_kubernetes_cluster.this.kube_config[0].host
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "aks_client_certificate" {
|
||||
value = base64decode(azurerm_kubernetes_cluster.this.kube_config[0].client_certificate)
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "aks_client_key" {
|
||||
value = base64decode(azurerm_kubernetes_cluster.this.kube_config[0].client_key)
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "aks_cluster_ca_certificate" {
|
||||
value = base64decode(azurerm_kubernetes_cluster.this.kube_config[0].cluster_ca_certificate)
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "shared_pat" {
|
||||
sensitive = true
|
||||
value = data.azurerm_key_vault_secret.pat.value
|
||||
}
|
||||
|
||||
output "shared_id_rsa" {
|
||||
sensitive = true
|
||||
value = data.azurerm_key_vault_secret.id_rsa.value
|
||||
}
|
||||
|
||||
output "shared_id_rsa_pub" {
|
||||
sensitive = true
|
||||
value = data.azurerm_key_vault_secret.id_rsa_pub.value
|
||||
}
|
||||
|
||||
output "fleet_infra_repository" {
|
||||
value = {
|
||||
http = azuredevops_git_repository.fleet_infra.remote_url
|
||||
ssh = "ssh://git@ssh.dev.azure.com/v3/${local.azure_devops_org}/${azuredevops_git_repository.fleet_infra.project_id}/${azuredevops_git_repository.fleet_infra.name}"
|
||||
}
|
||||
}
|
||||
|
||||
output "application_repository" {
|
||||
value = {
|
||||
http = azuredevops_git_repository.application.remote_url
|
||||
ssh = "ssh://git@ssh.dev.azure.com/v3/${local.azure_devops_org}/${azuredevops_git_repository.application.project_id}/${azuredevops_git_repository.application.name}"
|
||||
}
|
||||
}
|
||||
|
||||
output "flux_azure_sp" {
|
||||
value = {
|
||||
tenant_id = data.azurerm_client_config.current.tenant_id
|
||||
client_id = azurerm_kubernetes_cluster.this.kubelet_identity[0].client_id
|
||||
}
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output msi_client_id {
|
||||
value = azurerm_kubernetes_cluster.this.kubelet_identity[0].object_id
|
||||
}
|
||||
|
||||
output "event_hub_sas" {
|
||||
value = azurerm_eventhub_authorization_rule.this.primary_connection_string
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "sops_id" {
|
||||
value = azurerm_key_vault_key.sops.id
|
||||
}
|
||||
|
||||
output "acr" {
|
||||
value = {
|
||||
url = azurerm_container_registry.this.login_server
|
||||
username = ""
|
||||
password = ""
|
||||
}
|
||||
sensitive = true
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
data "azurerm_key_vault_secret" "pat" {
|
||||
depends_on = [azurerm_key_vault_secret.pat]
|
||||
key_vault_id = resource.azurerm_key_vault.this.id
|
||||
name = "pat"
|
||||
}
|
||||
|
||||
data "azurerm_key_vault_secret" "id_rsa" {
|
||||
depends_on = [azurerm_key_vault_secret.id_rsa]
|
||||
|
||||
key_vault_id = resource.azurerm_key_vault.this.id
|
||||
name = "id-rsa"
|
||||
}
|
||||
|
||||
data "azurerm_key_vault_secret" "id_rsa_pub" {
|
||||
depends_on = [azurerm_key_vault_secret.id_rsa_pub]
|
||||
|
||||
key_vault_id = resource.azurerm_key_vault.this.id
|
||||
name = "id-rsa-pub"
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
variable "azure_devops_org" {
|
||||
type = string
|
||||
default = "flux-azure"
|
||||
description = "Name of Azure DevOps organizations were the repositories will be created"
|
||||
}
|
||||
|
||||
variable "shared_suffix" {
|
||||
type = string
|
||||
default = "oarfish"
|
||||
description = "Generated random suffix for resources created by terraform/shared"
|
||||
}
|
||||
|
||||
variable "azuredevops_pat" {
|
||||
type = string
|
||||
description = "Personal access token for Azure DevOps repository"
|
||||
}
|
||||
|
||||
variable "azuredevops_id_rsa" {
|
||||
type = string
|
||||
description = "Personal access token for Azure DevOps repository"
|
||||
}
|
||||
|
||||
variable "azuredevops_id_rsa_pub" {
|
||||
type = string
|
||||
description = "Personal access token for Azure DevOps repository"
|
||||
}
|
||||
|
@ -0,0 +1,26 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: podinfo
|
||||
namespace: {{ .ns }}
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: podinfo
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: podinfo
|
||||
spec:
|
||||
containers:
|
||||
- name: podinfod
|
||||
image: {{ .url }}/container/podinfo:{{ .version }} # {"$imagepolicy": "{{ .name }}:podinfo"}
|
||||
readinessProbe:
|
||||
exec:
|
||||
command:
|
||||
- podcli
|
||||
- check
|
||||
- http
|
||||
- localhost:9898/readyz
|
||||
initialDelaySeconds: 5
|
||||
timeoutSeconds: 5
|
@ -0,0 +1,43 @@
|
||||
resources:
|
||||
- gotk-components.yaml
|
||||
- gotk-sync.yaml
|
||||
patches:
|
||||
- target:
|
||||
version: v1
|
||||
group: apps
|
||||
kind: Deployment
|
||||
name: image-reflector-controller
|
||||
namespace: flux-system
|
||||
patch: |-
|
||||
- op: add
|
||||
path: /spec/template/spec/containers/0/args/-
|
||||
value: --azure-autologin-for-acr
|
||||
patchesStrategicMerge:
|
||||
- |-
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: kustomize-controller
|
||||
namespace: flux-system
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: manager
|
||||
env:
|
||||
- name: AZURE_AUTH_METHOD
|
||||
value: msi
|
||||
- |-
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: source-controller
|
||||
namespace: flux-system
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: manager
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: {{ .secretName }}
|
@ -0,0 +1,5 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: foobar
|
||||
namespace: {{ .ns }}
|
@ -0,0 +1,7 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: "test"
|
||||
namespace: {{ .ns }}
|
||||
stringData:
|
||||
foo: "bar"
|
@ -0,0 +1,456 @@
|
||||
/*
|
||||
Copyright 2021 The Flux authors
|
||||
|
||||
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 test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/crane"
|
||||
git2go "github.com/libgit2/git2go/v33"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
helmv2beta1 "github.com/fluxcd/helm-controller/api/v2beta1"
|
||||
automationv1beta1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
|
||||
reflectorv1beta1 "github.com/fluxcd/image-reflector-controller/api/v1beta1"
|
||||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
|
||||
notiv1beta1 "github.com/fluxcd/notification-controller/api/v1beta1"
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
"github.com/fluxcd/pkg/git"
|
||||
"github.com/fluxcd/pkg/git/libgit2"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||
"github.com/fluxcd/test-infra/tftestenv"
|
||||
)
|
||||
|
||||
const defaultBranch = "main"
|
||||
|
||||
// fluxConfig contains configuration for installing FLux in a cluster
|
||||
type fluxConfig struct {
|
||||
kubeconfigPath string
|
||||
repoURL string
|
||||
password string
|
||||
objects map[client.Object]controllerutil.MutateFn
|
||||
kustomizeYaml string
|
||||
}
|
||||
|
||||
func setupScheme() error {
|
||||
err := sourcev1.AddToScheme(scheme.Scheme)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = kustomizev1.AddToScheme(scheme.Scheme)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = helmv2beta1.AddToScheme(scheme.Scheme)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = reflectorv1beta1.AddToScheme(scheme.Scheme)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = automationv1beta1.AddToScheme(scheme.Scheme)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = notiv1beta1.AddToScheme(scheme.Scheme)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// installFlux adds the core Flux components to the cluster specified in the kubeconfig file.
|
||||
func installFlux(ctx context.Context, kubeClient client.Client, conf fluxConfig) error {
|
||||
// Create flux-system namespace
|
||||
namespace := corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "flux-system",
|
||||
},
|
||||
}
|
||||
err := testEnv.Client.Create(ctx, &namespace)
|
||||
// create secret containing credentials for bootstrap repository
|
||||
httpsCredentials := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "https-credentials", Namespace: "flux-system"}}
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, kubeClient, httpsCredentials, func() error {
|
||||
httpsCredentials.StringData = map[string]string{
|
||||
"username": "git",
|
||||
"password": conf.password,
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create additional objects that are needed for flux to run correctly
|
||||
for obj, fn := range conf.objects {
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, kubeClient, obj, fn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Install Flux and push files to git repository
|
||||
gitClient, err := cloneRepository(conf.repoURL, "", conf.password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
repoDir := gitClient.Path()
|
||||
err = tftestenv.RunCommand(ctx, repoDir, "mkdir -p ./clusters/e2e/flux-system", tftestenv.RunCommandOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = tftestenv.RunCommand(ctx, repoDir,
|
||||
"flux install --components-extra=\"image-reflector-controller,image-automation-controller\" --export > ./clusters/e2e/flux-system/gotk-components.yaml",
|
||||
tftestenv.RunCommandOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = tftestenv.RunCommand(ctx, repoDir, fmt.Sprintf("flux create source git flux-system --git-implementation=libgit2 --url=%s --branch=%s --secret-ref=https-credentials --interval=1m --export > ./clusters/e2e/flux-system/gotk-sync.yaml", conf.repoURL, defaultBranch),
|
||||
tftestenv.RunCommandOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = tftestenv.RunCommand(ctx, repoDir, "flux create kustomization flux-system --source=flux-system --path='./clusters/e2e' --prune=true --interval=1m --export >> ./clusters/e2e/flux-system/gotk-sync.yaml", tftestenv.RunCommandOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
kustomizeYaml := `
|
||||
resources:
|
||||
- gotk-components.yaml
|
||||
- gotk-sync.yaml
|
||||
`
|
||||
if conf.kustomizeYaml != "" {
|
||||
kustomizeYaml = conf.kustomizeYaml
|
||||
}
|
||||
|
||||
err = tftestenv.RunCommand(ctx, gitClient.Path(), fmt.Sprintf("echo \"%s\" > ./clusters/e2e/flux-system/kustomization.yaml", kustomizeYaml), tftestenv.RunCommandOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// commit and push manifests
|
||||
err = commitAndPushAll(ctx, gitClient, defaultBranch, "Add sync manifests")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Need to apply CRDs first to make sure that the sync resources will apply properly
|
||||
err = tftestenv.RunCommand(ctx, repoDir, fmt.Sprintf("kubectl --kubeconfig=%s apply -f ./clusters/e2e/flux-system/gotk-components.yaml", conf.kubeconfigPath), tftestenv.RunCommandOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = tftestenv.RunCommand(ctx, repoDir, fmt.Sprintf("kubectl --kubeconfig=%s apply -k ./clusters/e2e/flux-system/", conf.kubeconfigPath), tftestenv.RunCommandOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// verifyGitAndKustomization checks that the gitrespository and kustomization combination are working properly.
|
||||
func verifyGitAndKustomization(ctx context.Context, kubeClient client.Client, namespace, name string) error {
|
||||
nn := types.NamespacedName{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
}
|
||||
source := &sourcev1.GitRepository{}
|
||||
err := kubeClient.Get(ctx, nn, source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if apimeta.IsStatusConditionPresentAndEqual(source.Status.Conditions, meta.ReadyCondition, metav1.ConditionTrue) == false {
|
||||
return fmt.Errorf("source condition not ready")
|
||||
}
|
||||
kustomization := &kustomizev1.Kustomization{}
|
||||
err = kubeClient.Get(ctx, nn, kustomization)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if apimeta.IsStatusConditionPresentAndEqual(kustomization.Status.Conditions, meta.ReadyCondition, metav1.ConditionTrue) == false {
|
||||
return fmt.Errorf("kustomization condition not ready")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type nsConfig struct {
|
||||
repoURL string
|
||||
protocol string
|
||||
objectName string
|
||||
path string
|
||||
modifyGitSpec func(spec *sourcev1.GitRepositorySpec)
|
||||
modifyKsSpec func(spec *kustomizev1.KustomizationSpec)
|
||||
}
|
||||
|
||||
// setupNamespaces creates the namespace, then creates the git secret,
|
||||
// git repository and kustomization in that namespace
|
||||
func setupNamespace(ctx context.Context, name string, opts nsConfig) error {
|
||||
namespace := corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
},
|
||||
}
|
||||
_, err := controllerutil.CreateOrUpdate(ctx, testEnv.Client, &namespace, func() error {
|
||||
return nil
|
||||
})
|
||||
|
||||
secret := corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "git-credentials",
|
||||
Namespace: name,
|
||||
},
|
||||
}
|
||||
|
||||
secretData := map[string]string{
|
||||
"username": "git",
|
||||
"password": cfg.pat,
|
||||
}
|
||||
if opts.protocol == "ssh" {
|
||||
secretData = map[string]string{
|
||||
"identity": cfg.idRsa,
|
||||
"identity.pub": cfg.idRsaPub,
|
||||
"known_hosts": cfg.knownHosts,
|
||||
}
|
||||
}
|
||||
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, testEnv.Client, &secret, func() error {
|
||||
secret.StringData = secretData
|
||||
return nil
|
||||
})
|
||||
|
||||
gitSpec := &sourcev1.GitRepositorySpec{
|
||||
Interval: metav1.Duration{
|
||||
Duration: 1 * time.Minute,
|
||||
},
|
||||
Reference: &sourcev1.GitRepositoryRef{
|
||||
Branch: name,
|
||||
},
|
||||
SecretRef: &meta.LocalObjectReference{
|
||||
Name: secret.Name,
|
||||
},
|
||||
URL: opts.repoURL,
|
||||
}
|
||||
if infraOpts.Provider == "azure" {
|
||||
gitSpec.GitImplementation = sourcev1.LibGit2Implementation
|
||||
}
|
||||
if opts.modifyGitSpec != nil {
|
||||
opts.modifyGitSpec(gitSpec)
|
||||
}
|
||||
source := &sourcev1.GitRepository{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace.Name}}
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, testEnv.Client, source, func() error {
|
||||
source.Spec = *gitSpec
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ksSpec := &kustomizev1.KustomizationSpec{
|
||||
Path: opts.path,
|
||||
SourceRef: kustomizev1.CrossNamespaceSourceReference{
|
||||
Kind: sourcev1.GitRepositoryKind,
|
||||
Name: source.Name,
|
||||
Namespace: source.Namespace,
|
||||
},
|
||||
Interval: metav1.Duration{
|
||||
Duration: 1 * time.Minute,
|
||||
},
|
||||
Prune: true,
|
||||
}
|
||||
if opts.modifyKsSpec != nil {
|
||||
opts.modifyKsSpec(ksSpec)
|
||||
}
|
||||
kustomization := &kustomizev1.Kustomization{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace.Name}}
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, testEnv.Client, kustomization, func() error {
|
||||
kustomization.Spec = *ksSpec
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cloneRepository(url, branchName string, password string) (*libgit2.Client, error) {
|
||||
checkoutBranch := defaultBranch
|
||||
|
||||
tmpDir, err := os.MkdirTemp("", "*-repository")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client, err := libgit2.NewClient(tmpDir, &git.AuthOptions{
|
||||
Transport: git.HTTP,
|
||||
Username: "git",
|
||||
Password: password,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = client.Clone(context.Background(), url, git.CheckoutOptions{
|
||||
Branch: checkoutBranch,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if branchName != "" && branchName != defaultBranch {
|
||||
if err := client.SwitchBranch(context.Background(), branchName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func addFile(dir, path, content string) error {
|
||||
err := os.WriteFile(filepath.Join(dir, path), []byte(content), 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func commitAndPushAll(ctx context.Context, client *libgit2.Client, branch, message string) error {
|
||||
_, err := client.Commit(git.Commit{
|
||||
Author: git.Signature{
|
||||
Name: "git",
|
||||
Email: "test@example.com",
|
||||
},
|
||||
Message: message,
|
||||
}, nil)
|
||||
// no staged files error occurs when we are reusing infrastructure
|
||||
// since the remote repository exists and is up to-date
|
||||
if err != nil && !strings.Contains(err.Error(), "no staged files") {
|
||||
return err
|
||||
}
|
||||
|
||||
err = client.Push(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createTagAndPush(client *libgit2.Client, branchName, tag, password string) error {
|
||||
repo, err := git2go.OpenRepository(client.Path())
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
branch, err := repo.LookupBranch(branchName, git2go.BranchAll)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
commit, err := repo.LookupCommit(branch.Target())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tags, err := repo.Tags.List()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, existingTag := range tags {
|
||||
if existingTag == tag {
|
||||
err = repo.Tags.Remove(tag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sig := &git2go.Signature{
|
||||
Name: "git",
|
||||
Email: "test@example.com",
|
||||
When: time.Now(),
|
||||
}
|
||||
_, err = repo.Tags.Create(tag, commit, sig, "create tag")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
origin, err := repo.Remotes.Lookup("origin")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = origin.Push([]string{fmt.Sprintf("+refs/tags/%s", tag)}, &git2go.PushOptions{
|
||||
RemoteCallbacks: git2go.RemoteCallbacks{
|
||||
CredentialsCallback: credentialCallback("git", password),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func credentialCallback(username, password string) git2go.CredentialsCallback {
|
||||
return func(url string, usernameFromURL string, allowedTypes git2go.CredType) (*git2go.Cred, error) {
|
||||
cred, err := git2go.NewCredentialUserpassPlaintext(username, password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cred, nil
|
||||
}
|
||||
}
|
||||
|
||||
func executeTemplate(path string, templateValues map[string]string) (string, error) {
|
||||
buf, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
tmpl := template.Must(template.New("golden").Parse(string(buf)))
|
||||
var out bytes.Buffer
|
||||
if err := tmpl.Execute(&out, templateValues); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return out.String(), nil
|
||||
}
|
||||
|
||||
func pushImagesFromURL(repoURL, imgURL string, tags []string) error {
|
||||
img, err := crane.Pull(imgURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, tag := range tags {
|
||||
log.Printf("Pushing '%s' to '%s:%s'\n", imgURL, repoURL, tag)
|
||||
if err := crane.Push(img, fmt.Sprintf("%s:%s", repoURL, tag)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue