Move tests to seperate repository

pull/3071/head
Somtochi Onyekwere 3 years ago
parent 90f0d81532
commit 271281e651

@ -2,6 +2,10 @@ name: e2e-azure
on:
workflow_dispatch:
inputs:
tag:
description: 'test version'
required: false
schedule:
- cron: '0 6 * * *'
push:

@ -19,6 +19,7 @@ all: test build
tidy:
go mod tidy -compat=1.18
cd tests/azure && go mod tidy -compat=1.18
cd tests/integration && go mod tidy -compat=1.18
fmt:
go fmt ./...

3
tests/.gitignore vendored

@ -9,7 +9,7 @@
# .tfstate files
*.tfstate
*.tfstate.*
# .terraform*
# Crash log files
crash.log
@ -37,4 +37,5 @@ override.tf.json
.terraformrc
terraform.rc
.env
# End of https://www.toptal.com/developers/gitignore/api/terraform

@ -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…
Cancel
Save