Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .github/workflows/pr-cloud.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: pr-cloud-acceptance
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this workflow was removed as public cloud workflows were inconsistent, let's not add it until all those issues are resolved. I think this was needed for GKE tests, but please remove this workflow for now. Let's add it back after all cloud tests issues are resolved.

on:
pull_request:
types: [opened, synchronize]
# Runs on PRs to main and all release branches
branches:
- main
- "release/**"
# these should be the only settings that you will ever need to change
env:
BRANCH: ${{ github.event.pull_request.head.ref }}
CONTEXT: "pr"

jobs:
cloud-acceptance:
name: cloud-acceptance
runs-on: ubuntu-24.04
steps:
- uses: benc-uk/workflow-dispatch@25b02cc069be46d637e8fe2f1e8484008e9e9609 # v1.2.3
name: cloud
with:
workflow: cloud.yml
repo: hashicorp/consul-k8s-workflows
ref: main
token: ${{ secrets.ELEVATED_GITHUB_TOKEN }}
inputs: '{ "context":"${{ env.CONTEXT }}-${{ github.event.pull_request.number }}", "repository":"${{ github.repository }}", "branch":"${{ env.BRANCH }}", "sha":"${{ github.sha }}", "token":"${{ secrets.ELEVATED_GITHUB_TOKEN }}" }'
245 changes: 205 additions & 40 deletions acceptance/tests/consul-dns/consul_dns_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,54 +185,219 @@ func createACLTokenWithGivenPolicy(t *testing.T, consulClient *api.Client, polic
}

func updateCoreDNSWithConsulDomain(t *testing.T, ctx environment.TestContext, releaseName string, enableDNSProxy bool, port string) {
updateCoreDNSFile(t, ctx, releaseName, enableDNSProxy, port, "coredns-custom.yaml")
updateCoreDNS(t, ctx, "coredns-custom.yaml")

t.Cleanup(func() {
updateCoreDNS(t, ctx, "coredns-original.yaml")
time.Sleep(5 * time.Second)
})
actualName := getCoreDNSConfigMapName(t, ctx)

// 1. BACKUP: Capture the current state
logger.Log(t, "Backing up original CoreDNS config...")
originalConfig, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t),
"get", "configmap", actualName, "-n", "kube-system", "-o", "yaml")
require.NoError(t, err)

// --- FIX: Sanitize the YAML to remove version locking ---
// We remove fields that cause "Conflict" errors during restore.
lines := strings.Split(originalConfig, "\n")
var sanitizedLines []string
for _, line := range lines {
trimmed := strings.TrimSpace(line)
// Skip metadata fields that tie the file to a specific point in time
if strings.HasPrefix(trimmed, "resourceVersion:") ||
strings.HasPrefix(trimmed, "uid:") ||
strings.HasPrefix(trimmed, "creationTimestamp:") ||
strings.HasPrefix(trimmed, "generation:") {
continue
}
sanitizedLines = append(sanitizedLines, line)
}
cleanConfig := strings.Join(sanitizedLines, "\n")
// ---------------------------------------------------------

// Write the sanitized backup file
err = os.WriteFile("coredns-original.yaml", []byte(cleanConfig), 0644)
require.NoError(t, err)

// 2. GENERATE & APPLY Custom Config
updateCoreDNSFile(t, ctx, releaseName, enableDNSProxy, port, "coredns-custom.yaml")
updateCoreDNS(t, ctx, "coredns-custom.yaml")

// 3. CLEANUP: Restore the backup
t.Cleanup(func() {
logger.Log(t, "Restoring original CoreDNS configuration...")
updateCoreDNS(t, ctx, "coredns-original.yaml")
time.Sleep(5 * time.Second)
})
}

