mirror of https://github.com/fluxcd/flux2.git
commit
19caeb178f
@ -0,0 +1,66 @@
|
||||
name: e2e-azure
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 6 * * *'
|
||||
push:
|
||||
branches: [ azure/e2e ]
|
||||
|
||||
jobs:
|
||||
e2e:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Restore Go cache
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go1.16-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go1.16-
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.16.x
|
||||
- name: Install libgit2
|
||||
run: |
|
||||
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 648ACFD622F3D138
|
||||
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 0E98404D386FA1D9
|
||||
echo "deb http://deb.debian.org/debian unstable main" | sudo tee -a /etc/apt/sources.list
|
||||
echo "deb-src http://deb.debian.org/debian unstable main" | sudo tee -a /etc/apt/sources.list
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --allow-downgrades libgit2-dev/unstable zlib1g-dev/unstable libssh2-1-dev/unstable libpcre3-dev/unstable
|
||||
- name: Setup Flux CLI
|
||||
run: |
|
||||
make build
|
||||
mkdir -p $HOME/.local/bin
|
||||
mv ./bin/flux $HOME/.local/bin
|
||||
- name: Setup SOPS
|
||||
run: |
|
||||
wget https://github.com/mozilla/sops/releases/download/v3.7.1/sops-v3.7.1.linux
|
||||
chmod +x sops-v3.7.1.linux
|
||||
mkdir -p $HOME/.local/bin
|
||||
mv sops-v3.7.1.linux $HOME/.local/bin/sops
|
||||
- name: Setup Terraform
|
||||
uses: hashicorp/setup-terraform@v1
|
||||
with:
|
||||
terraform_version: 1.0.7
|
||||
terraform_wrapper: false
|
||||
- name: Setup Azure CLI
|
||||
run: |
|
||||
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
|
||||
- name: Run Azure e2e tests
|
||||
env:
|
||||
ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }}
|
||||
ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }}
|
||||
ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }}
|
||||
ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }}
|
||||
run: |
|
||||
echo $HOME
|
||||
echo $PATH
|
||||
ls $HOME/.local/bin
|
||||
az login --service-principal -u ${ARM_CLIENT_ID} -p ${ARM_CLIENT_SECRET} -t ${ARM_TENANT_ID}
|
||||
cd ./tests/azure
|
||||
go test -timeout 60m .
|
@ -0,0 +1,40 @@
|
||||
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/terraform
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=terraform
|
||||
|
||||
### Terraform ###
|
||||
# Local .terraform directories
|
||||
**/.terraform/*
|
||||
|
||||
# .tfstate files
|
||||
*.tfstate
|
||||
*.tfstate.*
|
||||
|
||||
# Crash log files
|
||||
crash.log
|
||||
|
||||
# Exclude all .tfvars files, which are likely to contain sentitive data, such as
|
||||
# password, private keys, and other secrets. These should not be part of version
|
||||
# control as they are data points which are potentially sensitive and subject
|
||||
# to change depending on the environment.
|
||||
#
|
||||
*.tfvars
|
||||
|
||||
# Ignore override files as they are usually used to override resources locally and so
|
||||
# are not checked in
|
||||
override.tf
|
||||
override.tf.json
|
||||
*_override.tf
|
||||
*_override.tf.json
|
||||
|
||||
# Include override files you do wish to add to version control using negated pattern
|
||||
# !example_override.tf
|
||||
|
||||
# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
|
||||
# example: *tfplan*
|
||||
|
||||
# Ignore CLI configuration files
|
||||
.terraformrc
|
||||
terraform.rc
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/terraform
|
@ -0,0 +1,51 @@
|
||||
# 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 tests are run with the help of pre configured Azure subscriptions and Azure DevOps organization. Access to thse accounts are currently limited to
|
||||
Flux maintainers.
|
||||
* [Azure Subscription](https://portal.azure.com/#@weaveworksendtoend.onmicrosoft.com/resource/subscriptions/71e8dce4-9af6-405a-8e96-425f5d3c302b/overview)
|
||||
* [Azure DevOps organization](https://dev.azure.com/flux-azure/)
|
||||
|
||||
All infrastructure is and should be created with Terraform. There are two separate Terraform states. All state should be configured to use remote
|
||||
state in Azure. They should all be placed in the [same container](https://portal.azure.com/#@weaveworksendtoend.onmicrosoft.com/resource/subscriptions/71e8dce4-9af6-405a-8e96-425f5d3c302b/resourceGroups/terraform-state/providers/Microsoft.Storage/storageAccounts/terraformstate0419/containersList)
|
||||
but use different keys.
|
||||
|
||||
The [shared](./terraform/shared) Terraform creates long running cheaper infrastructure that is used across all tests. This includes a Key Vault which
|
||||
contains an ssh key and Azure DevOps Personal Access Token which cant be created automatically. It also includes an Azure Container Registry which the
|
||||
forked [podinfo](https://dev.azure.com/flux-azure/e2e/_git/podinfo) repository pushes an Helm Chart and Docker image to.
|
||||
|
||||
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, and Azure EventHub. The resources should be created and destroyed before and after every test run. Currently
|
||||
the same state is reused between runs to make sure that resources are left running after each test run.
|
||||
|
||||
## Tests
|
||||
|
||||
Each test run is intiated by running `terraform apply` on the aks Terraform, it does this by using the library [terraform-exec](github.com/hashicorp/terraform-exec).
|
||||
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 offest 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
|
||||
|
||||
## Give User Access
|
||||
|
||||
There are a couple of steps required when adding a new user to get access to the Azure portal and Azure DevOps. To begin with add the new user to
|
||||
[Azure AD](https://portal.azure.com/#blade/Microsoft_AAD_IAM/UsersManagementMenuBlade/MsGraphUsers), and add the user to the [Azure AD group
|
||||
flux-contributors](https://portal.azure.com/#blade/Microsoft_AAD_IAM/GroupDetailsMenuBlade/Overview/groupId/24e0f3f6-6555-4d3d-99ab-414c869cab5d). The
|
||||
new users should now go through the invite process, and be able to sign in to both the Azure Portal and Azure DevOps.
|
||||
|
||||
After the new user has signed into Azure DevOps you will need to modify the users permissions. This cannot be done before the user has signed in a
|
||||
first time. In the [organization users page](https://dev.azure.com/flux-azure/_settings/users) chose "Manage User" and set the "Access Level" to basic
|
||||
and make the user "Project Contributor" of the "e2e" Azure DevOps project.
|
@ -0,0 +1,883 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
b64 "encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
eventhub "github.com/Azure/azure-event-hubs-go/v3"
|
||||
"github.com/hashicorp/terraform-exec/tfexec"
|
||||
"github.com/hashicorp/terraform-exec/tfinstall"
|
||||
git2go "github.com/libgit2/git2go/v31"
|
||||
"github.com/microsoft/azure-devops-go-api/azuredevops"
|
||||
"github.com/microsoft/azure-devops-go-api/azuredevops/git"
|
||||
"github.com/stretchr/testify/require"
|
||||
giturls "github.com/whilp/git-urls"
|
||||
|
||||
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/client"
|
||||
"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"
|
||||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
|
||||
notiv1beta1 "github.com/fluxcd/notification-controller/api/v1beta1"
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
"github.com/fluxcd/pkg/runtime/events"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
kubeconfigPath string
|
||||
kubeClient client.Client
|
||||
|
||||
azdoPat string
|
||||
idRsa string
|
||||
idRsaPub string
|
||||
knownHosts string
|
||||
fleetInfraRepository repoConfig
|
||||
applicationRepository repoConfig
|
||||
|
||||
fluxAzureSp spConfig
|
||||
sopsId string
|
||||
acr acrConfig
|
||||
eventHubSas string
|
||||
}
|
||||
|
||||
type spConfig struct {
|
||||
tenantId string
|
||||
clientId string
|
||||
clientSecret string
|
||||
}
|
||||
|
||||
type repoConfig struct {
|
||||
http string
|
||||
ssh string
|
||||
}
|
||||
|
||||
type acrConfig struct {
|
||||
url string
|
||||
username string
|
||||
password string
|
||||
}
|
||||
|
||||
var cfg config
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
ctx := context.TODO()
|
||||
|
||||
log.Println("Setting up Azure test infrastructure")
|
||||
execPath, err := tfinstall.Find(ctx, &whichTerraform{})
|
||||
if err != nil {
|
||||
log.Fatalf("terraform exec path not found: %v", err)
|
||||
}
|
||||
tf, err := tfexec.NewTerraform("./terraform/aks", execPath)
|
||||
if err != nil {
|
||||
log.Fatalf("could not create terraform instance: %v", err)
|
||||
}
|
||||
log.Println("Init Terraform")
|
||||
err = tf.Init(ctx, tfexec.Upgrade(true))
|
||||
if err != nil {
|
||||
log.Fatalf("error running init: %v", err)
|
||||
}
|
||||
log.Println("Applying Terraform")
|
||||
err = tf.Apply(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("error running apply: %v", err)
|
||||
}
|
||||
state, err := tf.Show(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("error running show: %v", err)
|
||||
}
|
||||
outputs := state.Values.Outputs
|
||||
kubeconfig := outputs["aks_kube_config"].Value.(string)
|
||||
aksHost := outputs["aks_host"].Value.(string)
|
||||
aksCert := outputs["aks_client_certificate"].Value.(string)
|
||||
aksKey := outputs["aks_client_key"].Value.(string)
|
||||
aksCa := outputs["aks_cluster_ca_certificate"].Value.(string)
|
||||
azdoPat := outputs["shared_pat"].Value.(string)
|
||||
idRsa := outputs["shared_id_rsa"].Value.(string)
|
||||
idRsaPub := outputs["shared_id_rsa_pub"].Value.(string)
|
||||
fleetInfraRepository := outputs["fleet_infra_repository"].Value.(map[string]interface{})
|
||||
applicationRepository := outputs["application_repository"].Value.(map[string]interface{})
|
||||
fluxAzureSp := outputs["flux_azure_sp"].Value.(map[string]interface{})
|
||||
sharedSopsId := outputs["sops_id"].Value.(string)
|
||||
acr := outputs["acr"].Value.(map[string]interface{})
|
||||
eventHubSas := outputs["event_hub_sas"].Value.(string)
|
||||
|
||||
log.Println("Creating Kubernetes client")
|
||||
kubeconfigPath, kubeClient, err := getKubernetesCredentials(kubeconfig, aksHost, aksCert, aksKey, aksCa)
|
||||
if err != nil {
|
||||
log.Fatalf("error create Kubernetes client: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(filepath.Dir(kubeconfigPath))
|
||||
|
||||
cfg = config{
|
||||
kubeconfigPath: kubeconfigPath,
|
||||
kubeClient: kubeClient,
|
||||
azdoPat: azdoPat,
|
||||
idRsa: idRsa,
|
||||
idRsaPub: idRsaPub,
|
||||
knownHosts: "ssh.dev.azure.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7Hr1oTWqNqOlzGJOfGJ4NakVyIzf1rXYd4d7wo6jBlkLvCA4odBlL0mDUyZ0/QUfTTqeu+tm22gOsv+VrVTMk6vwRU75gY/y9ut5Mb3bR5BV58dKXyq9A9UeB5Cakehn5Zgm6x1mKoVyf+FFn26iYqXJRgzIZZcZ5V6hrE0Qg39kZm4az48o0AUbf6Sp4SLdvnuMa2sVNwHBboS7EJkm57XQPVU3/QpyNLHbWDdzwtrlS+ez30S3AdYhLKEOxAG8weOnyrtLJAUen9mTkol8oII1edf7mWWbWVf0nBmly21+nZcmCTISQBtdcyPaEno7fFQMDD26/s0lfKob4Kw8H",
|
||||
fleetInfraRepository: repoConfig{
|
||||
http: fleetInfraRepository["http"].(string),
|
||||
ssh: fleetInfraRepository["ssh"].(string),
|
||||
},
|
||||
applicationRepository: repoConfig{
|
||||
http: applicationRepository["http"].(string),
|
||||
ssh: applicationRepository["ssh"].(string),
|
||||
},
|
||||
fluxAzureSp: spConfig{
|
||||
tenantId: fluxAzureSp["tenant_id"].(string),
|
||||
clientId: fluxAzureSp["client_id"].(string),
|
||||
clientSecret: fluxAzureSp["client_secret"].(string),
|
||||
},
|
||||
sopsId: sharedSopsId,
|
||||
acr: acrConfig{
|
||||
url: acr["url"].(string),
|
||||
username: acr["username"].(string),
|
||||
password: acr["password"].(string),
|
||||
},
|
||||
eventHubSas: eventHubSas,
|
||||
}
|
||||
|
||||
err = installFlux(ctx, kubeClient, kubeconfigPath, cfg.fleetInfraRepository.http, azdoPat, cfg.fluxAzureSp)
|
||||
if err != nil {
|
||||
log.Fatalf("error installing Flux: %v", err)
|
||||
}
|
||||
|
||||
log.Println("Running Azure e2e tests")
|
||||
exitVal := m.Run()
|
||||
|
||||
log.Println("Tearing down Azure test infrastructure")
|
||||
err = tf.Destroy(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("error running Show: %v", err)
|
||||
}
|
||||
|
||||
os.Exit(exitVal)
|
||||
}
|
||||
|
||||
func TestFluxInstallation(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
require.Eventually(t, func() bool {
|
||||
err := verifyGitAndKustomization(ctx, cfg.kubeClient, "flux-system", "flux-system")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}, 60*time.Second, 5*time.Second)
|
||||
}
|
||||
|
||||
func TestAzureDevOpsCloning(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
branchName := "feature/branch"
|
||||
tagName := "v1"
|
||||
|
||||
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, repoDir, err := getRepository(cfg.applicationRepository.http, branchName, true, cfg.azdoPat)
|
||||
require.NoError(t, err)
|
||||
for _, tt := range tests {
|
||||
manifest := fmt.Sprintf(`
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: foobar
|
||||
namespace: %s
|
||||
`, tt.name)
|
||||
err = runCommand(ctx, repoDir, fmt.Sprintf("mkdir -p ./cloning-test/%s", tt.name))
|
||||
require.NoError(t, err)
|
||||
err = runCommand(ctx, repoDir, fmt.Sprintf("echo '%s' > ./cloning-test/%s/configmap.yaml", manifest, tt.name))
|
||||
require.NoError(t, err)
|
||||
}
|
||||
err = commitAndPushAll(repo, branchName, cfg.azdoPat)
|
||||
require.NoError(t, err)
|
||||
err = createTagAndPush(repo, branchName, tagName, cfg.azdoPat)
|
||||
require.NoError(t, err)
|
||||
|
||||
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
|
||||
secretData := map[string]string{
|
||||
"username": "git",
|
||||
"password": cfg.azdoPat,
|
||||
}
|
||||
if tt.cloneType == "ssh" {
|
||||
url = cfg.applicationRepository.ssh
|
||||
secretData = map[string]string{
|
||||
"identity": cfg.idRsa,
|
||||
"identity.pub": cfg.idRsaPub,
|
||||
"known_hosts": cfg.knownHosts,
|
||||
}
|
||||
}
|
||||
|
||||
namespace := corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: tt.name,
|
||||
},
|
||||
}
|
||||
_, err := controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, &namespace, func() error {
|
||||
return nil
|
||||
})
|
||||
gitSecret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "git-credentials",
|
||||
Namespace: namespace.Name,
|
||||
},
|
||||
}
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, gitSecret, func() error {
|
||||
gitSecret.StringData = secretData
|
||||
return nil
|
||||
})
|
||||
source := &sourcev1.GitRepository{ObjectMeta: metav1.ObjectMeta{Name: tt.name, Namespace: namespace.Name}}
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, source, func() error {
|
||||
source.Spec = sourcev1.GitRepositorySpec{
|
||||
GitImplementation: sourcev1.LibGit2Implementation,
|
||||
Reference: ref,
|
||||
SecretRef: &meta.LocalObjectReference{
|
||||
Name: gitSecret.Name,
|
||||
},
|
||||
URL: url,
|
||||
}
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
kustomization := &kustomizev1.Kustomization{ObjectMeta: metav1.ObjectMeta{Name: tt.name, Namespace: namespace.Name}}
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, kustomization, func() error {
|
||||
kustomization.Spec = kustomizev1.KustomizationSpec{
|
||||
Path: fmt.Sprintf("./cloning-test/%s", tt.name),
|
||||
SourceRef: kustomizev1.CrossNamespaceSourceReference{
|
||||
Kind: sourcev1.GitRepositoryKind,
|
||||
Name: tt.name,
|
||||
Namespace: namespace.Name,
|
||||
},
|
||||
Interval: metav1.Duration{Duration: 1 * time.Minute},
|
||||
Prune: true,
|
||||
}
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Wait for configmap to be deployed
|
||||
require.Eventually(t, func() bool {
|
||||
err := verifyGitAndKustomization(ctx, cfg.kubeClient, namespace.Name, tt.name)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
nn := types.NamespacedName{Name: "foobar", Namespace: namespace.Name}
|
||||
cm := &corev1.ConfigMap{}
|
||||
err = cfg.kubeClient.Get(ctx, nn, cm)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}, 120*time.Second, 5*time.Second)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestImageRepositoryACR(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
name := "image-repository-acr"
|
||||
repoUrl := cfg.applicationRepository.http
|
||||
oldVersion := "1.0.0"
|
||||
newVersion := "1.0.1"
|
||||
manifest := fmt.Sprintf(`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: podinfo
|
||||
namespace: %s
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: podinfo
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: podinfo
|
||||
spec:
|
||||
containers:
|
||||
- name: podinfod
|
||||
image: %s/container/podinfo:%s # {"$imagepolicy": "%s:podinfo"}
|
||||
readinessProbe:
|
||||
exec:
|
||||
command:
|
||||
- podcli
|
||||
- check
|
||||
- http
|
||||
- localhost:9898/readyz
|
||||
initialDelaySeconds: 5
|
||||
timeoutSeconds: 5`, name, cfg.acr.url, oldVersion, name)
|
||||
|
||||
repo, repoDir, err := getRepository(repoUrl, name, true, cfg.azdoPat)
|
||||
require.NoError(t, err)
|
||||
err = addFile(repoDir, "podinfo.yaml", manifest)
|
||||
require.NoError(t, err)
|
||||
err = commitAndPushAll(repo, name, cfg.azdoPat)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = setupNamespace(ctx, cfg.kubeClient, repoUrl, cfg.azdoPat, name)
|
||||
require.NoError(t, err)
|
||||
acrSecret := corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "acr-docker", Namespace: name}}
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, &acrSecret, func() error {
|
||||
acrSecret.Type = corev1.SecretTypeDockerConfigJson
|
||||
acrSecret.StringData = map[string]string{
|
||||
".dockerconfigjson": fmt.Sprintf(`
|
||||
{
|
||||
"auths": {
|
||||
"%s": {
|
||||
"auth": "%s"
|
||||
}
|
||||
}
|
||||
}
|
||||
`, cfg.acr.url, b64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", cfg.acr.username, cfg.acr.password)))),
|
||||
}
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
imageRepository := reflectorv1beta1.ImageRepository{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "podinfo",
|
||||
Namespace: name,
|
||||
},
|
||||
}
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, &imageRepository, func() error {
|
||||
imageRepository.Spec = reflectorv1beta1.ImageRepositorySpec{
|
||||
Image: fmt.Sprintf("%s/container/podinfo", cfg.acr.url),
|
||||
Interval: metav1.Duration{
|
||||
Duration: 1 * time.Minute,
|
||||
},
|
||||
SecretRef: &meta.LocalObjectReference{
|
||||
Name: acrSecret.Name,
|
||||
},
|
||||
}
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
imagePolicy := reflectorv1beta1.ImagePolicy{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "podinfo",
|
||||
Namespace: name,
|
||||
},
|
||||
}
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, &imagePolicy, func() error {
|
||||
imagePolicy.Spec = reflectorv1beta1.ImagePolicySpec{
|
||||
ImageRepositoryRef: meta.LocalObjectReference{
|
||||
Name: imageRepository.Name,
|
||||
},
|
||||
Policy: reflectorv1beta1.ImagePolicyChoice{
|
||||
SemVer: &reflectorv1beta1.SemVerPolicy{
|
||||
Range: "1.0.x",
|
||||
},
|
||||
},
|
||||
}
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
imageAutomation := automationv1beta1.ImageUpdateAutomation{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "podinfo",
|
||||
Namespace: name,
|
||||
},
|
||||
}
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, &imageAutomation, func() error {
|
||||
imageAutomation.Spec = automationv1beta1.ImageUpdateAutomationSpec{
|
||||
Interval: metav1.Duration{
|
||||
Duration: 1 * time.Minute,
|
||||
},
|
||||
SourceRef: automationv1beta1.SourceReference{
|
||||
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
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Wait for image repository to be ready
|
||||
require.Eventually(t, func() bool {
|
||||
_, repoDir, err := getRepository(repoUrl, name, false, cfg.azdoPat)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
func TestKeyVaultSops(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
name := "key-vault-sops"
|
||||
repoUrl := cfg.applicationRepository.http
|
||||
secretYaml := `apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: "test"
|
||||
namespace: "key-vault-sops"
|
||||
stringData:
|
||||
foo: "bar"`
|
||||
|
||||
repo, tmpDir, err := getRepository(repoUrl, name, true, cfg.azdoPat)
|
||||
err = runCommand(ctx, tmpDir, "mkdir -p ./key-vault-sops")
|
||||
require.NoError(t, err)
|
||||
err = runCommand(ctx, tmpDir, fmt.Sprintf("echo \"%s\" > ./key-vault-sops/secret.enc.yaml", secretYaml))
|
||||
require.NoError(t, err)
|
||||
err = runCommand(ctx, tmpDir, fmt.Sprintf("sops --encrypt --encrypted-regex '^(data|stringData)$' --azure-kv %s --in-place ./key-vault-sops/secret.enc.yaml", cfg.sopsId))
|
||||
require.NoError(t, err)
|
||||
err = commitAndPushAll(repo, name, cfg.azdoPat)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = setupNamespace(ctx, cfg.kubeClient, repoUrl, cfg.azdoPat, name)
|
||||
require.NoError(t, err)
|
||||
|
||||
source := &sourcev1.GitRepository{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: name}}
|
||||
require.Eventually(t, func() bool {
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, source, func() error {
|
||||
source.Spec = sourcev1.GitRepositorySpec{
|
||||
GitImplementation: sourcev1.LibGit2Implementation,
|
||||
Reference: &sourcev1.GitRepositoryRef{
|
||||
Branch: name,
|
||||
},
|
||||
SecretRef: &meta.LocalObjectReference{
|
||||
Name: "https-credentials",
|
||||
},
|
||||
URL: repoUrl,
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}, 10*time.Second, 1*time.Second)
|
||||
kustomization := &kustomizev1.Kustomization{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: name}}
|
||||
require.Eventually(t, func() bool {
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, kustomization, func() error {
|
||||
kustomization.Spec = kustomizev1.KustomizationSpec{
|
||||
Path: "./key-vault-sops",
|
||||
SourceRef: kustomizev1.CrossNamespaceSourceReference{
|
||||
Kind: sourcev1.GitRepositoryKind,
|
||||
Name: source.Name,
|
||||
Namespace: source.Namespace,
|
||||
},
|
||||
Interval: metav1.Duration{Duration: 1 * time.Minute},
|
||||
Prune: true,
|
||||
Decryption: &kustomizev1.Decryption{
|
||||
Provider: "sops",
|
||||
},
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}, 10*time.Second, 1*time.Second)
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
nn := types.NamespacedName{Name: "test", Namespace: name}
|
||||
secret := &corev1.Secret{}
|
||||
err = cfg.kubeClient.Get(ctx, nn, secret)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}, 120*time.Second, 5*time.Second)
|
||||
}
|
||||
|
||||
func TestAzureDevOpsCommitStatus(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
name := "commit-status"
|
||||
repoUrl := cfg.applicationRepository.http
|
||||
manifest := fmt.Sprintf(`
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: foobar
|
||||
namespace: %s
|
||||
`, name)
|
||||
|
||||
repo, repoDir, err := getRepository(repoUrl, name, true, cfg.azdoPat)
|
||||
require.NoError(t, err)
|
||||
err = addFile(repoDir, "configmap.yaml", manifest)
|
||||
require.NoError(t, err)
|
||||
err = commitAndPushAll(repo, name, cfg.azdoPat)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = setupNamespace(ctx, cfg.kubeClient, repoUrl, cfg.azdoPat, name)
|
||||
require.NoError(t, err)
|
||||
|
||||
kustomization := &kustomizev1.Kustomization{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: name}}
|
||||
require.Eventually(t, func() bool {
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, kustomization, func() error {
|
||||
kustomization.Spec.HealthChecks = []meta.NamespacedObjectKindReference{
|
||||
{
|
||||
APIVersion: "v1",
|
||||
Kind: "ConfigMap",
|
||||
Name: "foobar",
|
||||
Namespace: name,
|
||||
},
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}, 10*time.Second, 1*time.Second)
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
err := verifyGitAndKustomization(ctx, cfg.kubeClient, 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.kubeClient, &secret, func() error {
|
||||
secret.StringData = map[string]string{
|
||||
"token": cfg.azdoPat,
|
||||
}
|
||||
return nil
|
||||
})
|
||||
provider := notiv1beta1.Provider{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "azuredevops",
|
||||
Namespace: name,
|
||||
},
|
||||
}
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, &provider, func() error {
|
||||
provider.Spec = notiv1beta1.ProviderSpec{
|
||||
Type: "azuredevops",
|
||||
Address: repoUrl,
|
||||
SecretRef: &meta.LocalObjectReference{
|
||||
Name: "azuredevops-token",
|
||||
},
|
||||
}
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
alert := notiv1beta1.Alert{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "azuredevops",
|
||||
Namespace: name,
|
||||
},
|
||||
}
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, &alert, func() error {
|
||||
alert.Spec = notiv1beta1.AlertSpec{
|
||||
ProviderRef: meta.LocalObjectReference{
|
||||
Name: provider.Name,
|
||||
},
|
||||
EventSources: []notiv1beta1.CrossNamespaceObjectReference{
|
||||
{
|
||||
Kind: "Kustomization",
|
||||
Name: name,
|
||||
Namespace: name,
|
||||
},
|
||||
},
|
||||
}
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
u, err := giturls.Parse(repoUrl)
|
||||
require.NoError(t, err)
|
||||
id := strings.TrimLeft(u.Path, "/")
|
||||
id = strings.TrimSuffix(id, ".git")
|
||||
comp := strings.Split(id, "/")
|
||||
orgUrl := fmt.Sprintf("%s://%s/%v", u.Scheme, u.Host, comp[0])
|
||||
project := comp[1]
|
||||
repoId := comp[3]
|
||||
branch, err := repo.LookupBranch(name, git2go.BranchLocal)
|
||||
require.NoError(t, err)
|
||||
commit, err := repo.LookupCommit(branch.Target())
|
||||
rev := commit.Id().String()
|
||||
connection := azuredevops.NewPatConnection(orgUrl, cfg.azdoPat)
|
||||
client, err := git.NewClient(ctx, connection)
|
||||
require.NoError(t, err)
|
||||
getArgs := git.GetStatusesArgs{
|
||||
Project: &project,
|
||||
RepositoryId: &repoId,
|
||||
CommitId: &rev,
|
||||
}
|
||||
require.Eventually(t, 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)
|
||||
}
|
||||
|
||||
func TestEventHubNotification(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
name := "event-hub"
|
||||
|
||||
// Start listening to eventhub with latest offset
|
||||
hub, err := eventhub.NewHubFromConnectionString(cfg.eventHubSas)
|
||||
require.NoError(t, err)
|
||||
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)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(runtimeInfo.PartitionIDs))
|
||||
listenerHandler, err := hub.Receive(ctx, runtimeInfo.PartitionIDs[0], handler, eventhub.ReceiveWithLatestOffset())
|
||||
require.NoError(t, err)
|
||||
|
||||
// Setup Flux resources
|
||||
repoUrl := cfg.applicationRepository.http
|
||||
manifest := fmt.Sprintf(`
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: foobar
|
||||
namespace: %s
|
||||
`, name)
|
||||
|
||||
repo, repoDir, err := getRepository(repoUrl, name, true, cfg.azdoPat)
|
||||
require.NoError(t, err)
|
||||
err = addFile(repoDir, "configmap.yaml", manifest)
|
||||
require.NoError(t, err)
|
||||
err = commitAndPushAll(repo, name, cfg.azdoPat)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = setupNamespace(ctx, cfg.kubeClient, repoUrl, cfg.azdoPat, name)
|
||||
require.NoError(t, err)
|
||||
|
||||
secret := corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: name,
|
||||
},
|
||||
}
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, &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.kubeClient, &provider, func() error {
|
||||
provider.Spec = notiv1beta1.ProviderSpec{
|
||||
Type: "azureeventhub",
|
||||
Address: repoUrl,
|
||||
SecretRef: &meta.LocalObjectReference{
|
||||
Name: name,
|
||||
},
|
||||
}
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
alert := notiv1beta1.Alert{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: name,
|
||||
},
|
||||
}
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, &alert, func() error {
|
||||
alert.Spec = notiv1beta1.AlertSpec{
|
||||
ProviderRef: meta.LocalObjectReference{
|
||||
Name: provider.Name,
|
||||
},
|
||||
EventSources: []notiv1beta1.CrossNamespaceObjectReference{
|
||||
{
|
||||
Kind: "Kustomization",
|
||||
Name: name,
|
||||
Namespace: name,
|
||||
},
|
||||
},
|
||||
}
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
kustomization := &kustomizev1.Kustomization{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: name}}
|
||||
require.Eventually(t, func() bool {
|
||||
_, err := controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, kustomization, func() error {
|
||||
kustomization.Spec.HealthChecks = []meta.NamespacedObjectKindReference{
|
||||
{
|
||||
APIVersion: "v1",
|
||||
Kind: "ConfigMap",
|
||||
Name: "foobar",
|
||||
Namespace: name,
|
||||
},
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}, 10*time.Second, 1*time.Second)
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
err := verifyGitAndKustomization(ctx, cfg.kubeClient, name, name)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}, 60*time.Second, 5*time.Second)
|
||||
|
||||
// Wait to read even from event hub
|
||||
require.Eventually(t, func() bool {
|
||||
select {
|
||||
case eventJson := <-c:
|
||||
event := &events.Event{}
|
||||
err := json.Unmarshal([]byte(eventJson), event)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return false
|
||||
}
|
||||
if event.InvolvedObject.Kind != "Kustomization" {
|
||||
return false
|
||||
}
|
||||
if event.Message != "Health check passed" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
//}, 700*time.Second, 10*time.Second)
|
||||
}, 60*time.Second, 1*time.Second)
|
||||
err = listenerHandler.Close(ctx)
|
||||
require.NoError(t, err)
|
||||
err = hub.Close(ctx)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// TODO: Enable when source-controller supports Helm charts from OCI sources.
|
||||
/*func TestACRHelmRelease(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
|
||||
// Create namespace for test
|
||||
namespace := corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "acr-helm-release",
|
||||
},
|
||||
}
|
||||
err := kubeClient.Create(ctx, &namespace)
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
kubeClient.Delete(ctx, &namespace)
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
// Copy ACR credentials to new namespace
|
||||
acrNn := types.NamespacedName{
|
||||
Name: "acr-helm",
|
||||
Namespace: "flux-system",
|
||||
}
|
||||
acrSecret := corev1.Secret{}
|
||||
err = kubeClient.Get(ctx, acrNn, &acrSecret)
|
||||
require.NoError(t, err)
|
||||
acrSecret.ObjectMeta = metav1.ObjectMeta{
|
||||
Name: acrSecret.Name,
|
||||
Namespace: namespace.Name,
|
||||
}
|
||||
err = kubeClient.Create(ctx, &acrSecret)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create HelmRepository and wait for it to sync
|
||||
helmRepository := sourcev1.HelmRepository{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "acr",
|
||||
Namespace: namespace.Name,
|
||||
},
|
||||
Spec: sourcev1.HelmRepositorySpec{
|
||||
URL: "https://acrappsoarfish.azurecr.io/helm/podinfo",
|
||||
Interval: metav1.Duration{
|
||||
Duration: 5 * time.Minute,
|
||||
},
|
||||
SecretRef: &meta.LocalObjectReference{
|
||||
Name: acrSecret.Name,
|
||||
},
|
||||
PassCredentials: true,
|
||||
},
|
||||
}
|
||||
err = kubeClient.Create(ctx, &helmRepository)
|
||||
require.NoError(t, err)
|
||||
}*/
|
@ -0,0 +1,24 @@
|
||||
module github.com/fluxcd/flux2/tests/azure
|
||||
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/Azure/azure-event-hubs-go/v3 v3.3.13
|
||||
github.com/fluxcd/helm-controller/api v0.11.2
|
||||
github.com/fluxcd/image-automation-controller/api v0.14.1
|
||||
github.com/fluxcd/image-reflector-controller/api v0.11.1
|
||||
github.com/fluxcd/kustomize-controller/api v0.14.1
|
||||
github.com/fluxcd/notification-controller/api v0.16.0
|
||||
github.com/fluxcd/pkg/apis/meta v0.10.1
|
||||
github.com/fluxcd/pkg/runtime v0.12.1
|
||||
github.com/fluxcd/source-controller/api v0.15.4
|
||||
github.com/hashicorp/terraform-exec v0.14.0
|
||||
github.com/libgit2/git2go/v31 v31.6.1
|
||||
github.com/microsoft/azure-devops-go-api/azuredevops v1.0.0-b5
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/whilp/git-urls v1.0.0
|
||||
k8s.io/api v0.22.2
|
||||
k8s.io/apimachinery v0.22.2
|
||||
k8s.io/client-go v0.22.2
|
||||
sigs.k8s.io/controller-runtime v0.10.1
|
||||
)
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,19 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type whichTerraform struct{}
|
||||
|
||||
func (w *whichTerraform) ExecPath(ctx context.Context) (string, error) {
|
||||
cmd := exec.CommandContext(ctx, "which", "terraform")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
path := strings.TrimSuffix(string(output), "\n")
|
||||
return path, nil
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
# This file is maintained automatically by "terraform init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.terraform.io/hashicorp/azuread" {
|
||||
version = "1.6.0"
|
||||
constraints = "1.6.0"
|
||||
hashes = [
|
||||
"h1:BlO53mX+Y2W//YqlCKvoxzofegFQk636XlKtmZYH0PY=",
|
||||
"zh:0db70045a464d325fdb3d71809f0467844c3e2fcf1349e568bc51ad5035c99d9",
|
||||
"zh:3629f1d7b4eba48d744b24c7cf7fe878d5ef5910a36b525507bd3d588010ccec",
|
||||
"zh:5a73a45b6d1ff353810cc9b00d7c90a2fb328ba0a9ef3d24392b1500fb98741a",
|
||||
"zh:7a6a9c390cf1bf752321abb8d0643c9f623e8c2ad871dfb378d64c9d90fada2d",
|
||||
"zh:7d6de55d326b046dabc16bd7b655f008ff780c36ffc884b139a7c7da37b446d5",
|
||||
"zh:8d725c618396ccae290e411296c892e08e776c3e9e5a82b0ef1f633a917146ec",
|
||||
"zh:a206d1d8042bf66ca12b97334bbd6fcdf12fd6131f8cb4547c82b9fa7a701612",
|
||||
"zh:b03ab4ff07dcb5ed8be8b0619c6ec9fb0da0c83594ccb0a1bff72f346083b530",
|
||||
"zh:b6131f9d438b340a4016c770b569139ec7ac2532358a8ab783234e8c93d141d5",
|
||||
"zh:ce9372d38e9e62accfd54f4669753000d3dcbae4b45686d74630eb63eb879f37",
|
||||
"zh:df9a607c333d464d8bdeb248b1ff41e493c1d0661453a1e1ce396b89952a74ee",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/hashicorp/azurerm" {
|
||||
version = "2.76.0"
|
||||
constraints = "2.76.0"
|
||||
hashes = [
|
||||
"h1:kF+u0s0DPnE5gMKhzQACWRUIdwZG1Ax4atXt9hk1J8M=",
|
||||
"zh:137eb7c07d3d3c9fe123e74381c108c4442efba9fc051faa2ca603503ff2840f",
|
||||
"zh:142a354dffd59a1d6b7f1614ab66a468ace3636d95933589a8d704ee8dbc4ea6",
|
||||
"zh:4c343b4da8b86e4213c1b11f73337cec73a55b1fa95a0e0e0c79f34597d37cc3",
|
||||
"zh:75d3109d48726fdbaad840d2fa294ec3362b32a3628c261af00f5c5608427521",
|
||||
"zh:7b1e78c144c6ad2beebc798abb9e76c725bf34ced41df36dc0120a0f2426e801",
|
||||
"zh:981235b01c3d4acf94c78cdd96624fd01d0a3622bc06b5c62aef3e788f1481c3",
|
||||
"zh:bad819efae7293ce371409e1ed34197c3e879f61d3e44893af0ce68e6aaffde7",
|
||||
"zh:c8008967722929deccfec9695754ae55028ce12311c321ae7a7c753dde162a44",
|
||||
"zh:d38513d1138864269b2ff333b08a64a7949630d489f18e660630bbaff3b7ebb8",
|
||||
"zh:e1f64d2d91b5f5cba6a9c5d35278a4918d332d7385a87f8e3466aaadb782a90f",
|
||||
"zh:e93a377a1e823df69718686703b07f1712046eeb742006022e982f2e8a594161",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/hashicorp/random" {
|
||||
version = "3.1.0"
|
||||
hashes = [
|
||||
"h1:BZMEPucF+pbu9gsPk0G0BHx7YP04+tKdq2MrRDF1EDM=",
|
||||
"zh:2bbb3339f0643b5daa07480ef4397bd23a79963cc364cdfbb4e86354cb7725bc",
|
||||
"zh:3cd456047805bf639fbf2c761b1848880ea703a054f76db51852008b11008626",
|
||||
"zh:4f251b0eda5bb5e3dc26ea4400dba200018213654b69b4a5f96abee815b4f5ff",
|
||||
"zh:7011332745ea061e517fe1319bd6c75054a314155cb2c1199a5b01fe1889a7e2",
|
||||
"zh:738ed82858317ccc246691c8b85995bc125ac3b4143043219bd0437adc56c992",
|
||||
"zh:7dbe52fac7bb21227acd7529b487511c91f4107db9cc4414f50d04ffc3cab427",
|
||||
"zh:a3a9251fb15f93e4cfc1789800fc2d7414bbc18944ad4c5c98f466e6477c42bc",
|
||||
"zh:a543ec1a3a8c20635cf374110bd2f87c07374cf2c50617eee2c669b3ceeeaa9f",
|
||||
"zh:d9ab41d556a48bd7059f0810cf020500635bfc696c9fc3adab5ea8915c1d886b",
|
||||
"zh:d9e13427a7d011dbd654e591b0337e6074eef8c3b9bb11b2e39eaaf257044fd7",
|
||||
"zh:f7605bd1437752114baf601bdf6931debe6dc6bfe3006eb7e9bb9080931dca8a",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/microsoft/azuredevops" {
|
||||
version = "0.1.7"
|
||||
constraints = "0.1.7"
|
||||
hashes = [
|
||||
"h1:AWNWqJ3XhlKp3xdJF+3WKdK1zVoCFYInQvi06exsBzg=",
|
||||
"zh:0c024992f2282ef73d4829e487ec8482dd98e9272b903f2e5979f5f62567ee4e",
|
||||
"zh:47fef8f57dfdca6aebe5a907b4866880007512019d9bec29805fc83501412309",
|
||||
"zh:692736c501c6b987a4a74c69fb7702a54969180706d1f67eff13e6ed2a0f9fec",
|
||||
"zh:6c3c4339206f5dcbc9d10fb2fe343652e7e14255223dcece5bf79ef9030858ef",
|
||||
"zh:77dfc63377b8d8fe24cbbe479ead18bfd1c7ded067fd694b6532434d6305ad31",
|
||||
"zh:93dba26dbade208a1cba43333f104a64252ca2404636ab033702da29648bfaaa",
|
||||
"zh:952d28b3e6c137de9b8700d2b748e5a4a2aa53ed07005f0f7abdd66b84cc63fe",
|
||||
"zh:a7b8238b8b2f04ad2d720a207377bfc2066d54b1d9d7285f2535afc43ff80fdb",
|
||||
"zh:bb23d8fc3cdd3c01d7620dadb2ba7b724706f2112d7738e135d1be1455682f5e",
|
||||
"zh:cb4da640beb5fc59296479c201a03351789496c04aaa57ae1530a7aac9095b92",
|
||||
"zh:ede6fb7ab598081fdddac56d470bae14448271dfd43a645bc02d136643391ebe",
|
||||
"zh:fd8291e6dc9118323a744660326a0f11de2a475c4a358e50f480feed1f3bb080",
|
||||
]
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
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 = data.azurerm_container_registry.shared.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,37 @@
|
||||
resource "azurerm_key_vault" "this" {
|
||||
name = "kv-${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" "sops_write" {
|
||||
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 = [
|
||||
"Encrypt",
|
||||
"Decrypt",
|
||||
"Create",
|
||||
"Delete",
|
||||
"Purge",
|
||||
"Get",
|
||||
"List",
|
||||
]
|
||||
}
|
||||
|
||||
resource "azurerm_key_vault_key" "sops" {
|
||||
depends_on = [azurerm_key_vault_access_policy.sops_write]
|
||||
|
||||
name = "sops"
|
||||
key_vault_id = azurerm_key_vault.this.id
|
||||
key_type = "RSA"
|
||||
key_size = 2048
|
||||
|
||||
key_opts = [
|
||||
"decrypt",
|
||||
"encrypt",
|
||||
]
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
terraform {
|
||||
backend "azurerm" {
|
||||
resource_group_name = "terraform-state"
|
||||
storage_account_name = "terraformstate0419"
|
||||
container_name = "aks-tfstate"
|
||||
key = "prod.terraform.tfstate"
|
||||
}
|
||||
|
||||
required_version = "1.0.7"
|
||||
|
||||
required_providers {
|
||||
azurerm = {
|
||||
source = "hashicorp/azurerm"
|
||||
version = "2.76.0"
|
||||
}
|
||||
azuread = {
|
||||
source = "hashicorp/azuread"
|
||||
version = "1.6.0"
|
||||
}
|
||||
azuredevops = {
|
||||
source = "microsoft/azuredevops"
|
||||
version = "0.1.7"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "azurerm" {
|
||||
features {}
|
||||
}
|
||||
|
||||
provider "azuredevops" {
|
||||
org_service_url = "https://dev.azure.com/${local.azure_devops_org}"
|
||||
personal_access_token = data.azurerm_key_vault_secret.shared_pat.value
|
||||
}
|
||||
|
||||
data "azurerm_client_config" "current" {}
|
||||
|
||||
resource "random_pet" "suffix" {}
|
||||
|
||||
locals {
|
||||
azure_devops_org = "flux-azure"
|
||||
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,76 @@
|
||||
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
|
||||
}
|
||||
|
||||
output "aks_client_certificate" {
|
||||
value = base64decode(azurerm_kubernetes_cluster.this.kube_config[0].client_certificate)
|
||||
}
|
||||
|
||||
output "aks_client_key" {
|
||||
value = base64decode(azurerm_kubernetes_cluster.this.kube_config[0].client_key)
|
||||
}
|
||||
|
||||
output "aks_cluster_ca_certificate" {
|
||||
value = base64decode(azurerm_kubernetes_cluster.this.kube_config[0].cluster_ca_certificate)
|
||||
}
|
||||
|
||||
output "shared_pat" {
|
||||
sensitive = true
|
||||
value = data.azurerm_key_vault_secret.shared_pat.value
|
||||
}
|
||||
|
||||
output "shared_id_rsa" {
|
||||
sensitive = true
|
||||
value = data.azurerm_key_vault_secret.shared_id_rsa.value
|
||||
}
|
||||
|
||||
output "shared_id_rsa_pub" {
|
||||
sensitive = true
|
||||
value = data.azurerm_key_vault_secret.shared_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 = azuread_service_principal.flux.application_id
|
||||
client_secret = azuread_service_principal_password.flux.value
|
||||
}
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
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 = data.azurerm_container_registry.shared.login_server
|
||||
username = azuread_service_principal.flux.application_id
|
||||
password = azuread_service_principal_password.flux.value
|
||||
}
|
||||
sensitive = true
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
resource "azuread_application" "flux" {
|
||||
display_name = "flux-${local.name_suffix}"
|
||||
|
||||
required_resource_access {
|
||||
resource_app_id = "00000003-0000-0000-c000-000000000000"
|
||||
|
||||
resource_access {
|
||||
id = "df021288-bdef-4463-88db-98f22de89214"
|
||||
type = "Role"
|
||||
}
|
||||
}
|
||||
|
||||
required_resource_access {
|
||||
resource_app_id = "00000002-0000-0000-c000-000000000000"
|
||||
|
||||
resource_access {
|
||||
id = "1cda74f2-2616-4834-b122-5cb1b07f8a59"
|
||||
type = "Role"
|
||||
}
|
||||
resource_access {
|
||||
id = "78c8a3c8-a07e-4b9e-af1b-b5ccab50a175"
|
||||
type = "Role"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "azuread_service_principal" "flux" {
|
||||
application_id = azuread_application.flux.application_id
|
||||
}
|
||||
|
||||
resource "azuread_service_principal_password" "flux" {
|
||||
service_principal_id = azuread_service_principal.flux.object_id
|
||||
}
|
||||
|
||||
resource "azurerm_role_assignment" "acr" {
|
||||
scope = data.azurerm_container_registry.shared.id
|
||||
role_definition_name = "AcrPull"
|
||||
principal_id = azuread_service_principal.flux.object_id
|
||||
}
|
||||
|
||||
resource "azurerm_key_vault_access_policy" "sops_decrypt" {
|
||||
key_vault_id = azurerm_key_vault.this.id
|
||||
tenant_id = data.azurerm_client_config.current.tenant_id
|
||||
object_id = azuread_service_principal.flux.object_id
|
||||
|
||||
key_permissions = [
|
||||
"Encrypt",
|
||||
"Decrypt",
|
||||
"Get",
|
||||
"List",
|
||||
]
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
locals {
|
||||
shared_suffix = "oarfish"
|
||||
}
|
||||
|
||||
data "azurerm_resource_group" "shared" {
|
||||
name = "e2e-shared"
|
||||
}
|
||||
|
||||
data "azurerm_container_registry" "shared" {
|
||||
name = "acrapps${local.shared_suffix}"
|
||||
resource_group_name = data.azurerm_resource_group.shared.name
|
||||
}
|
||||
|
||||
data "azurerm_key_vault" "shared" {
|
||||
resource_group_name = data.azurerm_resource_group.shared.name
|
||||
name = "kv-credentials-${local.shared_suffix}"
|
||||
}
|
||||
|
||||
data "azurerm_key_vault_secret" "shared_pat" {
|
||||
key_vault_id = data.azurerm_key_vault.shared.id
|
||||
name = "pat"
|
||||
}
|
||||
|
||||
data "azurerm_key_vault_secret" "shared_id_rsa" {
|
||||
key_vault_id = data.azurerm_key_vault.shared.id
|
||||
name = "id-rsa"
|
||||
}
|
||||
|
||||
data "azurerm_key_vault_secret" "shared_id_rsa_pub" {
|
||||
key_vault_id = data.azurerm_key_vault.shared.id
|
||||
name = "id-rsa-pub"
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
# This file is maintained automatically by "terraform init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.terraform.io/hashicorp/azuread" {
|
||||
version = "1.6.0"
|
||||
constraints = "1.6.0"
|
||||
hashes = [
|
||||
"h1:BlO53mX+Y2W//YqlCKvoxzofegFQk636XlKtmZYH0PY=",
|
||||
"zh:0db70045a464d325fdb3d71809f0467844c3e2fcf1349e568bc51ad5035c99d9",
|
||||
"zh:3629f1d7b4eba48d744b24c7cf7fe878d5ef5910a36b525507bd3d588010ccec",
|
||||
"zh:5a73a45b6d1ff353810cc9b00d7c90a2fb328ba0a9ef3d24392b1500fb98741a",
|
||||
"zh:7a6a9c390cf1bf752321abb8d0643c9f623e8c2ad871dfb378d64c9d90fada2d",
|
||||
"zh:7d6de55d326b046dabc16bd7b655f008ff780c36ffc884b139a7c7da37b446d5",
|
||||
"zh:8d725c618396ccae290e411296c892e08e776c3e9e5a82b0ef1f633a917146ec",
|
||||
"zh:a206d1d8042bf66ca12b97334bbd6fcdf12fd6131f8cb4547c82b9fa7a701612",
|
||||
"zh:b03ab4ff07dcb5ed8be8b0619c6ec9fb0da0c83594ccb0a1bff72f346083b530",
|
||||
"zh:b6131f9d438b340a4016c770b569139ec7ac2532358a8ab783234e8c93d141d5",
|
||||
"zh:ce9372d38e9e62accfd54f4669753000d3dcbae4b45686d74630eb63eb879f37",
|
||||
"zh:df9a607c333d464d8bdeb248b1ff41e493c1d0661453a1e1ce396b89952a74ee",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/hashicorp/azurerm" {
|
||||
version = "2.76.0"
|
||||
constraints = "2.76.0"
|
||||
hashes = [
|
||||
"h1:kF+u0s0DPnE5gMKhzQACWRUIdwZG1Ax4atXt9hk1J8M=",
|
||||
"zh:137eb7c07d3d3c9fe123e74381c108c4442efba9fc051faa2ca603503ff2840f",
|
||||
"zh:142a354dffd59a1d6b7f1614ab66a468ace3636d95933589a8d704ee8dbc4ea6",
|
||||
"zh:4c343b4da8b86e4213c1b11f73337cec73a55b1fa95a0e0e0c79f34597d37cc3",
|
||||
"zh:75d3109d48726fdbaad840d2fa294ec3362b32a3628c261af00f5c5608427521",
|
||||
"zh:7b1e78c144c6ad2beebc798abb9e76c725bf34ced41df36dc0120a0f2426e801",
|
||||
"zh:981235b01c3d4acf94c78cdd96624fd01d0a3622bc06b5c62aef3e788f1481c3",
|
||||
"zh:bad819efae7293ce371409e1ed34197c3e879f61d3e44893af0ce68e6aaffde7",
|
||||
"zh:c8008967722929deccfec9695754ae55028ce12311c321ae7a7c753dde162a44",
|
||||
"zh:d38513d1138864269b2ff333b08a64a7949630d489f18e660630bbaff3b7ebb8",
|
||||
"zh:e1f64d2d91b5f5cba6a9c5d35278a4918d332d7385a87f8e3466aaadb782a90f",
|
||||
"zh:e93a377a1e823df69718686703b07f1712046eeb742006022e982f2e8a594161",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/hashicorp/random" {
|
||||
version = "3.1.0"
|
||||
hashes = [
|
||||
"h1:BZMEPucF+pbu9gsPk0G0BHx7YP04+tKdq2MrRDF1EDM=",
|
||||
"zh:2bbb3339f0643b5daa07480ef4397bd23a79963cc364cdfbb4e86354cb7725bc",
|
||||
"zh:3cd456047805bf639fbf2c761b1848880ea703a054f76db51852008b11008626",
|
||||
"zh:4f251b0eda5bb5e3dc26ea4400dba200018213654b69b4a5f96abee815b4f5ff",
|
||||
"zh:7011332745ea061e517fe1319bd6c75054a314155cb2c1199a5b01fe1889a7e2",
|
||||
"zh:738ed82858317ccc246691c8b85995bc125ac3b4143043219bd0437adc56c992",
|
||||
"zh:7dbe52fac7bb21227acd7529b487511c91f4107db9cc4414f50d04ffc3cab427",
|
||||
"zh:a3a9251fb15f93e4cfc1789800fc2d7414bbc18944ad4c5c98f466e6477c42bc",
|
||||
"zh:a543ec1a3a8c20635cf374110bd2f87c07374cf2c50617eee2c669b3ceeeaa9f",
|
||||
"zh:d9ab41d556a48bd7059f0810cf020500635bfc696c9fc3adab5ea8915c1d886b",
|
||||
"zh:d9e13427a7d011dbd654e591b0337e6074eef8c3b9bb11b2e39eaaf257044fd7",
|
||||
"zh:f7605bd1437752114baf601bdf6931debe6dc6bfe3006eb7e9bb9080931dca8a",
|
||||
]
|
||||
}
|
@ -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,43 @@
|
||||
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",
|
||||
]
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
terraform {
|
||||
backend "azurerm" {
|
||||
resource_group_name = "terraform-state"
|
||||
storage_account_name = "terraformstate0419"
|
||||
container_name = "shared-tfstate"
|
||||
key = "prod.terraform.tfstate"
|
||||
}
|
||||
|
||||
required_version = "1.0.7"
|
||||
|
||||
required_providers {
|
||||
azurerm = {
|
||||
source = "hashicorp/azurerm"
|
||||
version = "2.76.0"
|
||||
}
|
||||
azuread = {
|
||||
source = "hashicorp/azuread"
|
||||
version = "1.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "azurerm" {
|
||||
features {}
|
||||
}
|
||||
|
||||
resource "random_pet" "suffix" {
|
||||
length = 1
|
||||
separator = ""
|
||||
}
|
||||
|
||||
data "azurerm_client_config" "current" {}
|
||||
|
||||
data "azurerm_subscription" "current" {}
|
||||
|
||||
resource "azurerm_resource_group" "this" {
|
||||
name = "e2e-shared"
|
||||
location = "West Europe"
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
output "azure_devops_sp" {
|
||||
value = {
|
||||
client_id = azuread_service_principal.azure_devops.application_id
|
||||
client_secret = azuread_application_password.azure_devops.value
|
||||
}
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "github_sp" {
|
||||
value = {
|
||||
tenant_id = data.azurerm_client_config.current.tenant_id
|
||||
subscription_id = data.azurerm_client_config.current.subscription_id
|
||||
client_id = azuread_service_principal.github.application_id
|
||||
client_secret = azuread_application_password.github.value
|
||||
}
|
||||
sensitive = true
|
||||
}
|
||||
|
@ -0,0 +1,105 @@
|
||||
# Service Principal used by Azure DevOps to push OCI and Helm Charts
|
||||
resource "azuread_application" "azure_devops" {
|
||||
display_name = "azure-devops-${random_pet.suffix.id}"
|
||||
|
||||
required_resource_access {
|
||||
resource_app_id = "00000003-0000-0000-c000-000000000000"
|
||||
|
||||
resource_access {
|
||||
id = "df021288-bdef-4463-88db-98f22de89214"
|
||||
type = "Role"
|
||||
}
|
||||
}
|
||||
|
||||
required_resource_access {
|
||||
resource_app_id = "00000002-0000-0000-c000-000000000000"
|
||||
|
||||
resource_access {
|
||||
id = "1cda74f2-2616-4834-b122-5cb1b07f8a59"
|
||||
type = "Role"
|
||||
}
|
||||
resource_access {
|
||||
id = "78c8a3c8-a07e-4b9e-af1b-b5ccab50a175"
|
||||
type = "Role"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "azuread_application_password" "azure_devops" {
|
||||
display_name = "password"
|
||||
application_object_id = azuread_application.azure_devops.object_id
|
||||
}
|
||||
|
||||
resource "azuread_service_principal" "azure_devops" {
|
||||
application_id = azuread_application.azure_devops.application_id
|
||||
}
|
||||
|
||||
resource "azurerm_role_assignment" "azure_devops_acr" {
|
||||
scope = azurerm_container_registry.this.id
|
||||
role_definition_name = "Contributor"
|
||||
principal_id = azuread_service_principal.azure_devops.object_id
|
||||
}
|
||||
|
||||
# Service Principal that is used to run the tests in GitHub Actions
|
||||
resource "azuread_application" "github" {
|
||||
display_name = "github-${random_pet.suffix.id}"
|
||||
|
||||
required_resource_access {
|
||||
resource_app_id = "00000003-0000-0000-c000-000000000000"
|
||||
|
||||
resource_access {
|
||||
id = "df021288-bdef-4463-88db-98f22de89214"
|
||||
type = "Role"
|
||||
}
|
||||
}
|
||||
|
||||
required_resource_access {
|
||||
resource_app_id = "00000002-0000-0000-c000-000000000000"
|
||||
|
||||
resource_access {
|
||||
id = "1cda74f2-2616-4834-b122-5cb1b07f8a59"
|
||||
type = "Role"
|
||||
}
|
||||
resource_access {
|
||||
id = "78c8a3c8-a07e-4b9e-af1b-b5ccab50a175"
|
||||
type = "Role"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "azuread_application_password" "github" {
|
||||
display_name = "password"
|
||||
application_object_id = azuread_application.github.object_id
|
||||
}
|
||||
|
||||
resource "azuread_service_principal" "github" {
|
||||
application_id = azuread_application.github.application_id
|
||||
}
|
||||
|
||||
data "azurerm_storage_account" "terraform_state" {
|
||||
resource_group_name = "terraform-state"
|
||||
name = "terraformstate0419"
|
||||
}
|
||||
|
||||
resource "azurerm_role_assignment" "github_resource_group" {
|
||||
scope = data.azurerm_subscription.current.id
|
||||
role_definition_name = "Contributor"
|
||||
principal_id = azuread_service_principal.github.object_id
|
||||
}
|
||||
|
||||
resource "azurerm_role_assignment" "github_acr" {
|
||||
scope = azurerm_container_registry.this.id
|
||||
role_definition_name = "Owner"
|
||||
principal_id = azuread_service_principal.github.object_id
|
||||
}
|
||||
|
||||
resource "azurerm_key_vault_access_policy" "github_keyvault_secret_read" {
|
||||
key_vault_id = azurerm_key_vault.this.id
|
||||
tenant_id = data.azurerm_client_config.current.tenant_id
|
||||
object_id = azuread_service_principal.github.object_id
|
||||
|
||||
secret_permissions = [
|
||||
"Get",
|
||||
"List",
|
||||
]
|
||||
}
|
@ -0,0 +1,461 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
git2go "github.com/libgit2/git2go/v31"
|
||||
|
||||
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"
|
||||
"k8s.io/client-go/rest"
|
||||
"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/v1beta1"
|
||||
notiv1beta1 "github.com/fluxcd/notification-controller/api/v1beta1"
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
||||
)
|
||||
|
||||
const defaultBranch = "main"
|
||||
|
||||
// getKubernetesCredentials returns a path to a kubeconfig file and a kube client instance.
|
||||
func getKubernetesCredentials(kubeconfig, aksHost, aksCert, aksKey, aksCa string) (string, client.Client, error) {
|
||||
tmpDir, err := ioutil.TempDir("", "*-azure-e2e")
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
kubeconfigPath := fmt.Sprintf("%s/kubeconfig", tmpDir)
|
||||
os.WriteFile(kubeconfigPath, []byte(kubeconfig), 0750)
|
||||
kubeCfg := &rest.Config{
|
||||
Host: aksHost,
|
||||
TLSClientConfig: rest.TLSClientConfig{
|
||||
CertData: []byte(aksCert),
|
||||
KeyData: []byte(aksKey),
|
||||
CAData: []byte(aksCa),
|
||||
},
|
||||
}
|
||||
err = sourcev1.AddToScheme(scheme.Scheme)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
err = kustomizev1.AddToScheme(scheme.Scheme)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
err = helmv2beta1.AddToScheme(scheme.Scheme)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
err = reflectorv1beta1.AddToScheme(scheme.Scheme)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
err = automationv1beta1.AddToScheme(scheme.Scheme)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
err = notiv1beta1.AddToScheme(scheme.Scheme)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
kubeClient, err := client.New(kubeCfg, client.Options{Scheme: scheme.Scheme})
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return kubeconfigPath, kubeClient, nil
|
||||
}
|
||||
|
||||
// installFlux adds the core Flux components to the cluster specified in the kubeconfig file.
|
||||
func installFlux(ctx context.Context, kubeClient client.Client, kubeconfigPath, repoUrl, azdoPat string, sp spConfig) error {
|
||||
namespace := corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "flux-system",
|
||||
},
|
||||
}
|
||||
_, err := controllerutil.CreateOrUpdate(ctx, cfg.kubeClient, &namespace, func() error {
|
||||
return nil
|
||||
})
|
||||
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": azdoPat,
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
azureSp := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "azure-sp", Namespace: "flux-system"}}
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, kubeClient, azureSp, func() error {
|
||||
azureSp.StringData = map[string]string{
|
||||
"AZURE_TENANT_ID": sp.tenantId,
|
||||
"AZURE_CLIENT_ID": sp.clientId,
|
||||
"AZURE_CLIENT_SECRET": sp.clientSecret,
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Install Flux and push files to git repository
|
||||
repo, repoDir, err := getRepository(repoUrl, defaultBranch, true, azdoPat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = runCommand(ctx, repoDir, "mkdir -p ./clusters/e2e/flux-system")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = runCommand(ctx, repoDir, "flux install --components-extra=\"image-reflector-controller,image-automation-controller\" --export > ./clusters/e2e/flux-system/gotk-components.yaml")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = 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", repoUrl, defaultBranch))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = 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")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
kustomizeYaml := `
|
||||
resources:
|
||||
- gotk-components.yaml
|
||||
- gotk-sync.yaml
|
||||
patchesStrategicMerge:
|
||||
- |-
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: kustomize-controller
|
||||
namespace: flux-system
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: manager
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: azure-sp
|
||||
- |-
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: source-controller
|
||||
namespace: flux-system
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: manager
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: azure-sp
|
||||
`
|
||||
err = runCommand(ctx, repoDir, fmt.Sprintf("echo \"%s\" > ./clusters/e2e/flux-system/kustomization.yaml", kustomizeYaml))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = commitAndPushAll(repo, defaultBranch, azdoPat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Need to apply CRDs first to make sure that the sync resources will apply properly
|
||||
err = runCommand(ctx, repoDir, fmt.Sprintf("kubectl --kubeconfig=%s apply -f ./clusters/e2e/flux-system/gotk-components.yaml", kubeconfigPath))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = runCommand(ctx, repoDir, fmt.Sprintf("kubectl --kubeconfig=%s apply -k ./clusters/e2e/flux-system/", kubeconfigPath))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runCommand(ctx context.Context, dir, command string) error {
|
||||
timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Minute)
|
||||
defer cancel()
|
||||
cmd := exec.CommandContext(timeoutCtx, "bash", "-c", command)
|
||||
cmd.Dir = dir
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failure to run command %s: %v", string(output), 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
|
||||
}
|
||||
|
||||
func setupNamespace(ctx context.Context, kubeClient client.Client, repoUrl, password, name string) error {
|
||||
namespace := corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
},
|
||||
}
|
||||
_, err := controllerutil.CreateOrUpdate(ctx, kubeClient, &namespace, func() error {
|
||||
return nil
|
||||
})
|
||||
secret := corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "https-credentials",
|
||||
Namespace: name,
|
||||
},
|
||||
}
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, kubeClient, &secret, func() error {
|
||||
secret.StringData = map[string]string{
|
||||
"username": "git",
|
||||
"password": password,
|
||||
}
|
||||
return nil
|
||||
})
|
||||
source := &sourcev1.GitRepository{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace.Name}}
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, kubeClient, source, func() error {
|
||||
source.Spec = sourcev1.GitRepositorySpec{
|
||||
Interval: metav1.Duration{
|
||||
Duration: 1 * time.Minute,
|
||||
},
|
||||
GitImplementation: sourcev1.LibGit2Implementation,
|
||||
Reference: &sourcev1.GitRepositoryRef{
|
||||
Branch: name,
|
||||
},
|
||||
SecretRef: &meta.LocalObjectReference{
|
||||
Name: "https-credentials",
|
||||
},
|
||||
URL: repoUrl,
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
kustomization := &kustomizev1.Kustomization{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace.Name}}
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, kubeClient, kustomization, func() error {
|
||||
kustomization.Spec = kustomizev1.KustomizationSpec{
|
||||
SourceRef: kustomizev1.CrossNamespaceSourceReference{
|
||||
Kind: sourcev1.GitRepositoryKind,
|
||||
Name: source.Name,
|
||||
Namespace: source.Namespace,
|
||||
},
|
||||
Interval: metav1.Duration{
|
||||
Duration: 1 * time.Minute,
|
||||
},
|
||||
Prune: true,
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getRepository(url, branchName string, overrideBranch bool, password string) (*git2go.Repository, string, error) {
|
||||
checkoutBranch := defaultBranch
|
||||
if overrideBranch == false {
|
||||
checkoutBranch = branchName
|
||||
}
|
||||
|
||||
tmpDir, err := ioutil.TempDir("", "*-repository")
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
repo, err := git2go.Clone(url, tmpDir, &git2go.CloneOptions{
|
||||
FetchOptions: &git2go.FetchOptions{
|
||||
RemoteCallbacks: git2go.RemoteCallbacks{
|
||||
CredentialsCallback: credentialCallback("git", password),
|
||||
},
|
||||
},
|
||||
CheckoutBranch: checkoutBranch,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
// Nothing to do further if correct branch is checked out
|
||||
if checkoutBranch == branchName {
|
||||
return repo, tmpDir, nil
|
||||
}
|
||||
head, err := repo.Head()
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
headCommit, err := repo.LookupCommit(head.Target())
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
_, err = repo.CreateBranch(branchName, headCommit, true)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return repo, tmpDir, 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(repo *git2go.Repository, branchName, password string) error {
|
||||
idx, err := repo.Index()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = idx.AddAll([]string{}, git2go.IndexAddDefault, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
treeId, err := idx.WriteTree()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = idx.Write()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tree, err := repo.LookupTree(treeId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
branch, err := repo.LookupBranch(branchName, git2go.BranchLocal)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
commitTarget, err := repo.LookupCommit(branch.Target())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sig := &git2go.Signature{
|
||||
Name: "git",
|
||||
Email: "test@example.com",
|
||||
When: time.Now(),
|
||||
}
|
||||
_, err = repo.CreateCommit(fmt.Sprintf("refs/heads/%s", branchName), sig, sig, "add file", tree, commitTarget)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
origin, err := repo.Remotes.Lookup("origin")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = origin.Push([]string{fmt.Sprintf("+refs/heads/%s", branchName)}, &git2go.PushOptions{
|
||||
RemoteCallbacks: git2go.RemoteCallbacks{
|
||||
CredentialsCallback: credentialCallback("git", password),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createTagAndPush(repo *git2go.Repository, branchName, tag, password string) error {
|
||||
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 getTestManifest(namespace string) string {
|
||||
return fmt.Sprintf(`
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: %s
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: foobar
|
||||
namespace: %s
|
||||
`, namespace, namespace)
|
||||
}
|
Loading…
Reference in New Issue