func updateCoreDNSFile(t *testing.T, ctx environment.TestContext, releaseName string,
enableDNSProxy bool, port string, dnsFileName string) {
dnsIP, err := getDNSServiceClusterIP(t, ctx, releaseName, enableDNSProxy)
require.NoError(t, err)

dnsTarget := dnsIP
if enableDNSProxy {
dnsTarget = net.JoinHostPort(dnsIP, port)
}

input, err := os.ReadFile("coredns-template.yaml")
require.NoError(t, err)
newContents := strings.Replace(string(input), "{{CONSUL_DNS_IP}}", dnsTarget, -1)
err = os.WriteFile(dnsFileName, []byte(newContents), os.FileMode(0644))
require.NoError(t, err)
enableDNSProxy bool, port string, dnsFileName string) {

// Calculate target IP
dnsIP, err := getDNSServiceClusterIP(t, ctx, releaseName, enableDNSProxy)
require.NoError(t, err)

actualName := getCoreDNSConfigMapName(t, ctx)

dnsTarget := dnsIP
if enableDNSProxy {
dnsTarget = net.JoinHostPort(dnsIP, port)
}

// --- STRATEGY A: GKE / Legacy (kube-dns) ---
// GKE ignores Corefile changes or breaks if we overwrite it. We must use stubDomains.
if strings.Contains(actualName, "kube-dns") {
logger.Log(t, "Detected GKE/kube-dns. Using stubDomains strategy.", "target", dnsTarget)

// stubDomains expects a JSON map: domain -> [ips]
stubDomainJSON := fmt.Sprintf(`{"consul": ["%s"]}`, dnsTarget)

// We create a ConfigMap that ONLY updates the "stubDomains" key.
// This merges safely with GKE's internal config.
configMapYAML := fmt.Sprintf(`
apiVersion: v1
kind: ConfigMap
metadata:
name: %s
namespace: kube-system
data:
stubDomains: |
%s
`, actualName, stubDomainJSON)

err = os.WriteFile(dnsFileName, []byte(configMapYAML), 0644)
require.NoError(t, err)
return
}

// --- STRATEGY B: EKS / AKS / Standard (coredns) ---
// Standard clusters allow us to overwrite the Corefile.
logger.Log(t, "Detected Standard CoreDNS. Using Corefile template strategy.", "target", dnsTarget)

input, err := os.ReadFile("coredns-template.yaml")
require.NoError(t, err)

newContents := strings.Replace(string(input), "{{CONSUL_DNS_IP}}", dnsTarget, -1)
newContents = strings.ReplaceAll(newContents, "name: coredns", fmt.Sprintf("name: %s", actualName))

err = os.WriteFile(dnsFileName, []byte(newContents), os.FileMode(0644))
require.NoError(t, err)
}

func updateCoreDNS(t *testing.T, ctx environment.TestContext, coreDNSConfigFile string) {
coreDNSCommand := []string{
"replace", "-n", "kube-system", "-f", coreDNSConfigFile,
}
var logs string

timer := &retry.Timer{Timeout: 30 * time.Minute, Wait: 60 * time.Second}
retry.RunWith(timer, t, func(r *retry.R) {
var err error
logs, err = k8s.RunKubectlAndGetOutputE(r, ctx.KubectlOptions(r), coreDNSCommand...)
require.NoError(r, err)
})

require.Contains(t, logs, "configmap/coredns replaced")
restartCoreDNSCommand := []string{"rollout", "restart", "deployment/coredns", "-n", "kube-system"}
_, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), restartCoreDNSCommand...)
require.NoError(t, err)
// Wait for restart to finish.
out, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "rollout", "status", "--timeout", "5m", "--watch", "deployment/coredns", "-n", "kube-system")
require.NoError(t, err, out, "rollout status command errored, this likely means the rollout didn't complete in time")
actualName := getCoreDNSConfigMapName(t, ctx)

// --- STEP 0: PATCH THE FILE ---
content, err := os.ReadFile(coreDNSConfigFile)
require.NoError(t, err)

strContent := string(content)
fileChanged := false

// Ensure name matches the cluster (kube-dns vs coredns)
if actualName == "kube-dns" && strings.Contains(strContent, "name: coredns") {
logger.Log(t, "Patching config file to target kube-dns instead of coredns")
strContent = strings.ReplaceAll(strContent, "name: coredns", "name: kube-dns")
fileChanged = true
} else if actualName == "coredns" && strings.Contains(strContent, "name: kube-dns") {
logger.Log(t, "Patching config file to target coredns instead of kube-dns")
strContent = strings.ReplaceAll(strContent, "name: kube-dns", "name: coredns")
fileChanged = true
} else if actualName == "rke2-coredns" && !strings.Contains(strContent, "name: rke2-coredns") {
logger.Log(t, "Patching config file to target rke2-coredns")
strContent = strings.ReplaceAll(strContent, "name: coredns", "name: rke2-coredns")
fileChanged = true
}

if fileChanged {
err = os.WriteFile(coreDNSConfigFile, []byte(strContent), 0644)
require.NoError(t, err)
}

// --- STEP 1: APPLY ---
coreDNSCommand := []string{
"apply", "-n", "kube-system", "-f", coreDNSConfigFile,
}
var logs string

timer := &retry.Timer{Timeout: 30 * time.Minute, Wait: 60 * time.Second}
retry.RunWith(timer, t, func(r *retry.R) {
var err error
logs, err = k8s.RunKubectlAndGetOutputE(r, ctx.KubectlOptions(r), coreDNSCommand...)
require.NoError(r, err)
})

// --- STEP 2: VALIDATE OUTPUT ---
msgConfigured := fmt.Sprintf("configmap/%s configured", actualName)
msgReplaced := fmt.Sprintf("configmap/%s replaced", actualName)
msgUnchanged := fmt.Sprintf("configmap/%s unchanged", actualName)

require.True(t,
strings.Contains(logs, msgConfigured) || strings.Contains(logs, msgReplaced) || strings.Contains(logs, msgUnchanged),
"expected CoreDNS update output to contain '%s', '%s', or '%s' but got: \n%s",
msgConfigured, msgReplaced, msgUnchanged, logs)

// --- STEP 3: RESTART DEPLOYMENT ---
deploymentName := "deployment/coredns"
if strings.Contains(actualName, "kube-dns") {
deploymentName = "deployment/kube-dns"
} else if strings.Contains(actualName, "rke2") {
deploymentName = "deployment/rke2-coredns"
}

logger.Log(t, "Restarting DNS deployment", "name", deploymentName)

restartCoreDNSCommand := []string{"rollout", "restart", deploymentName, "-n", "kube-system"}
_, err = k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), restartCoreDNSCommand...)
require.NoError(t, err)

// --- STEP 4: WAIT FOR ROLLOUT ---
out, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "rollout", "status", "--timeout", "5m", "--watch", deploymentName, "-n", "kube-system")
require.NoError(t, err, out, "rollout status command errored, this likely means the rollout didn't complete in time")
}

func getCoreDNSConfigMapName(t *testing.T, ctx environment.TestContext) string {
client := ctx.KubernetesClient(t).CoreV1().ConfigMaps("kube-system")
ctxBg := context.Background()

// 1. Check Labels (Standard method)
labelSelectors := []string{
"k8s-app=coredns",
"app.kubernetes.io/name=coredns",
"k8s-app=kube-dns",
}

for _, label := range labelSelectors {
cms, err := client.List(ctxBg, metav1.ListOptions{LabelSelector: label})
if err == nil && len(cms.Items) > 0 {
for _, cm := range cms.Items {
if _, ok := cm.Data["Corefile"]; ok {
logger.Log(t, "found DNS configmap via label with Corefile", "label", label, "name", cm.Name)
return cm.Name
}
}
}
}

// 2. Check Known Names (Fallback for GKE)
knownNames := []string{"coredns", "kube-dns", "rke2-coredns"}
for _, name := range knownNames {
cm, err := client.Get(ctxBg, name, metav1.GetOptions{})
if err == nil {
logger.Log(t, "found DNS configmap via name", "name", cm.Name)
return cm.Name
}
}

// Debugging failure
cms, err := client.List(ctxBg, metav1.ListOptions{})
var observedNames []string
if err == nil {
for _, cm := range cms.Items {
observedNames = append(observedNames, cm.Name)
}
}
t.Fatalf("Failed to find CoreDNS ConfigMap. Checked labels: %v, Checked names: %v. Available: %v",
labelSelectors, knownNames, observedNames)
return ""
}
func verifyDNS(
t *testing.T,
cfg *config.TestConfig,
Expand Down
17 changes: 13 additions & 4 deletions charts/consul/test/terraform/gke/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ data "google_container_engine_versions" "main" {
resource "google_compute_network" "custom_network" {
name = "network-${random_string.cluster_prefix.result}"
auto_create_subnetworks = false
lifecycle {
prevent_destroy = false
ignore_changes = [name]
}
}

resource "google_compute_subnetwork" "subnet" {
Expand All @@ -58,7 +62,10 @@ resource "google_container_cluster" "cluster" {
tags = ["consul-k8s-${random_string.cluster_prefix.result}-${random_id.suffix[count.index].dec}"]
machine_type = "e2-standard-8"
}
subnetwork = google_compute_subnetwork.subnet[count.index].self_link
subnetwork = google_compute_subnetwork.subnet[count.index].self_link
ip_allocation_policy {
cluster_ipv4_cidr_block = cidrsubnet("10.100.0.0/14", 2, count.index)
}
resource_labels = var.labels
deletion_protection = false
}
Expand All @@ -76,9 +83,11 @@ resource "google_compute_firewall" "firewall-rules" {
protocol = "all"
}

source_ranges = [google_container_cluster.cluster[count.index == 0 ? 1 : 0].cluster_ipv4_cidr]
source_tags = ["cluster-${random_string.cluster_prefix.result}-${count.index == 0 ? 1 : 0}"]
target_tags = ["cluster-${random_string.cluster_prefix.result}-${count.index}"]
source_ranges = [
google_container_cluster.cluster[count.index == 0 ? 1 : 0].cluster_ipv4_cidr,
google_compute_subnetwork.subnet[count.index == 0 ? 1 : 0].ip_cidr_range
]
target_tags = ["consul-k8s-${random_string.cluster_prefix.result}-${random_id.suffix[count.index].dec}"]
}

resource "null_resource" "kubectl" {
Expand Down
Loading