# Technical Documentation: hashicorp/consul

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/hashicorp/consul)

### Overview (`README.md`)

<h1>
  <img src="./ui/logo.svg" align="left" height="46px" alt="Consul logo"/>
  <span>Consul</span>
</h1>

[](LICENSE)
[](https://hub.docker.com/r/hashicorp/consul)
[](https://goreportcard.com/report/github.com/hashicorp/consul)
[](https://github.com/hashicorp/consul/actions/workflows/go-tests.yml)

Consul is a distributed, highly available, and data center aware solution to connect and configure applications across dynamic, distributed infrastructure.

* Documentation and Tutorials: [https://developer.hashicorp.com/consul]
* Forum: [Discuss](https://discuss.hashicorp.com/c/consul)

Consul provides several key features:

* **Multi-Datacenter** - Consul is built to be datacenter aware, and can
  support any number of regions without complex configuration.

* **Service Mesh** - Consul Service Mesh enables secure service-to-service
  communication with automatic TLS encryption and identity-based authorization. Applications
  can use sidecar proxies in a service mesh configuration to establish TLS
  connections for inbound and outbound connections with Transparent Proxy.

* **API Gateway** - Consul API Gateway manages access to services within Consul Service Mesh, 
  allow users to define traffic and authorization policies to services deployed within the mesh.  

* **Service Discovery** - Consul makes it simple for services to register
  themselves and to discover other services via a DNS or HTTP interface.
  External services such as SaaS providers can be registered as well.

* **Health Checking** - Health Checking enables Consul to quickly alert
  operators about any issues in a cluster. The integration with service
  discovery prevents routing traffic to unhealthy hosts and enables service
  level circuit breakers.

* **Dynamic App Configuration** - An HTTP API that allows users to store indexed objects within Consul,
  for storing configuration parameters and application metadata.

Consul runs on Linux, macOS, FreeBSD, Solaris, and Windows and includes an
optional [browser based UI](https://demo.consul.io). A commercial version
called [Consul Enterprise](https://developer.hashicorp.com/consul/docs/enterprise) is also
available.

**Please note**: We take Consul's security and our users' trust very seriously. If you
believe you have found a security issue in Consul, please [responsibly disclose](https://www.hashicorp.com/security#vulnerability-reporting)
by contacting us at security@hashicorp.com.

## Quick Start

A few quick start guides are available on the Consul website:

* **Standalone binary install:** https://learn.hashicorp.com/collections/consul/get-started-vms
* **Minikube install:** https://learn.hashicorp.com/tutorials/consul/kubernetes-minikube
* **Kind install:** https://learn.hashicorp.com/tutorials/consul/kubernetes-kind
* **Kubernetes install:** https://learn.hashicorp.com/tutorials/consul/kubernetes-deployment-guide
* **Deploy HCP Consul:** https://learn.hashicorp.com/tutorials/consul/hcp-gs-deploy 

## Documentation

Full, comprehensive documentation is available on the Consul website: https://developer.hashicorp.com/consul/docs

## Contributing

Thank you for your interest in contributing! Please refer to [CONTRIBUTING.md](https://github.com/hashicorp/consul/blob/main/.github/CONTRIBUTING.md)
for guidance. For contributions specifically to the browser based UI, please
refer to the UI's [README.md](https://github.com/hashicorp/consul/blob/main/ui/packages/consul-ui/README.md)
for guidance.

---

### Overview (`api/README.md`)

# Consul API Client

This package provides the `api` package which provides programmatic access to the full Consul API.

The full documentation is available on [Godoc](https://godoc.org/github.com/hashicorp/consul/api).

## Usage

Below is an example of using the Consul client. To run the example, you must first
[install Consul](https://developer.hashicorp.com/consul/downloads) and 
[Go](https://go.dev/doc/install).

To run the client API, create a new Go module.

```shell
go mod init consul-demo
```

Copy the example code into a file called `main.go` in the directory where the module is defined.
As seen in the example, the Consul API is often imported with the alias `capi`.

```go
package main

import (
	"fmt"

	capi "github.com/hashicorp/consul/api"
)

func main() {
	// Get a new client
	client, err := capi.NewClient(capi.DefaultConfig())
	if err != nil {
		panic(err)
	}

	// Get a handle to the KV API
	kv := client.KV()

	// PUT a new KV pair
	p := &capi.KVPair{Key: "REDIS_MAXCLIENTS", Value: []byte("1000")}
	_, err = kv.Put(p, nil)
	if err != nil {
		panic(err)
	}

	// Lookup the pair
	pair, _, err := kv.Get("REDIS_MAXCLIENTS", nil)
	if err != nil {
		panic(err)
	}
	fmt.Printf("KV: %v %s\n", pair.Key, pair.Value)
}
```

Install the Consul API dependency with `go mod tidy`.

In a separate terminal window, start a local Consul server.

```shell
consul agent -dev -node machine
```

Run the example.

```shell
go run .
```

You should get the following result printed to the terminal.

```shell
KV: REDIS_MAXCLIENTS 1000
```

After running the code, you can also view the values in the Consul UI on your local machine at http://localhost:8500/ui/dc1/kv

---

### Overview (`bench/README.md`)

Consul Benchmark
================

This repo contains the automation necessary for the Consul benchmarks.

There is a single main Packer file `bench.json`. To use it, the variables
for `do_client_id` and `do_api_key` must be provided. These correspond to
your DigitalOcean client ID and API key.

When Packer runs, it will generate 3 images:

* bench-bootstrap - Consul server in bootstrap mode
* bench-server - Consul server
* bench-worker - Worker node

For the benchmark you should start 1 bootstrap instance, and 2 normal
servers. As many workers as desired can be started. Once the nodes are
up, you must SSH into one of the Consul servers.

Connect all the nodes with:

    $ consul join <n1> ... <n5>

This will connect all the nodes within the same datacenter.

To run the benchmarks, use the Makefile:

    $ cd /
    $ make # Runs all the benchmarks
    $ make put # Runs only the PUT benchmarks

There is no good way to currently cause multiple workers to run at the same
time, so I just type in the make command and rapidly start the test on all
workers. It is not perfect, but the test runs long enough that the calls
overlap.

---

### Overview (`sdk/README.md`)

## Internal SDK

Please note that this folder, while public, is not meant for new consumers of
these libraries; this should currently be considered an internal, not external,
SDK. It is public due to existing needs from other HashiCorp software. The tags
in this folder will stay at the 0.x.y level; accordingly users should expect
that things can move around, disappear, or change API at any time.

---

### Overview (`test-integ/README.md`)

# test-integ

Go integration tests for consul. `/test/integration` also holds integration tests; they need migrating.

These should use the [testing/deployer framework](../testing/deployer) to bring
up some local testing infrastructure and fixtures to run test assertions against.

Where reasonably possible, try to bring up infrastructure interesting enough to
be able to run many related sorts of tests against it, rather than waiting for
many similar clusters to be provisioned and torn down. This will help ensure
that the integration tests do not consume CPU cycles needlessly.

## Prerequisites

Before you can run these tests, a fresh dev build should be created and tagged
for automatic use by tests using `utils.TargetImages()`. This can be done by
the toplevel consul make target:

    make test-deployer-setup

You can run the entire set of deployer integration tests using:

    make test-deployer         # everything except peering_commontopo
    make test-deployer-peering # just peering_commontopo

You can also run them one by one if you like:

    go test ./connect/ -run Test_Snapshot_Restore_Agentless -v

You can have the logs stream unbuffered directly to your terminal which can
help diagnose stuck tests that would otherwise need to fully timeout before the
logs would print by also setting the `NOLOGBUFFER=1` environment variable.

## Getting started

Deployer tests have three main parts:

1. Declarative topology description.
2. Launching the infrastructure defined by that description.
3. Making test assertions about the infrastructure.

Some tests may also have an optional _mutation_ phase followed by additional
assertions. These are only needed if the test needs to observe a reaction in
the system to a change in the environment or configuration.

### Topology description

Test authors craft a declarative description of the infrastructure necessary to exist for the test.
These are also referred to as a "topology".

These are comprised of 4 main parts:

- **Images**: The set of docker images and specific versions that will be used
              by default if not overridden on each Cluster or Node.

  - Consul CE
  - Consul Enterprise
  - Consul Dataplane
  - Envoy Proxy

- **Networks**: The non-overlapping networks that should exist for use by the Clusters.

- **Clusters**: The unique Consul clusters that should exist.

  - **Nodes**: A "box with ip address(es)". This should feel a bit like a VM or
               a Kubernetes Pod as an enclosing entity.

    - **Workloads**: The list of service instances that will execute on the given node.

  - **InitialConfigEntries**: Config entries that should be created as
                              part of the fixture and that make sense to
                              include as part of the test definition, rather
                              than something created during the test assertion
                              phase.

  - **InitialResources**: Resources that should be created as part of
                          the fixture and that make sense to include as part of
                          the test definition, rather than something created
                          during the test assertion phase.

- **Peerings**: The peering relationships between Clusters to establish.

In the [topoutil](./topoutil) package there are some helpers for defining
common sets of nodes or workloads like Consul Servers, Mesh Gateways, or [fortio
servers](https://github.com/fortio/fortio)

#### Useful topology concepts

Consul has a lot of independent configurables that can greatly increase the
testing configuration space required to flush out any bugs. The topology
definition was designed to be easily "exploded" to create testing microcosms on
a variety of axes:

- agentful (clients) vs agentless (dataplane)
- tenancies (partitions, namespaces)
- locally or across a peering

Since the topology is just a declarative struct, a test author could rewrite
any one of these attributes with a single field (such as `Node.Kind`) and cause
the identical test to run against the other configuration. With the addition of
a few `if enterprise {}` blocks and `for` loops, a test author could easily
write one test of a behavior and execute it to cover agentless, agentful, and
non-default tenancy in a few extra lines of code.

#### Non-optional security settings

The test framework always enables ACLs in default deny mode and provisions
minimal-permission tokens automatically to the various containers that need
them.

TLS certificates are similarly minted and distributed to all components that
need them.

### Launching a topology

There is a [sprawltest](../testing/deployer/sprawl/sprawltest) package that has
utilities to bring up a topology in the context of a Go test. This is basically a one-liner:

    sp := sprawltest.Launch(t, config)

After this line returns you will have a handle (`sp`) to the running cluster
and can use it to get ready-made api clients, http clients, gRPC resource
client, or test sockets open to a variety of the topology components for use in
authoring test code.

This helper will rig up a `t.Cleanup` handler that will destroy all resources
created during the test. This can be opted-out of by setting the
`SPRAWL_KEEP_RUNNING=1` environment variable before running the tests.

### Upgrade test
We are migrating upgrade tests from consul-container(`/test/integration`) to
this directory using the [testing/deployer framework](../testing/deployer).

The current implementation supports two upgrade strategies: [standard upgrade](https://developer.hashicorp.com/consul/docs/upgrading/instructions/general-process)
and [autopilot upgrade](https://developer.hashicorp.com/consul/tutorials/datacenter-operations/upgrade-automation). The basic test scenario can be found in `./test-integ/upgrade/basic`.


### Test assertions

Typical service mesh tests want to ensure that use of a service from another
service behaves in a certain way. Because the entire set of components is known
declaratively, we can process it into a flat list of known source/destination
relationships:

    ships := topology.ComputeRelationships()

This works hand-in-hand with the topology concepts mentioned above to
programmatically verify independent subunits of a topology that may exist (this
is helpful for things like testing multiple tenancy configurations without
duplicating all of the assertion code).

This can also be pretty printed to the log for diagnostic purposes with:

	t.Log(topology.RenderRelationships(ships))

Which looks like this:

    $ NOLOGBUFFER=1 go test ./catalogv2/ -run TestBasicL4ExplicitDestinations -v
    ...(skipping a bunch of output)...
    2023-11-08T11:48:04.395-0600 [INFO]  TestBasicL4ExplicitDestinations: topology is ready for use: elapsed=33.510298357s
        explicit_destinations_test.go:55: DOWN   |node               |service                         |port   |UP    |service                         |
            dc1    |default/dc1-box2   |default/default/single-client   |5000   |dc1   |default/default/single-server   |
            dc1    |default/dc1-box4   |default/default/multi-client    |5000   |dc1   |default/default/multi-server    |
            dc1    |default/dc1-box4   |default/default/multi-client    |5001   |dc1   |default/default/multi-server    |
                   |                   |                                |       |      |                                |

    === RUN   TestBasicL4ExplicitDestinations/relationship:_default/default/single-client_on_default/dc1-box2_in_dc1_via_:5000_=>_default/default/single-server_in_dc1_port_http
        service.go:224: making call to http://10.238.170.5:5000
        service.go:245: ...got response code 200
    === RUN   TestBasicL4ExplicitDestinations/relationship:_default/default/multi-client_on_default/dc1-box4_in_dc1_via_:5000_=>_default/default/multi-server_in_dc1_port_http
        service.go:224: making call to http://10.238.170.7:5000
        service.go:245: ...got response code 200
    === RUN   TestBasicL4ExplicitDestinations/relationship:_default/default/multi-client_on_default/dc1-box4_in_dc1_via_:5001_=>_default/default/multi-server_in_dc1_port_http-alt
        service.go:224: making call to http://10.238.170.7:5001
        service.go:245: ...got response code 200
    2023-11-08T11:48:04.420-0600 [INFO]  TestBasicL4ExplicitDestinations.tfgen: Running 'terraform destroy'...
    --- PASS: TestBasicL4ExplicitDestinations (40.60s)
        --- PASS: TestBasicL4ExplicitDestinations/relationship:_default/default/single-client_on_default/dc1-box2_in_dc1_via_:5000_=>_default/default/single-server_in_dc1_port_http (0.01s)
        --- PASS: TestBasicL4ExplicitDestinations/relationship:_default/default/multi-client_on_default/dc1-box4_in_dc1_via_:5000_=>_default/default/multi-server_in_dc1_port_http (0.01s)
        --- PASS: TestBasicL4ExplicitDestinations/relationship:_default/default/multi-client_on_default/dc1-box4_in_dc1_via_:5001_=>_default/default/multi-server_in_dc1_port_http-alt (0.01s)
    PASS
    ok  	github.com/hashicorp/consul/test-integ/catalogv2	40.612s

There is a ready-made helper to assist with making common inquiries to Consul
and Envoy that you can create in your test:

    asserter := topoutil.NewAsserter(sp)

    asserter.UpstreamEndpointStatus(t, svc, clusterPrefix+".", "HEALTHY", 1)

---

### Overview (`types/README.md`)

# Consul `types` Package

The Go language has a strong type system built into the language.  The
`types` package corrals named types into a single package that is terminal in
`go`'s import graph.  The `types` package should not have any downstream
dependencies.  Each subsystem that defines its own set of types exists in its
own file, but all types are defined in the same package.

# Why

> Everything should be made as simple as possible, but not simpler.

`string` is a useful container and underlying type for identifiers, however
the `string` type is effectively opaque to the compiler in terms of how a
given string is intended to be used.  For instance, there is nothing
preventing the following from happening:

```go
// `map` of Widgets, looked up by ID
var widgetLookup map[string]*Widget
// ...
var widgetID string = "widgetID"
w, found := widgetLookup[widgetID]

// Bad!
var widgetName string = "name of widget"
w, found := widgetLookup[widgetName]
```

but this class of problem is entirely preventable:

```go
type WidgetID string
var widgetLookup map[WidgetID]*Widget
var widgetName
```

TL;DR: intentions and idioms aren't statically checked by compilers.  The
`types` package uses Go's strong type system to prevent this class of bug.

---

### Overview (`ui/README.md`)

# Consul UI Monorepo

This monorepo contains multiple projects, the UI for Consul and addons and
packages used by the UI.

This top-level repository provides limited common tasks, such as installation
and commit assistance.  However, most tasks must be executed from within a
subproject, e.g. running or testing.

**If you are looking to work on the Consul UI you probably want to read
the README that is in `./packages/consul-ui/README.md`.**




**Table of Contents**  *generated with [DocToc](https://github.com/thlorenz/doctoc)*

- [Prerequisites](#prerequisites)
- [Installation](#installation)
- [Pnpm Commands](#pnpm-commands)
- [Contributing](#contributing)
  - [Building ToC](#building-toc)



## Prerequisites

You will need the following things properly installed on your computer.

* [Git][git]
* [Node.js][node]
* [Pnpm][pnpm] installed globally
* [Google Chrome][chrome]
* [Firefox][firefox]

[git]: https://git-scm.com/
[node]: https://nodejs.org/
[pnpm]: https://pnpm.io/
[chrome]: https://google.com/chrome/
[firefox]: https://firefox.com/

## Installation

* `git clone https://github.com/hashicorp/consul.git` this repository
* `cd ui`
* `pnpm i`

## Pnpm Commands

List of available project commands.  `pnpm run <command-name>`

| Command             | Description                        |
|---------------------|------------------------------------|
| doc:toc             | Re-builds the ToC for this README. |
| compliance:licenses | Checks that all dependencies have CE-compatible licenses. |

For Vercel preview deployments, the workspace-level configuration lives in
`./vercel.json`.

## Contributing

### Building ToC

To autogenerate a ToC (table of contents) for this README,
run `pnpm doc:toc`.  Please update the ToC whenever editing the structure
of README.

---

### Overview (`agent/uiserver/README.md`)

# uiserver

The contents of `dist/` are generated by `make ui` in the root of this repo 
which compiles `ui` assets and copies them here.

A CI job (`publish-static-assets`) will detect any diffs in `ui` files and
commit the compiled files. Avoid committing files manually to `dist/`.

---

### Overview (`internal/go-sso/README.md`)

# go-sso

This is a Go library that is being incubated in Consul to assist in doing
opinionated OIDC-based single sign on.

The `go.mod.sample` and `go.sum.sample` files are what the overall real
`go.mod` and `go.sum` files should end up being when extracted from the Consul
codebase.

---

### Overview (`sdk/testutil/README.md`)

Consul Testing Utilities
========================

This package provides some generic helpers to facilitate testing in Consul.

TestServer
==========

TestServer is a harness for managing Consul agents and initializing them with
test data. Using it, you can form test clusters, create services, add health
checks, manipulate the K/V store, etc. This test harness is completely decoupled
from Consul's core and API client, meaning it can be easily imported and used in
external unit tests for various applications. It works by invoking the Consul
CLI, which means it is a requirement to have Consul installed in the `$PATH`.

Following is an example usage:

```
/* Detailed source-code truncated for AI context efficiency. */
```

---

### Overview (`test-integ/peering_commontopo/README.md`)

# CONSUL PEERING COMMON TOPOLOGY TESTS

These peering tests all use a `commonTopo` (read: "common topology") to enable sharing a deployment of a Consul. Sharing a deployment of Consul cuts down on setup time.

To run these tests, you will need to have docker installed. Next, make sure that you have all the required consul containers built:

```
make test-deployer-setup
```

## Non-Shared CommonTopo Tests

The tests in question are designed in a manner that modifies the topology. As a result, it is not possible to share the testing environment across these tests.

## Shared CommonTopo Tests

The tests in question are designed in a manner that does not modify the topology in any way that would interfere with other tests. As a result, it is possible to share the testing environment across these tests.

To run all consul peering tests with no shared topology, run the following command:

```
cd /path/to/peering_commontopo
go test -timeout=10m -v -no-share-topo . 
```

To run all peering tests with shared topology only:

```
cd /path/to/peering_commontopo
go test -timeout=10m -run '^TestSuitesOnSharedTopo' -v . 
```

To run individual peering topology tests:

```
cd /path/to/peering_commontopo
go test -timeout=10m -run '^TestSuiteExample' -v -no-share-topo .    
```

## Local Development and Testing

If writing tests for peering with no shared topology, this recommendation does not apply. The following methods below not necessarily need to be implmented. For shared topology tests, all the methods in the `sharedTopoSuite` interface must be implemented.

- `testName()` prepends the test suite name to each test in the test suite.
- `setup()` phase must ensure that any resources added to the topology cannot interfere with other tests. Principally by prefixing.
- `test()` phase must be "passive" and not mutate the topology in any way that would interfere with other tests.

Common topology peering tests are defined in the [test-integ/peering_commontopo/](/test-integ/peering_commontopo/) directory and new peering integration tests should always be added to this location. Adding integration tests that does not modify the topology should always start by invoking

```go
runShareableSuites(t, testSuiteExample)
```

else

```go
func TestSuiteExample(t *testing.T) {
 ct := NewCommonTopo(t)
 s := &testSuiteExample{}
 s.setup(t, ct)
 ct.Launch(t)
 s.test(t, ct)
}
```

Some of these tests *do* mutate in their `test()` phase, and while they use `commonTopo` for the purpose of code sharing, they are not included in the "shared topo" tests in `sharedtopology_test.go`.

---

### Overview (`test/load/README.md`)

# Terraform Consul Load Testing
Consul Load Testing is used to capture baseline performance metrics for Consul under stress. This will assist in ensuring there are no performance regressions during releases and substantial changes to Consul. Per the defaults, the test runs for 10 minutes with 25 virtual users spawned by k6. Instance sizes for test instances are `t2.small` and for the Consul cluster `m5n.large`.  All metrics from Consul are pushed to a datadog dashboard for user review.
 
This relies on the [Gruntwork's Terraform AWS Consul Module](https://github.com/hashicorp/terraform-aws-consul) which *by default* creates 3 Consul servers across 3 availability zones. A load test instance which has an image that is configured with the necessary scripts and [k6](https://k6.io/) is created and sends traffic to a load balancer. The load balancer will distribute requests across the Consul clients who will ultimately forward the requests to the servers.
 
<img src="loadtestdiagram.png" width="500" height="300"/>
  
## How to use
[Terraform](https://www.terraform.io/downloads.html) and [Packer](https://www.packer.io/downloads), AWS and [Datadog](https://docs.datadoghq.com/getting_started/) are required to use this. All of this, except the AWS resources that will be utilized, are free.
 
This repo has the following folder structure:
* packer: This contains all the necessary stuff to make the load test and the Consul AMI to be utilized in Terraform.
* terraform: This contains all the relevant Terraform files
 
 
## Getting Started 
 
1) Download all necessary tools listed (Terraform, Packer)
2) Set up an [AWS account](https://aws.amazon.com/premiumsupport/knowledge-center/create-and-activate-aws-account/) and a [Datadog account](https://docs.datadoghq.com/getting_started/) - downloading the Datadog client is not necessary. 
3) Configure your AWS credentials using one of the [options supported by the AWS
  SDK](http://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html). Usually, the easiest option is to
  set the `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, and `AWS_DEFAULT_REGION` environment variables.
4) Follow the [Packer README](./packer/README.md) to generate your load test and Consul AMIs
5) Follow the [Terraform README](./terraform/README.md) to stand up the infrastructure in AWS
6) Watch the results in either your datadog dashboard or in the output of your Terraform Apply


## Debugging in Datadog 
Consul has hundreds of metrics to choose from. We recommend reading over [Datadog's article](https://www.datadoghq.com/blog/consul-metrics/#communication-metrics) that breaks down relevant Consul metrics, especially on their "communication" portion. This test runs solely based on making requests to endpoints which is what makes that section so relevant.

---

### Overview (`testing/deployer/README.md`)

[](https://pkg.go.dev/github.com/hashicorp/consul/testing/deployer)

## Summary

This is a Go library used to launch one or more Consul clusters that can be
peered using the cluster peering feature. Under the covers `terraform` is used
in conjunction with the
[`kreuzwerker/docker`](https://registry.terraform.io/providers/kreuzwerker/docker/latest)
provider to manage a fleet of local docker containers and networks.

### Configuration

The complete topology of Consul clusters is defined using a topology.Config
which allows you to define a set of networks and reference those networks when
assigning nodes and workloads to clusters. Both Consul clients and
`consul-dataplane` instances are supported.

Here is an example configuration with two peered clusters:

```
/* Detailed source-code truncated for AI context efficiency. */
```

Once you have a topology configuration, you simply call the appropriate
`Launch` function to validate and boot the cluster.

You may also modify your original configuration (in some allowed ways) and call
`Relaunch` on an existing topology which will differentially adjust the running
infrastructure. This can be useful to do things like upgrade instances in place
or subly reconfigure them.

### For Testing

It is meant to be consumed primarily by unit tests desiring a complex
reasonably realistic Consul setup. For that use case use the `sprawl/sprawltest` wrapper:

```
func TestSomething(t *testing.T) {
    cfg := &topology.Config{...}
    sp := sprawltest.Launch(t, cfg)
    // do stuff with 'sp'
}
```

---

### Overview (`test/load/packer/README.md`)

# Terraform Consul Load Testing
Packer will output AMI IDs when it completes - save these AMI IDs as Terraform will require them later.
The generated ami will have common tag `Environment = "consul-load-test"`.

```
==> Builds finished. The artifacts of successful builds are:
--> amazon-ebs: AMIs were created:

us-east-1: ami-19601070
```
## Consul AMI:

Within the `consul-ami/` directory
1) Retrieve your [Datadog API key]((https://docs.datadoghq.com/account_management/api-app-keys/#api-keys)), set this as an environment variable, ex: `export DD_API_KEY=$YOURDDAPIKEYHERE`
2) Set the AWS_DEFAULT_REGION for Packer, ex: `export AWS_DEFAULT_REGION=us-east-1`
3) Set up AWS credentials, ex: `export AWS_ACCESS_KEY_ID=#YOURAWSKEYID & export AWS_SECRET_ACCESS_KEY=YOURAWSKEY`
4) Run `packer build consul.pkr.hcl`.

For additional customization you can add [tags](https://docs.datadoghq.com/getting_started/tagging/assigning_tags/?tab=noncontainerizedenvironments) within the `scripts/datadog.yaml` file. An example of a tag could be `"consul_version" : "consulent_175"`. These tags are searchable through the datadog dashboard. Another form of customization is changing the datacenter tag within `scripts/telemetry.json`, however it is defaulted to `us-east-1`.


## Load Test AMI

Within the `loadtest-ami/` directory

1) Set the AWS_DEFAULT_REGION for Packer, ex: `export AWS_DEFAULT_REGION=us-east-1`
2) Run the command `packer build loadtest.pkr.hcl`

The script that k6 runs is found within `scripts/loadtest.js`. This script can be updated to send requests to more Consul endpoints. For additional information on k6 please check out their [guides](https://k6.io/docs/getting-started/running-k6).

---

### Overview (`test/load/terraform/README.md`)

# Terraform Consul Load Testing
## How to use
1. Build an image with the desired Consul version and a load test image in the Packer folder [here](../packer).
2. Create your own `vars.tfvars` file in this directory.
3. Place the appropriate AMI IDs in the `consul_ami_id` and `test_server_ami` variables. If no AMI ID is specified it will default
to pulling from latest.
4. Set either `consul_version` or `consul_download_url`. If neither is set it will default to utilizing Consul 1.9.0
5. AWS Variables are set off of environment variables. Make sure to export necessary variables [shown here](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#environment-variables).
6. Run `terraform init` once to setup the working directory.
7. Run `terraform plan -var-file=vars.tfvars`, and then `terraform apply -var-file=vars.tfvars` when ready.
8. Upon completion k6 should run and push metrics to the desired Datadog dashboard.

An example of a `vars.tfvars` :

```
vpc_name             = "consul-test-vpc"
vpc_cidr             = "11.0.0.0/16"
vpc_allwed_ssh_cidr  = "0.0.0.0/0"
public_subnet_cidrs  = ["11.0.1.0/24", "11.0.3.0/24"]
private_subnet_cidrs = ["11.0.2.0/24"]
vpc_az               = ["us-east-2a", "us-east-2b"]
test_instance_type   = "t2.micro"
test_server_ami      = "ami-0ad7711e837ebe166"
cluster_name         = "ctest"
test_public_ip       = "true"
instance_type        = "t2.micro"
ami_owners           = ["******"]
consul_ami_id        = "ami-016d80ff5472346f0"
````

Note that `vpc_allwed_ssh_cidr` must be set to allowed the test server to be accessible from the
machine running the load test, e.g., "0.0.0.0/0" (It is disabled by default).

## Customization
All customization for infrastructure that is available can be found by looking through the `variables.tf` file.
 
## How to SSH
After `terraform apply` is run Terraform should create a `keys/` directory which will give access to all instances created.
For example, `ssh -i "keys/[cluster-name]-spicy-banana.pem" ubuntu@[IPADDRESS]`

---

### Overview (`ui/packages/consul-ui/README.md`)

# consul-ui




- [Prerequisites](#prerequisites)
- [Installation](#installation)
- [Pnpm Commands](#pnpm-commands)
- [Running / Development](#running--development)
  - [Environment Variables](#environment-variables)
  - [Contributing/Engineering Documentation](#contributingengineering-documentation)
  - [Browser 'Debug Utility' Functions and 'Environment' Variables](#browser-debug-utility-functions-and-environment-variables)
  - [Code Generators](#code-generators)
  - [Running Tests](#running-tests)
  - [Linting](#linting)
  - [Building](#building)
    - [Running Tests in Parallel](#running-tests-in-parallel)



## Prerequisites

You will need the following things properly installed on your computer.

* [Git](https://git-scm.com/)
* [Node.js](https://nodejs.org/) (with npm)

* [pnpm](https://pnpm.io/)
* [Ember CLI](https://ember-cli.com/)
* [Google Chrome](https://google.com/chrome/)

## Installation

* `git clone https://github.com/hashicorp/consul.git` this repository
* `cd ui/packages/consul-ui`

then:

**To run the UI**

From within `ui/packages/consul-ui` directory run:

* `make start`

**To run tests**

From within `ui/packages/consul-ui` directory run:

* `make test-oss-view` which will run the tests in Chrome

(see below and/or the [testing section of the engineering docs](./docs/testing.mdx) for
further detail)

## Pnpm Commands

Most used tooling scripts below primarily use `make` which will `pnpm install`
and in turn call node package scripts.

List of available project commands.  `pnpm run <command-name>`

| Command | Description |
| ------- | ----------- |
| build:staging | Builds the UI in staging mode (ready for PR preview site). |
| build:ci | Builds the UI for CI. |
| build | Builds the UI for production. |
| lint | Runs all lint commands. |
| lint:hbs | Lints `hbs` template files. |
| lint:js | Lints `js` files. |
| format | Runs all auto-formatters. |
| format:js | Auto-formats `js` files using Prettier. |
| format:sass | Auto-formats `scss` files using Prettier. |
| start | Runs the development app on a local server using the mock API. |
| start:consul | Runs the development app local server using a real consul instance as the backend. |
| start:staging | Runs the staging app local server. |
| test | Runs the ember tests in a headless browser. |
| test:view | Runs the ember tests in a non-headless browser. |
| test:oss | Runs only the OSS ember tests in a headless browser. |
| test:oss:view | Runs only the OSS ember tests in a non-headless browser. |
| test:coverage:view | Runs only the test specified for coverage in a non-headless browser. |
| test:node | Runs tests that can't be run in ember using node. |
| doc:toc | Automatically generates a table of contents for this README file. |

## Running / Development

The source code comes with a small development mode that runs enough of the consul API
as a set of mocks/fixtures to be able to run the UI without having to run
consul.

* `make start` or `pnpm start` to start the ember app
* Visit your app at [http://localhost:4200](http://localhost:4200).

You can also run the UI against a normal Consul installation.

* `consul agent -dev` to start consul listening on http://localhost:8500
* `make start-consul` to start the ember app proxying to `consul` (this will
respect the `CONSUL_HTTP_ADDR` environment variable to locate the Consul
installation.
* Visit your app at [http://localhost:4200](http://localhost:4200).

Example:

```bash
CONSUL_HTTP_ADDR=http://10.0.0.1:8500 make start-consul
```

### Environment Variables

See [./docs/index.mdx](./docs/index.mdx#environment-variables)

### Branching

We follow a `ui/**/**` branch naming pattern. This branch naming pattern allows
front-end focused builds, such as FE tests, to run automatically in Pull
Requests. Please note this only works if you are a member of the HashiCorp
GitHub Org. If you are an external contributor, these tests won't run and will
instead be run by a member of our team during review.

Examples:
- `ui/feature/add...`
- `ui/bugfix/fix...`
- `ui/enhancement/update...`

### Contributing/Engineering Documentation

We have an in-app (only during development) component storybook and
documentation site which can be visited using the [Eng
Docs](http://localhost:4200/ui/docs) link in the top navigation of the UI.
Alternatively all of these docs are also readable via GitHub's UI, so folks can
use whatever works best for them.

### Browser 'Debug Utility' Functions and 'Environment' Variables

Run `make start` then visit http://localhost:4200/ui/docs/bookmarklets for a
list of debug/engineering utilities you can use to help development of the UI
under certain scenarios.

### Code Generators

Many classes used in the UI can be generated with ember generators, try `ember help generate` for more details

### Running Tests

Tests use the mock api (see ./mock-api for details), the mock-api runs
automatically during testing, you don't need to run anything separately from
the below commands in order for the tests to use the mock-api.

* `make test` or `pnpm run test`
* `make test-view` or `pnpm run test:view` to view the tests running in Chrome

For more guidance on running tests, see the [testing section of the engineering docs](./docs/testing.mdx).

OSS only tests can also be run using:

* `make test-oss` or `pnpm run test:oss`
* `make test-oss-view` or `pnpm run test:oss:view` to view the tests running in Chrome

### Linting

`make lint` currently runs linting on the majority of js files and hbs files (using `ember-template-lint`).

See `.eslintrc.js` and `.eslintignore` for specific configuration.


### Building

* `make build` builds the UI for production usage (env=production)
* `make build-ci` builds the UI for CI/test usage (env=test)

Static files are built into ./dist

#### Running Tests in Parallel

You probably don't need to understand this if you are simply running some
tests locally.

Alternatively, `ember-exam` can be used to split the tests across multiple browser instances for faster results. Most options are the same as `ember test`. To see a full list of options, run `ember exam --help`.

**Note:** The `EMBER_EXAM_PARALLEL` environment variable must be set to override the default `parallel` value of `1` browser instance in [testem.js](./testem.js).

To quickly run the tests across 4 parallel browser instances:
```sh
make test-parallel
```

To run manually:
```sh
$ EMBER_EXAM_PARALLEL=true ./node_modules/.bin/ember exam --split <num> --parallel
```

More ways to split tests can be found in the [ember-exam README.md](https://github.com/trentmwillis/ember-exam/blob/master/README.md).

### Vercel Deploys

A Vercel preview deploy Github action triggers after each pushed change in a pull request. Vercel checks if there was changes within the UI folder in the last commit. If there are no changes the build is cancelled. If the build proceeds it will include a link to the preview deploy in your PRs feed.

If you were to push changes to the UI folder and then immediately follow it up with a change to something outside of the UI folder, the lastest Vercel build will be ignored. Because of this, the preview link will not get posted to the PR feed even if the original Vercel build completes. If that is the case, you may browse the Vercel deploys for the original build.

Another scenario to watch for is when you rebase a series of commits, but the last commit is outside of the UI folder. Doing this will not trigger a new Vercel deploy as the last commit doesn't have any changes in the UI folder.

---

### Overview (`test/integration/connect/envoy/README.md`)

# Envoy Integration Tests

## Overview

These tests validate that Consul is configuring Envoy correctly. They set up various scenarios using Docker containers and then run
[Bats](https://github.com/sstephenson/bats) (a Bash test framework) tests to validate the expected results.

## Running Tests

To run the tests locally, `cd` into the root of the repo and run:

```console
make test-envoy-integ
```

To run a specific test, run:

```console
make test-envoy-integ GO_TEST_FLAGS="-run TestEnvoy/case-basic"
```

Where `case-basic` can be replaced by any directory name from this directory.

## How Do These Tests Work

1. The tests are all run through Go test via the `main_test.go` file. Each directory prefixed by `case-` is a subtest, for example,
`TestEnvoy/case-basic` and `TestEnvoy/case-wanfed-gw`.
2. The real framework for this test suite lives in `run-tests.sh`. Under the hood, `main_test.go` just runs `run-tests.sh` with
   various arguments.
3. The tests use your local code by building a Docker image from your local directory just before executing.
   _Note:_ this is implemented as the `docker-envoy-integ` Makefile target which is a prerequisite to the `test-envoy-integ` target,
   so if you are running the tests by invoking `run-tests.sh` or `go test` manually, be sure to rebuild the Docker image to ensure
   you are running your latest code.
4. The tests run Docker containers connected by a shared Docker network. All tests have at least one Consul server running and then
   depending on the test case they will spin up additional services or gateways. Some tests run multiple Consul servers to test
   multi-DC setups. See the [`case-wanfed-gateway` test](./case-wanfed-gw) for an example of this.
5. At a high level, tests are set up by executing the `setup.sh` script in each directory. This script uses helper functions
   defined in `helpers.bash`. Once the test case is set up, the validations in `verify.bats` are run.
6. If there exists a `vars.sh` file in the top-level of the case directory, the test runner will source it prior to invoking
   the `run_tests`, `test_teardown` and `capture_logs` phases of the test scenario.
7. If there exists a `capture.sh` file in the top-level of the case directory, it will be executed after the test is done, but prior to
   the containers being removed. This is useful for capturing logs or Envoy snapshots for debugging test failures.
8. Any files matching the `*.hcl` glob will be copied to the container `$WORKDIR/$CLUSTER/consul` directory prior to running the tests.
   This is useful for defining Consul configuration for each agent process to load on start up.
9. In CI, the tests are executed against different Envoy versions and with both `XDS_TARGET=client` and `XDS_TARGET=server`.
   If set to `client`, a Consul server and client are run, and services are registered against the client. If set to `server`,
   only a Consul server is run, and services are registered against the server. By default, `XDS_TARGET` is set to `server`.
   See [this comment](https://github.com/hashicorp/consul/blob/70bb6a2abdbc5ed4a6e728e8da243c5394a631d1/test/integration/connect/envoy/run-tests.sh#L178-L212) for more information.

## Investigating Test Failures

* When tests fail in CI, logs and additional debugging data are available in the artifacts of the test run.
* You can re-run the tests locally by running `make test-envoy-integ GO_TEST_FLAGS="-run TestEnvoy/<case-directory>"` where `<case-directory>` is
  replaced with the name of the directory, e.g. `case-basic`.
* You can override the envoy version by specifying `ENVOY_VERSION=<your envoy version>` eg. `ENVOY_VERSION=1.27.0 make test-envoy-integ`.
* Locally, all the logs of the failed test will be available in `workdir` in this directory.
* You can run with `DEBUG=1` to print out all the commands being run, e.g. `DEBUG=1 make test-envoy-integ GO_TEST_FLAGS="-run TestEnvoy/case-basic"`.
* If you want to prevent the Docker containers from being spun down after test failure, add a `sleep 9999` to the `verify.bats` test case that's failing.

## Creating a New Test

Below is a rough outline for creating a new test. For the example, assume our test case will be called `my-feature`.
1. Create a new directory named `case-my-feature`
2. If the test involves multiple datacenters/clusters, create a separate subdirectory for each cluster (eg. `case-my-feature/{dc1,dc2}`)
3. Add any necessary configuration to `*.hcl` files in the respective cluster subdirectory (or the test case directory when using a single cluster).
4. Create a `setup.sh` file in the case directory
5. Create a `capture.sh` file in the case directory
6. Create a `verify.bats` file in the case directory
7. Populate the `setup.sh`, `capture.sh` and `verify.bats` files with the appropriate code for running your test, validating its state and capturing any logs or snapshots.

---

### Overview (`ui/packages/consul-ui/mock-api/README.md`)

# consul-api-double

Super simple API 'double' for easily visualizing the Consul API via the filesystem.

This can be served via either a simple javascript HTTP server, or entirely via frontend methods if your project/circumstances means you are unable to use or it's complicated to use a backend server for testing.

See [@hashicorp/api-double](https://github.com/hashicorp/api-double/) for more details.

---

### Overview (`test/integration/consul-container/test/upgrade/README.md`)

# Upgrade Integration Tests

- [Introduction](#introduction)
	- [How it works](#how-it-works)
- [Getting Started](#getting-started)
	- [Prerequisites](#prerequisites)
	- [Running Upgrade integration tests](#running-upgrade-integration-tests)
- [Adding a new upgrade integration test](#adding-a-new-upgrade-integration-test)
	- [Errors Test Cases](#errors-test-cases)
- [FAQS](#faqs)


## Introduction

The goal of upgrade tests is to ensure problem-free upgrades on supported upgrade paths.
At any given time, Consul supports the latest minor release, and two older minor releases, e.g. 1.15, 1.14, and 1.13.
Upgrades to any higher version are permitted, including skipping a minor version e.g. from 1.13 to 1.15.

The upgrade tests also aim to highlight errors that may occur as users attempt to upgrade their current version to a newer version.

### How it works

This diagram illustrates the deployment architecture of an upgrade test, where
two consul agents (one server and one client), a static-server, static-client,
and envoy sidecars are deployed.

<img src="../util/upgrade_tests_workflow.png" alt="isolated" width="550"/>

> Note that all consul agents and user workloads such as application services, mesh-gateway are running in docker containers.

In general, each upgrade test has the following steps:
1. Create a cluster with a specified number of server and client agents, then enable the feature to be tested. 
2. Create some workload in the cluster, e.g., registering 2 services: static-server, static-client.
Static-server is a simple http application and the upstream service of static-client.
3. Make additional configuration to the cluster. For example, configure Consul intention to deny
connection between static client and server. Ensure that a connection cannot be made.
4. Upgrade Consul cluster to the `target-version` and restart the Envoy sidecars
(we restart Envoy sidecar to ensure the upgraded Consul binary can read the state from
the previous version and generate the correct Envoy configurations)
5. Re-validate the client, server and sidecars to ensure the persisted data from the previous
version can be accessed in the target version. Verify connection / disconnection
(e.g., deny Action)  

## Getting Started
### Prerequisites
To run the upgrade test, the following tools are required:
- install [Go](https://go.dev/) (the version should match that of our CI config's Go image).
- install [`golangci-lint`](https://golangci-lint.run/usage/install/) 
- install [`Makefile`](https://www.gnu.org/software/make/manual/make.html)
- [`Docker`](https://docs.docker.com/get-docker/) required to run tests locally  

### Running Upgrade integration tests
- run `make dev-docker`
- build a consul-envoy container image
  ```shell
  cd test/integration/consul-container
  docker build -t consul-envoy:latest-version \
    --build-arg CONSUL_IMAGE=docker.mirror.hashicorp.services/consul:1.20.0-rc1  \
    --build-arg ENVOY_VERSION=1.31.2  -f ./assets/Dockerfile-consul-envoy ./assets
  ```
- run the single test `go test -v -timeout 30m -run ^TestACL_Upgrade_Node_Token$ ./.../upgrade/`
- run all upgrade tests `go test -v -timeout 30m -run ./.../upgrade`

To specify targets and latest image pass `--target-version` and `--latest-version`
to the tests. By default, it uses the `consul` docker image with respectively
`local` and `latest` tags.

To use dev consul image, pass `target-image` and `target-version`:

    -target-image hashicorppreview/consul -target-version 1.15-dev

By default, all container's logs are written to either `stdout`, or `stderr`;
this makes it hard to debug, when the test case creates many containers. To
disable following container logs, run the test with `-follow-log false`.

Below are the supported CLI options
| Flags               | Default value | Description |
| -----------         | ----------- | ----------- |
| --latest-image      | `consul` in CE, `hashicorp/consulenterprise` in ENT   | Name of the Docker image to deploy initially.
| --latest-version    | latest                                                | Tag of the Docker image to deploy initially.
| --target-image      | `consul` in Ce, `hashicorp/consulenterprise` in ENT   | Name of the Docker image to upgrade to.
| --target-version    | local                                                 | Tag of the Docker image to upgrade to. `local` is the tag built by `make dev-docker` above.
| -follow-log         |  true                                                 | Emit all container logs. These can be noisy, so we recommend `--follow-log=false` for local development.


## Adding a new upgrade integration test

All upgrade tests are defined in [test/integration/consul-container/test/upgrade](/test/integration/consul-container/test/upgrade) subdirectory.

Following is a guide for adding a new upgrade test case.
1. Create consul cluster(s) with a specified version. Some utility functions are provided to make
a single cluster or two peered clusters:

```go
	// NewCluster creates a single cluster
	cluster, _, _ := libtopology.NewCluster(t, &libtopology.ClusterConfig{
		NumServers: 1,
		NumClients: 1,
		BuildOpts: &libcluster.BuildOptions{
			Datacenter:           "dc1",
			ConsulVersion:        utils.LatestVersion,
		},
	})
```

Or

```go
	// BasicPeeringTwoClustersSetup creates two peered clusters, named accpeting and dialing
	accepting, dialing := libtopology.BasicPeeringTwoClustersSetup(t, utils.LatestVersion, false)
```

Some workloads may require extra resources. They should be created in this setup section. For example,
[https://github.com/hashicorp/consul-enterprise/blob/19e515db29541132dbbda73efb7a458cd29d705f/test/integration/consul-container/test/upgrade/peering_http_test.go#L30-L41](this peering test creates a second static-server).

2. Verify the workload

```go
	libassert.HTTPServiceEchoes(t, "localhost", port, "")
	libassert.AssertFortioName(t, fmt.Sprintf("http://localhost:%d", appPort), "static-server-2-v2", "")
```

3. Call the `StandardUpgrade` method and check that the upgrade succeeded.
We also restart the Envoy proxy to make sure the upgraded agent can generate
the correct Envoy configurations.

```go
	require.NoError(t, 
		cluster.StandardUpgrade(t, context.Background(), utils.TargetVersion))
	require.NoError(t, staticServerConnectProxy.Restart())
	require.NoError(t, staticClientConnectProxy.Restart())
```
4. Verify the workload from step 3 again

```go
	libassert.HTTPServiceEchoes(t, "localhost", port, "")
	libassert.AssertFortioName(t, fmt.Sprintf("http://localhost:%d", appPort), "static-server-2-v2", "")
```

For longer verifications, it can be nice to make a local function instead:
```go
	tests := func() {
		libassert.HTTPServiceEchoes(t, "localhost", port, "")
		libassert.AssertFortioName(t, fmt.Sprintf("http://localhost:%d", appPort), "static-server-2-v2", "")
	}
	tests()
	// ... do upgrade
	tests()
```

### Errors Test Cases
There are some caveats for special error handling of versions prior to `1.14`.   
Upgrade tests for features such as peering had API changes that return an error if an upgrade is attempted, and should be accounted for in upgrade tests. If running upgrade tests for any version before `1.14`, the following lines of code need to be added to skip it or it will not pass.  

```go
	fromVersion, err := version.NewVersion(utils.LatestVersion)
	require.NoError(t, err)
	if fromVersion.LessThan(utils.Version_1_14) {
		t.Skip("...")
	}
```
See example [here](https://github.com/hashicorp/consul-enterprise/blob/005a0a92c5f39804cef4ad5c4cd6fd3334b95aa2/test/integration/consul-container/test/upgrade/peering_control_plane_mgw_test.go#L92-L96)

To write tests for bugs found during upgrades, see example on how to add a testcase for those scenarios [here](./fullstopupgrade_test.go). 

## FAQS

**Q.** Are containers' ports (e.g., consul's 8500, envoy sidecar's admin port
or local upstream port) exposed on the docker host? \
**A.** Yes, they are exposed. However, they are exposed through a [pod container](https://github.com/hashicorp/consul/blob/57e034b74621180861226a01efeb3e9cedc74d3a/test/integration/consul-container/libs/cluster/container.go#L132).
That is, a consul agent and the envoy proxy containers registered with the agent
share the [same Linux network namespace (i.e., they share `localhost`)](https://github.com/hashicorp/consul/blob/57e034b74621180861226a01efeb3e9cedc74d3a/test/integration/consul-container/libs/cluster/app.go#L23-L30) as the pod container.
The pod container use the same prefix as the consul agent in its name.

**Q.** To troubleshoot, how can I send API request or consul command to the deployed cluster? \
**A.** To send an API request or command to the deployed cluster, ensure that a cluster,  services and sidecars have been created. See example below:
```go
	cluster, _, _ := topology.NewCluster()
	clientService := createServices(t, cluster)
	_, port := clientService.GetAddr()
	_, adminPort := clientService.GetAdminAddr()
    ...
    time.Sleep(900 * time.Second)
	fmt.Println(port, adminPort)
```
Then in your terminal `docker ps -a | grep consul` to get the running services and cluster. Exec in the cluster and run commands directly or make API request to `localhost:port` to relevant service or `localhost:adminPort` for envoy. 

**Q.** To troubleshoot, how can I access the envoy admin page? \
**A.** To access envoy admin page, ensure that a cluster, services and sidecars have been created. Then get the adminPort for the client or server sidecar. See example on how to get the port above. Then navigate to a browser and go to the url `http://localhost:adminPort/`  

**Q.** My test is stuck with the error "could not start or join all agents: container 0: port not found"? \
**A.** Simply re-run the tests. If the error persists, prune docker images `docker system prune`, run `make dev-docker`, then re-run tests again. 

**Q.** How to clean up the resources created the upgrade test?
**A.**  Run the command `docker ps | grep consul` to find all left over resources, then `docker stop {CONTAINER_ID} && docker rm {CONTAINER_ID}`

---

### Overview (`ui/packages/consul-ui/docs/a11y-audit/README.md`)

# Consul UI accessibility audit

Tracks a systematic a11y audit of consul-ui, broken into independent, per-section
tasks. **Audit only** — findings are documented here with suggested fixes, but
no fixes are applied as part of this work.

## Method (applies to every task file)

- **Automated:** two independent rule engines are run against each representative
  page/state, since they catch different things:
  - Chrome DevTools Lighthouse accessibility audit (axe-core-based).
  - IBM's `accessibility-checker` (the open-source engine behind DevTools'
    "Accessibility Assessment" panel — that panel and "Screen Reader Simulator"
    themselves aren't drivable by any available automation tool, since they're
    proprietary Chrome-extension UI with no exposed API; running the engine
    directly via its Node API is the closest equivalent). Installed standalone
    in the scratchpad dir (`npm install accessibility-checker`), *not* added
    as a project dependency. Run via a small script calling
    `aChecker.getCompliance(url, label)` against the dev server.
  - In this pass IBM's checker surfaced 3 concrete bugs axe/Lighthouse missed
    entirely (findings #5–#7 in [01-shared-chrome.md](01-shared-chrome.md)),
    plus independently confirmed the contrast finding (#1) both tools agree on.
    Worth running both engines on every task, not just one.

  **Reproducing the IBM scan** (scratchpad install, not a project dependency —
  recreate this each session, it doesn't persist):
  ```bash
  mkdir a11y-ibm && cd a11y-ibm && npm init -y && npm install accessibility-checker
  ```
  ```js
  // scan.js
  const aChecker = require("accessibility-checker");
  (async () => {
    const { report } = await aChecker.getCompliance("http://localhost:4200/ui/dc1/services", "services");
    for (const it of report.results) {
      if (it.level === "violation" || it.level === "potentialviolation") {
        console.log(`[${it.level}] ${it.ruleId} :: ${it.message}`);
        console.log("  path:", it.path && it.path.dom);
      }
    }
    await aChecker.close();
  })();
  ```
  Run with `node scan.js`. Swap the URL/label per page being audited.
- **Manual keyboard:** Tab/Shift+Tab/Enter/Escape/Arrow-key traversal, checking
  focus order, visible focus indication, keyboard operability of all
  interactive elements (dropdowns, modals, tables, forms), and no keyboard traps.
- **Screen-reader-equivalent spot checks:** inspection of the accessibility
  tree (roles, accessible names, states, live regions) that Chrome DevTools
  exposes. **This is not a live run of VoiceOver/NVDA/JAWS** — no screen
  reader automation is available in this environment. Tree data is a good
  proxy for what would be announced, but doesn't substitute for a real AT
  pass before shipping fixes.

Findings use axe's severity vocabulary: **Critical / Serious / Moderate / Minor**.

## Environment used

Dev server at `localhost:4200`, with `packages/consul-ui/config/environment.js`
`operatorConfig` set to `ACLsEnabled: true, NamespacesEnabled: true,
PeeringEnabled: true, PartitionsEnabled: true` (already the case on this
branch), `V2CatalogEnabled: false`, `HCPEnabled: false`. The V2 catalog / HCP
shell is a different application mode, not just extra routes, and is **out of
scope** for this pass.

Run with `pnpm start` (the mock API, see [mock-api](../../mock-api)) — export
`CONSUL_SERVICE_MIN=2` first so the randomly-generated service count can
never collapse to a single, non-mesh service (see
[task-4-mock-data-plan.md](task-4-mock-data-plan.md) for why that matters). A
stable `backend` service with a sidecar proxy — real `Upstreams` and
`Expose.Paths` — is now always present in the services list regardless of
that random count, so `dc.services.instance.upstreams`/`.exposedpaths` have
a reliable, reproducible target to audit against.

## Tasks

| # | Task | Routes | Status | File |
|---|------|--------|--------|------|
| 1 | Shared chrome & global patterns | app chrome, `settings`, `unavailable`, `notfound` | Complete (7 findings) | [01-shared-chrome.md](01-shared-chrome.md) |
| 2 | Overview | `dc.show`(`.serverstatus`/`.cataloghealth`/`.license`) | Complete (1 new finding + confirmed recurrences) | [02-overview.md](02-overview.md) |
| 3 | Services (list & detail) | `dc.services`(`.show`.*), `dc.routing-config` | Complete (3 new findings) | [03-services.md](03-services.md) |
| 4 | Service instance detail | `dc.services.instance`.* | Complete (1 finding + confirmed recurrences on all 5 sub-tabs) | [04-service-instance.md](04-service-instance.md) |
| 5 | Nodes | `dc.nodes`(`.show`.*) | Complete (1 high-reach finding) | [05-nodes.md](05-nodes.md) |
| 6 | Key/Value | `dc.kv`.* | Complete (2 minor findings) | [06-kv.md](06-kv.md) |
| 7 | Intentions (top-level) | `dc.intentions`.* | Complete (1 finding, upstream HDS) | [07-intentions.md](07-intentions.md) |
| 8 | Access Controls — Tokens | `dc.acls.tokens`.* | Complete (no new findings; 1 tool false-positive documented) | [08-acls-tokens.md](08-acls-tokens.md) |
| 9 | Access Controls — Policies | `dc.acls.policies`.* | Complete (no new findings) | [09-acls-policies.md](09-acls-policies.md) |
| 10 | Access Controls — Roles | `dc.acls.roles`.* | Complete (no new findings) | [10-acls-roles.md](10-acls-roles.md) |
| 11 | Access Controls — Auth Methods | `dc.acls.auth-methods`.* | Complete (no new findings; corroborates #3) | [11-acls-auth-methods.md](11-acls-auth-methods.md) |
| 12 | Peers | `dc.peers`.* | Complete (no new findings; `.show` blocked on no peered clusters in mock data) | [12-peers.md](12-peers.md) |
| 13 | Admin Partitions | `dc.partitions`.* | Complete (no new findings) | [13-partitions.md](13-partitions.md) |
| 14 | Namespaces | `dc.nspaces`.* | Complete (1 finding, 3rd occurrence of a recurring pattern) | [14-namespaces.md](14-namespaces.md) |

Task 1 (shared chrome) is audited first since its components (header, side
nav, tables, pagination, modals, notifications) are reused across every other
section — findings there apply everywhere and don't need to be re-logged
per-section.

## Summary — all 14 tasks

All tasks are complete except Task 12, which is partial: `dc.peers.show` has
no peered cluster to open in this dataset. (Task 4 was in the same state —
`dc.services.instance`'s `.upstreams`/`.exposedpaths`/`.addresses`/`.metadata`
tabs needed mesh mock data before they could be audited — but that's now
resolved; see [task-4-mock-data-plan.md](task-4-mock-data-plan.md) and
[04-service-instance.md](04-service-instance.md).) Everything else was fully
reachable.

**Recurring patterns worth fixing once, centrally, rather than per-instance:**

1. **`aria-label`/`aria-labelledby` on an element with no compatible role** (the
   attribute is silently dropped because the element's implicit role is
   `generic`, which doesn't support naming) — found independently 4 times:
   `<header>` wrappers ([02-overview.md](02-overview.md) #1), HDS's own
   `Hds::Alert` ([07-intentions.md](07-intentions.md) #1, recurring again on
   the service-instance Exposed Paths tab's intro alert —
   [04-service-instance.md](04-service-instance.md)), and the ACL
   ruleset badge ([14-namespaces.md](14-namespaces.md) #1, the one instance
   with a confirmed real information loss). Worth an `ember-template-lint`
   rule if one doesn't already exist for this.
2. **HDS's `{{hds-tooltip}}` modifier makes any host element tabbable with no
   widget role** ([01-shared-chrome.md](01-shared-chrome.md) #7) — recurred in
   nearly every task (Overview, Services, Service instance, Nodes, Peers).
   Upstream in `@hashicorp/design-system-components`; consul-ui uses it 30+
   places.
3. **`ConsulCopyButton`'s `aria-label` replaces its visible value instead of
   including it** ([05-nodes.md](05-nodes.md) #1) — WCAG 2.5.3 Label in Name,
   11 usage sites app-wide. Single highest-reach fix in the whole audit: one
   component change covers all 11.
4. **The side-nav `<aside>` has no accessible name** despite HDS rendering a
   hidden heading that was clearly meant to be it
   ([01-shared-chrome.md](01-shared-chrome.md) #5) — confirmed on every single
   page audited. One-line fix (`aria-label` on `Frame.Sidebar`).
5. **Side-nav/footer text contrast** (`#8c909c` on `#fafafa`, 3.05:1 vs the
   4.5:1 required) — [01-shared-chrome.md](01-shared-chrome.md) #1, confirmed
   on every single page audited, both by axe/Lighthouse and IBM's checker.

**The two most significant individual (non-recurring) findings:**

- The health-checks list loses all `<ul>`/`<li>` semantics because
  `display: flex` is applied to `<li>` for its layout, stripping the implicit
  `list`/`listitem` roles Chrome would otherwise assign — confirmed on both
  `dc.services.instance.healthchecks` and `dc.nodes.show.healthchecks`
  ([04-service-instance.md](04-service-instance.md) #1). ~20 checks read as
  one undifferentiated wall of text with no way to navigate check-by-check.
- The service topology diagram conveys health status by color alone (bare
  "45%9%45%" with no passing/warning/critical label) and has no structural/
  textual equivalent of the visual graph at all
  ([03-services.md](03-services.md) #1–#2). Neither automated engine caught
  either of these — both came from reading the topology component's source
  directly, a reminder that automated tools have real blind spots around
  custom visualizations.

**On tooling:** two IBM `accessibility-checker` results were investigated and
turned out to be false positives rather than real bugs — a `combobox_haspopup_valid`
complaint about Ember Power Select's portal wrapper (the real listbox
underneath is correctly implemented — [08-acls-tokens.md](08-acls-tokens.md)),
and the CSS-driven focus-ring pattern initially looked broken via
`getComputedStyle` until the `::before`-pseudo-element ring was checked
directly ([01-shared-chrome.md](01-shared-chrome.md)). Both are recorded in
their task files so they aren't rediscovered and mis-reported as bugs later.

---

### Overview (`test/integration/consul-container/test/envoy_extensions/testdata/wasm_test_files/README.md`)

# Building WASM test files

We have seen some issues with building the wasm test files on the build runners for the integration test. Currently,
the theory is that there may be some differences in the clang toolchain on different runners which cause panics in
tinygo if the job is scheduled on particular runners but not others.

In order to get around this, we are just building the wasm test file and checking it into the repo.

To build the wasm test file, 

```bash
~/consul/test/integration/consul-container/test/envoy_extensions/testdata/wasm_test_files
> docker run -v ./:/wasm --rm $(docker build -q .)
```

---

### Ui Packages Consul Ui Docs Index (`ui/packages/consul-ui/docs/index.mdx`)

---
title: Introduction
---
# Consul UI Engineering Docs

Welcome to Consul UIs engineering documentation.

## Environment Variables

There are various environment variable you can use whilst running `make start` or `make test` to configure various features:

| Variable | Default Value | Description |
| -------- | ------------- | ----------- |
| `TESTEM_AUTOLAUNCH` | Chrome | Controls which browser to open tests in. A setting of `""` means 'let me manually open the browser' |
| `EMBER_TEST_REPORT` |  | Output a test report |
| `CONSUL_DOCFY_CONFIG` |  | Define an additional `docfy-config.json` file to use |

## Adding documentation

Our documentation use [docfy](https://docfy.dev/docs) for rendering our markdown+glimmer-component documentation. In order to live render any code examples use the `preview-template` meta, for example:

~~~md
```hbs
<YourComponent
  @thing={{var}}
/>
```
~~~

The above will render the same code snippet in a box above the snippet.

The location and name of markdown files within the project differs slightly depending on what you need to add documentation for:

- **docs**: `docs/filename.mdx`
- **styles**: `app/styles/*/*/README.mdx` (currently mostly in `app/styles/base`)
- **components**: `app/components/your-component-name/README.mdx`
- **helpers**: `app/helpers/your-helper-name.mdx`
- **modifiers**: `app/modifiers/your-modifier-name.mdx`
- **services**: `app/services/your-service-name.mdx` (eventually these will partly use jsdoc code style generation)

---

### Config Checklist Adding Config Fields (`docs/config/checklist-adding-config-fields.md`)

# Adding a Consul Config Field

This is a checklist of all the places you need to update when adding a new field
to config. There may be a few other special cases not included but this covers
the majority of configs.

We suggest you copy the raw markdown into a gist or local file and check them
off as you go (you can mark them as done by replace `[ ]` with `[x]` so github
renders them as checked). Then **please include the completed lists you worked
through in your PR description**.

Examples of special cases this doesn't cover are:
 - If the config needs special treatment like a different default in `-dev` mode
   or differences between CE and Enterprise.
 - If custom logic is needed to support backwards compatibility when changing
   syntax or semantics of anything

There are four specific cases covered with increasing complexity:
 1. adding a simple config field only used by client agents
 1. adding a CLI flag to mirror that config field
 1. adding a config field that needs to be used in Consul servers
 1. adding a field to the Service Definition

## Adding a Simple Config Field for Client Agents

 - [ ] Add the field to the Config struct (or an appropriate sub-struct) in
   `agent/config/config.go`.
 - [ ] Add the field to the actual RuntimeConfig struct in
   `agent/config/runtime.go`.
 - [ ] Add an appropriate parser/setter in `agent/config/builder.go` to
   translate.
 - [ ] Add the new field with a random value to both the JSON and HCL files in
   `agent/config/testdata/full-config.*`, which should cause the test to fail.
   Then update the expected value in `TestLoad_FullConfig` in
   `agent/config/runtime_test.go` to make the test pass again.
 - [ ] Run `go test -run TestRuntimeConfig_Sanitize ./agent/config -update` to update
   the expected value for `TestRuntimeConfig_Sanitize`. Look at `git diff` to
   make sure the value changed as you expect.
 - [ ] **If** your new config field needed some validation as it's only valid in
   some cases or with some values (often true).
      - [ ] Add validation to Validate in `agent/config/builder.go`.
      - [ ] Add a test case to the table test `TestLoad_IntegrationWithFlags` in
        `agent/config/runtime_test.go`.
 - [ ] **If** your new config field needs a non-zero-value default.
      - [ ] Add that to `DefaultSource` in `agent/config/defaults.go`.
      - [ ] Add a test case to the table test `TestLoad_IntegrationWithFlags` in
        `agent/config/runtime_test.go`.
      - [ ] If the config needs to be defaulted for the test server used in unit tests,
            also add it to `DefaultConfig()` in `agent/consul/config.go`.
 - [ ] **If** your config should take effect on a reload/HUP.
      - [ ] Add necessary code to to trigger a safe (locked or atomic) update to
        any state the feature needs changing. This needs to be added to one or
        more of the following places:
         - `ReloadConfig` in `agent/agent.go` if it needs to affect the local
           client state or another client agent component.
         - `ReloadConfig` in `agent/consul/client.go` if it needs to affect
           state for client agent's RPC client.
      - [ ] Add a test to `agent/agent_test.go` similar to others with prefix
        `TestAgent_reloadConfig*`.
 - [ ] Add documentation to `website/content/docs/agent/config/config-files.mdx`.

Done! You can now use your new field in a client agent by accessing
`s.agent.Config.<FieldName>`.

If you need a CLI flag, access to the variable in a Server context, or touched
the Service Definition, make sure you continue on to follow the appropriate
checklists below.

## Adding a CLI Flag Corresponding to the new Field
If the config field also needs a CLI flag, then follow these steps.

 - [ ] Do all of the steps in [Adding a Simple Config
   Field For Client Agents](#adding-a-simple-config-field-for-client-agents).
 - [ ] Add the new flag to `agent/config/flags.go`.
 - [ ] Add a test case to TestParseFlags in `agent/config/flag_test.go`.
 - [ ] Add a test case (or extend one if appropriate) to the table test
   `TestLoad_IntegrationWithFlags` in `agent/config/runtime_test.go` to ensure setting the
   flag works.
 - [ ] Add flag (as well as config file) documentation to
   `website/source/docs/agent/config/config-files.mdx` and `website/source/docs/agent/config/cli-flags.mdx`.

## Adding a Simple Config Field for Servers
Consul servers have a separate Config struct for reasons. Note that Consul
server agents are actually also client agents, so in some cases config that is
only destined for servers doesn't need to follow this checklist provided it's
only needed during the bootstrapping of the server (which is done in code shared
by both server and client components in `agent.go`). For example WAN Gossip
configs are only valid on server agents but since WAN Gossip is setup in
`agent.go` they don't need to follow this checklist. The simplest (and mostly
accurate) rule is:

> If you need to access the config field from code in  `agent/consul` (e.g. RPC
> endpoints), then you need to follow this. If it's only in `agent` (e.g. HTTP
> endpoints or agent startup) you don't.

A final word of warning - **you should never need to pass config into the FSM
(`agent/consul/fsm`) or state store (`agent/consul/state`)**. Doing so is **_very
dangerous_** and can violate consistency guarantees and corrupt databases. If
you think you need this then please discuss the design with the Consul team
before writing code!

Consul's server components for historical reasons don't use the `RuntimeConfig`
struct they have their own struct called `Config` in `agent/consul/config.go`.

 - [ ] Do all of the steps in [Adding a Simple Config
   Field For Client Agents](#adding-a-simple-config-field-for-client-agents).
 - [ ] Add the new field to Config struct in `agent/consul/config.go`
 - [ ] Add code to set the values from the `RuntimeConfig` in `newConsulConfig` method in `agent/agent.go`
 - [ ] **If needed**, add a test to `agent_test.go` if there is some non-trivial
   behavior in the code you added in the previous step. We tend not to test
   simple assignments from one to the other since these are typically caught by
   higher-level tests of the actual functionality that matters but some examples
   can be found prefixed with `TestAgent_consulConfig*`
 - [ ] **If** your config should take effect on a reload/HUP
      - [ ] Add necessary code to `ReloadConfig` in `agent/consul/server.go` this
        needs to be adequately synchronized with any readers of the state being
        updated.
       - [ ] Add a new test or a new assertion to `TestServer_ReloadConfig`

You can now access that field from `s.srv.config.<FieldName>` inside an RPC
handler.

## Adding a New Field to Service Definition
The [Service Definition](https://developer.hashicorp.com/docs/agent/services.html) syntax
appears both in Consul config files but also in the `/v1/agent/service/register`
API.

For wonderful historical reasons, our config files have always used `snake_case`
attribute names in both JSON and HCL (even before we supported HCL!!) while our
API uses `CamelCase`.

Because we want documentation examples to work in both config files and API
bodies to avoid needless confusion, we have to accept both snake case and camel
case field names for the service definition.

Finally, adding a field to the service definition implies adding the field to
several internal structs and to all API outputs that display services from the
catalog. That explains the multiple layers needed below.

This list assumes a new field in the base service definition struct. Adding new
fields to health checks is similar but mostly needs `HealthCheck` structs and
methods updating instead. Adding fields to embedded structs like `ProxyConfig`
is largely the same pattern but may need different test methods etc. updating.

 - [ ] Do all of the steps in [Adding a Simple Config
   Field For Client Agents](#adding-a-simple-config-field-for-client-agents).
 - [ ] `agent/structs` package
      - [ ] Add the field to `ServiceDefinition` (`service_definition.go`)
      - [ ] Add the field to `NodeService` (`structs.go`)
      - [ ] Add the field to `ServiceNode` (`structs.go`)
      - [ ] Update `ServiceDefinition.ToNodeService` to translate the field
      - [ ] Update `NodeService.ToServiceNode` to translate the field
      - [ ] Update `ServiceNode.ToNodeService` to translate the field
      - [ ] Update `TestStructs_ServiceNode_Conversions`
      - [ ] Update `ServiceNode.PartialClone`
      - [ ] Update `TestStructs_ServiceNode_PartialClone` (`structs_test.go`)
      - [ ] If needed, update `NodeService.Validate` to ensure the field value is
        reasonable
      - [ ] Add test like `TestStructs_NodeService_Validate*` in
        `structs_test.go`
      - [ ] Add comparison in `NodeService.IsSame`
      - [ ] Update `TestStructs_NodeService_IsSame`
      - [ ] Add comparison in `ServiceNode.IsSameService`
      - [ ] Update `TestStructs_ServiceNode_IsSameService`
      - [ ] **If** your field name has MultipleWords,
          - [ ] Add it to the `aux` inline struct in
            `ServiceDefinition.UnmarshalJSON` (`service_defintion.go`). 
            - Note: if the field is embedded higher up in a nested struct,
              follow the chain and update the necessary struct's `UnmarshalJSON`
              method - you may need to add one if there are no other case
              transformations being done, copy and existing example. 
            - Note: the tests that exercise this are in agent endpoint for
              historical reasons (this is where the translation used to happen).
 - [ ] `agent` package
      - [ ] Update `testAgent_RegisterService` and/or add a new test to ensure
        your fields register correctly via API (`agent_endpoint_test.go`)
      - [ ] **If** your field name has MultipleWords,
          - [ ] Update `testAgent_RegisterService_TranslateKeys` to include
            examples with it set in `snake_case` and ensure it is parsed
            correctly. Run this via `TestAgent_RegisterService_TranslateKeys`
            (agent_endpoint_test.go).
 - [ ] `api` package
      - [ ] Add the field to `AgentService` (`agent.go`)
      - [ ] Add/update an appropriate test in `agent_test.go`
        - (Note you need to use `make test` or ensure the `consul` binary on
          your `$PATH` is a build with your new field - usually `make dev`
          ensures this unless you're path is funky or you have a consul binary
          even further up the shell's `$PATH`).
 - [ ] Docs
      - [ ] Update docs in `website/source/docs/agent/services.html.md`
      - [ ] Consider if it's worth adding examples to feature docs or API docs
        that show the new field's usage.

Note that although the new field will show up in the API output of
`/agent/services` , `/catalog/services` and `/health/services`, those tests
right now don't exercise anything that's super useful unless custom logic is
required since they don't even encode the response object as JSON and just
assert on the structs you already modified. If custom presentation logic is
needed, tests for these endpoints might be warranted too. It's usual to use
`omit-empty` for new fields that will typically not be used by existing
registrations although we don't currently test for that systematically.

---

### Contributing Add A Changelog Entry (`docs/contributing/add-a-changelog-entry.md`)

# Adding a Changelog Entry

Any change that a Consul user might need to know about should have a changelog entry.

What doesn't need a changelog entry?
- Docs changes
- Typos fixes, unless they are in a public-facing API
- Code changes we are certain no Consul users will need to know about

To include a [changelog entry](../.changelog) in a PR, commit a text file
named `.changelog/<PR#>.txt`, where `<PR#>` is the number associated with the open
PR in Github. The text file should describe the changes in the following format:

````
```release-note:<change type>
<code area>: <brief description of the improvement you made here>
```
````

Valid values for `<change type>` include:
- `feature`: for the addition of a new feature
- `improvement`: for an improvement (not a bug fix) to an existing feature
- `bug`: for a bug fix
- `security`: for any Common Vulnerabilities and Exposures (CVE) resolutions
- `breaking-change`: for any change that is not fully backwards-compatible
- `deprecation`: for functionality which is now marked for removal in a future release

`<code area>` is meant to categorize the functionality affected by the change.
Some common values are:
- `checks`: related to node or service health checks
- `cli`: related to the command-line interface and its commands
- `config`: related to configuration changes (e.g., adding a new config option)
- `connect`: catch-all for the Connect subsystem that provides service mesh functionality
  if no more specific `<code area>` applies
- `http`: related to the HTTP API interface and its endpoints
- `dns`: related to DNS functionality
- `ui`: any change related to the built-in Consul UI (`ui/` folder)

Look in the [`.changelog/`](../.changelog) folder for examples of existing changelog entries.

If a PR deserves multiple changelog entries, just add multiple entries separated by a newline
in the format described above to the `.changelog/<PR#>.txt` file.

---

### Contributing Fork The Project (`docs/contributing/fork-the-project.md`)

# Forking the Consul Repo

Community members wishing to contribute code to Consul must fork the Consul project
(`your-github-username/consul`). Branches pushed to that fork can then be submitted
as pull requests to the upstream project (`hashicorp/consul`).

To locally clone the repo so that you can pull the latest from the upstream project
(`hashicorp/consul`) and push changes to your own fork (`your-github-username/consul`):

1. [Create the forked repository](https://docs.github.com/en/get-started/quickstart/fork-a-repo#forking-a-repository) (`your-github-username/consul`)
2. Clone the `hashicorp/consul` repository and `cd` into the folder
3. Make `hashicorp/consul` the `upstream` remote rather than `origin`:
   `git remote rename origin upstream`.
4. Add your fork as the `origin` remote. For example:
   `git remote add origin https://github.com/myusername/consul`
5. Checkout a feature branch: `git checkout -t -b new-feature`
6. [Make changes](../../.github/CONTRIBUTING.md#modifying-the-code)
7. Push changes to the fork when ready to [submit a PR](../../.github/CONTRIBUTING.md#submitting-a-pull-request):
   `git push -u origin new-feature`

---

### Ui Packages Consul Ui Docs Bookmarklets (`ui/packages/consul-ui/docs/bookmarklets.mdx`)

# Debug/Engineering Utilities

The Consul UI has a set of debug/engineering utility functions that only exist in non-production environments (they are noop'ed from production builds using Ember's `runInDebug`). You can call these either straight from Web Inspectors `console`, or by using `javascript:` URLs i.e. `javascript:Routes()`.

Below is a list of the most commonly used functions as bookmarklets followed by more detailed documentation on these utilities. The bookmarklets can be added to your browser by dragging the bookmarklet link into your browsers Bookmarks Bar. Please add any new commonly used environment variables as bookmarklets here for other engineers to use.

| Link/Bookmarklet | Description |
| ---- | ----------- |
| [Print Routing DSL](javascript:Routes()) | Print out Ember's Route DSL for the application |
| [Save Current Scenario](javascript:Scenario()) | Opens a tab with links to allow you to create a bookmarklet or share a URL of your current scenario (your Consul UI related development/debug cookies) |
| [Toggle Color Scheme](javascript:Scenario('CONSUL_COLOR_SCHEME=!')) | Toggle Color Scheme from light to dark or vice versa |
| [Enable ACLs](javascript:Scenario('CONSUL_ACLS_ENABLE=1')) | Enable ACLs |
| [Enable Agentless](javascript:Scenario('CONSUL_AGENTLESS_ENABLE=1')) | Enable synthetic nodes (agentless) in the mock data |
| [Enable TProxy](javascript:Scenario('CONSUL_TPROXY_ENABLE=1')) | Enable TProxy |
| [Enable Nspaces](javascript:Scenario('CONSUL_NSPACES_ENABLE=1')) | Enable Namespace Support |
| [Enable Peers](javascript:Scenario('CONSUL_PEERINGS_ENABLE=1')) | Enable Peering Support |
| [Enable Partitions](javascript:Scenario('CONSUL_PARTITIONS_ENABLE=1')) | Enable Admin Partition Support |
| [Enable SSO](javascript:Scenario('CONSUL_SSO_ENABLE=1')) | Enable SSO Support |
| [Enable Metrics](javascript:Scenario('CONSUL_METRICS_PROXY_ENABLE=1;CONSUL_METRICS_PROVIDER=prometheus')) | Enable all configuration required for viewing the full Metrics Visualization |
| [Enable Intl Debug](javascript:Scenario('CONSUL_INTL_DEBUG=1')) | Render intl interpolation variable names instead of interpolating them |
| [Intl Lorem Ipsum](javascript:Scenario('CONSUL_INTL_LOCALE=la-fk')) | Render all intl copy as lorem ipsum |
| [Intl Dashes](javascript:Scenario('CONSUL_INTL_LOCALE=-')) | Render all intl copy as `-` |
| [Toggle QUnit Display](javascript:(function()%7B(function()%7Bconst%20s%20%3D%20document.getElementById(%22ember-testing-container%22).style%3Bs.display%20%3D%20s.display%20%3D%3D%3D%20%22none%22%20%3F%20%22block%22%20%3A%20%22none%22%3B%7D)()%3B%7D)()%3B) | Toggle the QUnit display on or off during browser testing |

| Function | Arguments | Description |
| -------- | --------- | ----------- |
| `Routes(url)` | url: The url to pass the DSL to, if left `undefined` just use a blank tab | Provides a way to easily print out Embers Route DSL for the application or to pass it straight to any third party utility such as ember-diagonal |
| `Scenario(str)` | str: 'Cookie formatted' string, if left `undefined` open a new tab with a link/bookmarklet to the current Scenario | Provides a way to easily save and reload scenarios of configurations via URLs or bookmarklets |


You can also configure these manually or example using Web Inspector to set a cookie as follows:

```bash
CONSUL_ACLS_ENABLE=1
```

This will enable the ACLs login page, to which you can login with any ACL
token/secret.

| Variable | Default Value | Description |
| -------- | ------------- | ----------- |
| `CONSUL_ACLS_ENABLE` | false | Enable/disable ACLs support. |
| `CONSUL_ACLS_LEGACY` | false | Enable/disable legacy ACLs support. |
| `CONSUL_AGENTLESS_ENABLE` | false | Enable/disable synthetic nodes (agentless) in the mock data. |
| `CONSUL_TPROXY_ENABLE` | false | Enable/disable TProxy support globally (if not a service may have this applied randomly). |
| `CONSUL_NSPACES_ENABLE` | false | Enable/disable Namespace support. |
| `CONSUL_SSO_ENABLE` | false | Enable/disable SSO support. |
| `CONSUL_OIDC_PROVIDER_URL` | undefined | Provide a OIDC provider URL for SSO. |
| `CONSUL_LATENCY` | 0 | Add a latency to network requests (milliseconds) |
| `CONSUL_METRICS_POLL_INTERVAL` | 10000 | Change the interval between requests to the metrics provider (milliseconds) |
| `CONSUL_METRICS_PROXY_ENABLE` | false | Enable/disable the metrics proxy |
| `CONSUL_METRICS_PROVIDER` | | Set the metrics provider to use for the Topology Tab |
| `CONSUL_SERVICE_DASHBOARD_URL` | | Set the template URL to use for Service Dashboard links |
| `CONSUL_UI_CONFIG` | | Set the entire `ui_config` for the UI as JSON if you don't want to use the above singular variables |
| `CONSUL_SERVICE_COUNT` | (random) | Configure the number of services that the API returns. |
| `CONSUL_NODE_COUNT` | (random) | Configure the number of nodes that the API returns. |
| `CONSUL_KV_COUNT` | (random) | Configure the number of KV entires that the API returns. |
| `CONSUL_INTENTION_COUNT` | (random) | Configure the number of intentions that the API returns. |
| `CONSUL_POLICY_COUNT` | (random) | Configure the number of policies that the API returns. |
| `CONSUL_ROLE_COUNT` | (random) | Configure the number of roles that the API returns. |
| `CONSUL_NSPACE_COUNT` | (random) | Configure the number of namespaces that the API returns. |
| `CONSUL_UPSTREAM_COUNT` | (random) | Configure the number of upstreams that the API returns. |
| `CONSUL_EXPOSED_COUNT` | (random) | Configure the number of exposed paths that the API returns. |
| `CONSUL_CHECK_COUNT` | (random) | Configure the number of health checks that the API returns. |
| `CONSUL_OIDC_PROVIDER_COUNT` | (random) | Configure the number of OIDC providers that the API returns. |
| `CONSUL_RESOURCE_<singular-resource-name>_<access-type>` | true | Configure permissions e.g `CONSUL_RESOURCE_INTENTION_WRITE=false`. |
| `DEBUG_ROUTES_ENDPOINT` | undefined | When using the window.Routes() debug utility ([see utility functions](#browser-debug-utility-functions)), use a URL to pass the route DSL to. %s in the URL will be replaced with the route DSL - http://url.com?routes=%s  |

See `./mock-api` for more details.

---

### Ui Packages Consul Ui Docs Data Layer (`ui/packages/consul-ui/docs/data-layer.mdx`)

---
title: The Data/Model Layer
---
# The Data/Model Layer

Consul UI is currently backed by an Ember conventional ember-data model/adapter/serializer set of classes. But the primary unit for the model layer of the application are our `Repository` classes which 'front' ember-data and provide a layer of abstraction between the application code and ember-data.

## ember-data Model classes

These model classes are primarily used to describe the `schema` of our data objects. We mainly use the ember-data `@attr` decorator to do this, but we also have  `@nullValue` and `@replace` decorators which allow you to define what to do when the HTTP API responds with a `null` value for a property (using `@nullValue`) something which is very common in with the Consul HTTP API. `@nullValue` is a shortcut for `@replace` which can be used to declaratively specify any sort of replacement. For example we use this for our HealthCheck model to replace a `Type` property with an empty string (`''`) to the value `serf` (which is what the empty string actually means).

When you come to need extra checks for abilities or characteristics of models, prefer using our `test` (or `is`/`can`) helpers along with `ember-can`s `Ability` classes instead of adding more computed properties to the model classes. Model classes should be primarily used for declaratively specifying the schema for the model. Here 'prefer' means don't worry if you can't, it's fine to keep using model classes if you can't use the `Ability` classes for some reason.

We don't use ember-data to model relationships spread across more than one API request. Please use nested `DataLoader`/`DataSource` components which offer a more flexible/featured way to model relationships between different API requests.

Whilst we don't use ember-data async relationships, we do use `ember-data-model-fragments` _minimally_ for when you need to add computed/tracked properties to nested objects of a model. We use them minimally because the compatibility matrix for this addon with ember-data can often lead to this addon blocking ember upgrades. Despite this, if you need to use it, then use it, we have it installed as a dependency currently.

## ember-data Adapter classes

We use a completely custom base adapter class (HTTPAdapter) which is based on (but doesn't inherit from) the ember-data RESTAdapter. This is not as strict at sticking to REST conventions as the original RESTAdapter, which suits Consul's HTTP API a lot better.

The adapter aims to be very simple and gives full control of the HTTP request to the engineer all in one place. For example:

```javascript
async requestForQuery(request, { dc, ns, partition, index, id, uri }) {
  return request`
    GET /v1/internal/ui/nodes?${{ dc }}
    X-Request-ID: ${uri}

    ${{
      ns,
      partition,
      index,
    }}
  `;
}
```

Here the `request` Tagged Template Literal here ensures that it is very difficult not to URL encode user provided values. Depending on where you interpolate a user provided value the value will be encoded accordingly, whether thats URL parameters, query parameters or header values. The format of the template follows the HTTP protocol as much as possible.

1. The HTTP method followed by a single space, followed by the URL for the request, followed by a question mark, an object interpolated directly after the question mark will be encoded as query parameters (using ='s &'s etc).
2. After a single return, you can optinally provide HTTP header values, separated by a single return.
3. A double return then lets you interpolate HTTP body values for POSTing/PUTing, if you've defined the HTTP method to be a GET request, then these values will be added to the query parameters.

As `request` performs all of the encoding for you when the user provided values are interpolated into the template, the only way you can add a URL encoding error is if you actually type one yourself into the code. This avoids all manner of bugs.

`request` is usually just returned by the method, but you can further normalize the response here also (which gives you direct access to the methods parameters without having to pass them all through somehow to the serializer. For example:

```javascript
async requestForQuery(request, { ns, dc, index }) {
  const respond = await request`
    GET /v1/partitions?${{ dc }}

    ${{ index }}
  `;
  // deleting the x-consul-index header disables blocking queries for this request
  await respond((headers, body) => delete headers['x-consul-index']);
  return respond;
}
```

`respond` here acts as kind of a distributed filter chain, in that you can call `respond` multiple times across different classes and it will keep filtering/mutating the values as you go. As the request and the response of a HTTP API endpint is usually very tightly coupled, it probably makes sense for us to do more and more of this here in the adapter in the future, and eventually to move all of this directly into our repositories.

## ember-data Serializer classes

Currently the majority of our serializing code (of which there is very little), is done in conventional ember-data serializers. Our Serializers also use a HTTPSerializer class as a base, which _does_ inherit form the standard ember-data serializer. Every model object gets fingerprinted with a `['partition', 'nspace', 'dc', 'slug', ...]` formatted unique ID. Therefore most models have `Partition`, `Namespace`, and `Datacenter` properties. The functions used for serializing are unfortunately a little verbose, but this gives use the distributed filter chain characteristic and was originally planned to also support streaming JSON responses.

## Consul UIs Repository classes

Most importantly Consul UI uses an extra `Repository` class per data model. This gives us a thin layer of abstraction over ember-data which means the rest of our application doesn't need to know it is using ember-data at all. We have a base/abstract Repository class which all of the others inherit from for now, and mostly just pass things through to more conventional ember-data access using the ember-data store. Importantly, it also gives us a place to smooth over any wrinkles that we might have with ember-data (and/or ember-changeset) without doing that outside of the data/model layer.

All access methods on the `Repository` classes follow a fairly standard naming scheme `findAll/findBySlug`, but since we use our `@dataSource` decorator/annotation to access these methods, functionally the naming isn't important, so naming is mainly for documentation/consistency purposes.

You'll find most `Repository` methods look like this:

```javascript
@dataSource('/:partition/:ns/:dc/auth-method/:id')
async findBySlug() {
  return super.findBySlug(...arguments);
}
```

Which means that `partition`, `ns` and `id` parameters will eventually find their way into the Adapter class ready for you to make the request.

The above method can be called from within the app, along with Blocking Query and/or more traditional polling support, using our DataSource and/or DataLoader component:

```hbs
{{! with loading, loaded, error and disconnection states }}
<DataLoader @src={{uri '/${partition}/${nspace}/${dc}/auth-method/${id}'
    (hash
      partition=route.params.partition
      nspace=route.params.nspace
      dc=route.params.dc
      id=(or route.params.id '')
    )
  }}
as |loader|>
  <:loaded>
    Show the Auth Methods here {{loader.data}}
  </:loaded>
</DataLoader>

{{! or just }}
<DataSource @src={{uri '/${partition}/${nspace}/${dc}/auth-method/${id}'
    (hash
      partition=route.params.partition
      nspace=route.params.nspace
      dc=route.params.dc
      id=(or route.params.id '')
    )
  }}
as |source|>
    Show the Auth Methods here {{source.data}}
</DataSource>
```

See the separate Component documentation for both the DataLoader and the DataSource component for more details.

---

### Ui Packages Consul Ui Docs Hds (`ui/packages/consul-ui/docs/hds.mdx`)

---
title: Hashicorp Design System
---

# Hashicorp Design System

The application integrates setup that make it possible to use the [Hashicorp Design System (HDS)](https://github.com/hashicorp/design-system) in the application. The application also
bundles [TailwindCSS](https://tailwindcss.com/) to follow a utility-based CSS approach that mixes well with HDS utility classes. Tailwind is expected to be used
as a supplement to HDS's utility classes, and should mainly be used for layouting.

## Design Tokens

### Colors

To make sure we follow HDS-guidelines, we disabled Tailwind's color utilities completely. Instead of relying on Tailwind for colors, you should be working with the colors from HashiCorp-Design-System and follow the [guidance in the docs](https://design-system-components-hashicorp.vercel.app/foundations/colors) on how to use them:

```hbs
<h2 class='hds-foreground-strong'>
  HDS is awesome
</h2>
```

Regular TailwindCSS colors aren't available.

```hbs
<h2 class='text-red-400'>
  TailwindCSS colors won't work
</h2>
```

### HDS core styles

HDS core styles are available via the provided `hds`-[utility-classes](https://design-system-components-hashicorp.vercel.app/foundations/typography) made available via `@hashicorp/design-system-tokens`.

You for example would work with HDS' typography styles in this way:

```hbs
<p class='hds-typography-display-400'>
  A paragraph styled via HDS.
</p>
```

Because HDS provides utility classes for core styles, you should not be using Tailwind classes to customize core styles like typography.

### Combining HDS and Tailwind

Tailwind is used as a supplement to what HDS provides. You will be using Tailwind for layouting and other utility functionality that isn't provided by HDS.

```hbs
<button type='button' class='underline transform scale-100 transition ease-in-out hover:scale-125'>
  Hover me
</button>
```

### Components

All components from Hashicorp Design System are available for you to use. Here's an example that shows how to implement a navigation bar with HDS and Tailwind in combination.

```hbs
<nav
  class='h-16 w-full consul-surface-nav flex items-center justify-between px-4 hds-font-weight-medium'
>
  <ul class='flex items-center'>
    <li>
      {{! should probably be a context-switcher }}
      <FlightIcon
        @name='menu'
        @color='var(--token-color-foreground-high-contrast)'
        class='cursor-pointer'
      />
    </li>
    <li>
      <FlightIcon @name='consul' class='ml-4 h-8 w-8' @color='var(--token-color-consul-brand)' />
    </li>
  </ul>
  <ul class='flex items-center space-x-2'>
    <li>
      <Hds::Dropdown as |dd|>
        <dd.ToggleButton @text='Help' />
        <dd.Title @text='Consul HDS' />
        <dd.Interactive @text='Documentation' @icon='docs-link' />
        <dd.Interactive @text='HashiCorp Learn' @icon='learn-link' />
        <dd.Separator />
        <dd.Interactive @text='Provide Feedback' @icon='github' />
      </Hds::Dropdown>
    </li>
    <li>
      <Hds::Link::Standalone
        @href='https://hashicorp.com'
        @icon='settings'
        @iconPosition='trailing'
        @size='medium'
        @text='Settings'
      />
    </li>
  </ul>
</nav>
```

Please notice the custom `consul-surface-nav`-utility class. This class has been created because at the moment of writing,
there wasn't a `hds-surface-` that matched what we needed for this navigation header. If you run into situations like
that, you can add a `consul-`-utility class to the `utilities`-layer in `tailwind.scss`.

```css
// consul-ui/app/styles/tailwind.scss
@layer utilities {
  .consul-surface-nav {
    background: var(--token-color-palette-neutral-700);
  }
  // ...
}
```

This should only be used as a last resort and you should stick to `hds-`-utilities whenever possible.

---

### Ui Packages Consul Ui Docs I18n (`ui/packages/consul-ui/docs/i18n.mdx`)

# i18n

At the time of writing we are currently in the process of
localizing/internationalizing the entire codebase. To help us to do this we
use the [`ember-intl`](https://github.com/ember-intl/ember-intl) ember addon

Please see [`ember-intl`s documentation](https://ember-intl.github.io/ember-intl/versions/master/docs/quickstart)
for information on how to use this.

Additionally we have several dev-time only cookie/environment variables to
help engineers see what still requires localizing, to test copy/text using
localizations other than en-us and to view any required interpolation
variables with rendered text, instead of doing the actual interpolation
itself.

| Link/Bookmarklet | Description |
| ---- | ----------- |
| [Enable Intl Debug](javascript:Scenario('CONSUL_INTL_DEBUG=1')) | Render intl interpolation variable names instead of interpolating them |
| [Intl Lorem Ipsum](javascript:Scenario('CONSUL_INTL_LOCALE=la-fk')) | Render all intl copy as lorem ipsum |
| [Intl Dashes](javascript:Scenario('CONSUL_INTL_LOCALE=-')) | Render all intl copy as `-` |

## en-us

If in doubt, or unless specifically directed otherwise, all text should use
sentence case, apart from 'Consul Nouns' which should use capital casing.
'Consul Nouns' here would be things like 'Services', 'Ingress Gateways', 'Nodes'
and other things that are specific 'objects' in Consul. We also use the form
'ACLs' for acronyms instead of 'Acls'.

```yaml
text: Viewing metrics for Ingress Gateways is not currently supported
text: ACLs are disabled
```

instead of:

```yaml
text: Viewing Metrics for ingress gateways is not currently supported
text: Acls are disabled
```

---

### Ui Packages Consul Ui Docs Node Metadata Hds Migration (`ui/packages/consul-ui/docs/node-metadata-hds-migration.md`)

# Node "Metadata" tab — HDS migration

Migrate the node detail **Metadata** tab (`/ui/{dc}/nodes/{name}/metadata`) from
the legacy `TabularCollection` table to the HDS **Card** layout shown in the
Figma design (`HDS-Migration`, node `3324-78030`).

Because the same `Consul::Metadata::List` component also renders the
service-instance metadata tab (`/ui/{dc}/services/{name}/instances/{id}/metadata`),
this single change migrates **both** tabs consistently.

## Goal / before → after

**Before:** `Consul::Metadata::List` rendered a `TabularCollection` with `Key` /
`Value` `<th>`/`<td>` columns and row striping — a full data-grid treatment.

**After:** a single `Hds::Card::Container` (white surface, `24px` padding,
`6px` radius, subtle border + low elevation) containing a two-column
**Key / Value** layout. No row dividers, no sortable headers, no toolbar, no
search, no copy button — a read-only description card, per Figma.

### Layout (from Figma node `3324-78089`)

| Property        | Value                                             |
| --------------- | ------------------------------------------------- |
| Card chrome     | `Hds::Card::Container @level="mid" @hasBorder`     |
| Card padding    | `24px`                                             |
| Columns         | Key (fixed `300px`) + Value (fills remaining)      |
| Column gap      | `32px`                                             |
| Row gap         | `16px`                                             |
| Header weight   | semibold (`Key` / `Value`)                         |
| Body weight     | regular                                            |
| Typography      | `body-300` (16px / 24px), `foreground-strong`      |
| Wrapping        | long keys/values wrap (`word-break: break-word`)   |

The consul logo shown before `v1.17.2` in the mock is **sample data**, not a
generic feature — metadata values are arbitrary strings and are rendered as-is.

## Architecture — CSS grid, not two stacks

The card uses a CSS **grid** (`grid-template-columns: 300px 1fr`) rather than two
independent flex columns so that Key and Value rows stay aligned even when a key
wraps onto multiple lines (e.g. the long `consul-dashboard-url…` sample). The
header cells (`Key` / `Value`) and each `dt` / `dd` pair are direct grid items.

## File-by-file changes

### 1. `app/components/consul/metadata/list/index.hbs`

Replace `TabularCollection` with `Hds::Card::Container` wrapping a `<dl>` grid:

- `data-test-metadata` moves onto the card container (preserved).
- Each entry renders `<dt data-test-metadata-key>` + `<dd data-test-metadata-value>`.

### 2. `app/components/consul/metadata/list/index.scss` (new)

Grid + typography tokens (`--token-typography-body-300-*`,
`--token-color-foreground-strong`, `--token-typography-font-weight-*`). Mirrors
the tokens used by the RTT card migration.

### 3. `app/styles/components.scss`

Register the new stylesheet:
`@import 'consul-ui/components/consul/metadata/list';`

### 4. `app/components/consul/metadata/list/index.js`

Unchanged — classic `tagName: ''` component; public `@items` API is unchanged
(`[key, value]` pairs from `entries`).

### 5. Tab templates — unchanged

`app/templates/dc/nodes/show/metadata.hbs` and
`app/templates/dc/services/instance/metadata.hbs` keep their `Route` wrapper and
`EmptyState` blocks; only the shared component's internals changed.

## Tests / page objects

The old markup emitted `[data-test-tabular-row]` per row (from
`TabularCollection`); the new grid emits one `[data-test-metadata-key]` per row.
Both metadata collections were updated to count that selector instead:

- `tests/pages/dc/nodes/show.js` — `.consul-metadata-list [data-test-metadata-key]`
- `tests/pages/dc/services/instance.js` — `.metadata [data-test-metadata-key]`

The `show.feature` metadata-count scenarios (e.g. "I see 3 of the metadata
object") continue to pass against the new selector.

## Testing

- `ember test --filter metadata`
- Visual QA at `/ui/dc1/nodes/{node}/metadata` and
  `/ui/dc1/services/{name}/instances/{id}/metadata`: card renders, Key/Value rows
  align, long keys wrap, and the empty state ("This node/instance has no
  metadata.") is unchanged when `Meta` is absent.

## Out of scope

- Toolbar / search / sort / pagination — not present in the Figma for this tab.
- Copy buttons on values — not in the Figma.
- Route query params / controller — none needed (no search/sort/filter state).

---

### Ui Packages Consul Ui Docs Node Rtt Hds Migration (`ui/packages/consul-ui/docs/node-rtt-hds-migration.md`)

# Node Detail — Round Trip Time Tab — HDS Migration

**Branch:** `suresh/nodes-hds-migration`
**Page:** `/ui/dc1/nodes/node-0/round-trip-time` (route `dc.nodes.show.rtt`)
**Figma:** [HDS-Migration — node `3324:70738`](https://www.figma.com/design/Hpmmh25EIyt0ZM0Jsv8xT9/HDS-Migration?node-id=3324-70738&m=dev)
**Type:** HDS Migration
**Scope:** The Round Trip Time tab body on a single node's detail page only — the min/median/max
stats block and the radial tomography graph. Follow-on to
[`nodes-hds-migration.md`](./nodes-hds-migration.md), which deferred "Node detail page & tabs" to
separate tickets. Sibling of [`node-healthchecks-hds-migration.md`](./node-healthchecks-hds-migration.md)
and [`node-lock-sessions-hds-migration.md`](./node-lock-sessions-hds-migration.md).

---

## Overview

The Figma target redesigns the Round Trip Time tab so its whole body sits inside a single **HDS
card**, with the min/median/max stats and the radial graph laid out **side by side** (separated by a
vertical divider) instead of stacked. Four things change, all presentational:

1. The tab body is wrapped in an **`Hds::Card::Container`** (white surface, 1px border, low
   elevation shadow, `6px` radius, `24px` padding) — currently the content sits bare in a
   `.tab-section` with no card chrome.
2. The stats and the graph move from **stacked** (stats above graph) to a **horizontal two-column**
   layout with a **vertical separator** between them (Figma: `flex`, `gap: 64px`, `items: start`).
3. The legacy `.definition-table` `<dl>/<dt>/<dd>` stats block is replaced with a simple two-column
   label/value layout using HDS body typography, and the labels gain a "time" suffix —
   **"Minimum time" / "Median time" / "Maximum time"** (currently "Minimum" / "Median" /
   "Maximum").
4. The `Consul::Tomography::Graph` SVG itself is **unchanged** — its colors are already tokenized.
   Only its container/placement changes.

No backend, data-model, or route-redirect changes are required — the `rtt.js` redirect (bounce to
`dc.nodes.show` when `tomography.distances` is empty) stays as-is.

---

## Files in Scope

| File | Change type | Notes |
|---|---|---|
| `app/templates/dc/nodes/show/rtt.hbs` | HBS | Wrap body in `Hds::Card::Container`; two-column layout; add vertical separator; rename stat labels; drop `.definition-table` `<dl>` |
| `app/styles/routes/dc/nodes/index.scss` | CSS | New scoped rules for the RTT card's two-column layout + stats typography (scoped to `dc.nodes.show.rtt`) |
| `app/components/consul/tomography/graph/index.scss` | CSS | `.background` disc fill `surface-strong` → `transparent` to match the Figma white field (see §4). Only usage of this component is the RTT tab. |
| `app/routes/dc/nodes/show/rtt.js` | No change | Empty-distances redirect unchanged |
| `translations/**` | No change | Labels kept as inline literals, consistent with the pre-migration template (see §3) |

---

## Comparison: Current vs Target

### 1. Card chrome — bare `.tab-section` → `Hds::Card::Container`

| | Current | Target |
|---|---|---|
| **Visual** | Stats `<dl>` and graph sit directly in `.tab-section` with no border/background. | Whole body in a white card: `--token-color-surface-primary` bg, 1px border, **Elevation/Surface/Low** shadow, `border-radius: 6px`, `24px` padding, overflow clipped. |
| **Figma** | — | Card node `3324:70797`: `bg surface-primary`, `p-24`, `rounded 6px`, `shadow 0 0 0 1px rgba(101,106,118,.15), 0 1px 1px rgba(...,.05), 0 2px 2px rgba(...,.05)`, `overflow-clip`. |
| **Implementation** | — | Reuse the established card primitive: `<Hds::Card::Container @level="mid" @hasBorder={{true}} @overflow="hidden">` — same invocation as `consul/server/card` and `consul/peer/bento-box`. `@level="mid"` maps to the Elevation/Surface/Low shadow in the Figma. |
| **Status** | ⚠️ **Change required.** | — |

> Note: unlike the health-checks / service-instances **toolbar** cards (which used
> `--token-color-surface-faint` + `--token-color-border-faint`), this content card uses the standard
> `Hds::Card::Container` white surface + elevation shadow, matching the Figma exactly.

### 2. Layout — stacked → side-by-side with vertical separator

| | Current | Target |
|---|---|---|
| **Visual** | Stats block on top, graph below (vertical stack in `.tab-section`). | Stats column on the **left**, graph on the **right**, side by side, with a **vertical divider** between them. Figma: `flex`, `gap: 64px`, `align-items: start`; separator is `w-px h-[329px]`. |
| **Implementation** | — | Card body is a horizontal flex container (`gap: 64px`, `align-items: flex-start`). Left: the stats column. Middle: a `1px` bordered `<div class="consul-node-rtt__divider">` that `align-self: stretch`es to the graph height. Right: `<Consul::Tomography::Graph>`. |
| **Status** | ✅ **Done.** `Hds::Separator` was rejected — it renders a horizontal `<hr>` with only a `@spacing` arg (no orientation), so the bordered-`<div>` divider is cleaner. |

### 3. Stats block — `.definition-table` `<dl>` → two-column label/value + "time" labels

| | Current | Target |
|---|---|---|
| **Visual** | `<dl><dt>Minimum</dt><dd>0.12ms</dd>…` styled by legacy `%definition-table` (dt = `display-100-semibold`, small). | Three rows, each `[label]  [value]`. Label = **Body/300/Semibold** (16px / 600 / lineHeight 24, color `#0c0c0e` → `--token-color-foreground-strong`), fixed `120px` width. Value = **Body/300/Regular** (16px / 400). Row gap `20px`, label→value gap `16px`. Labels read **"Minimum time" / "Median time" / "Maximum time"**. |
| **Current code** | `rtt.hbs` lines 14–35 — `<div class="definition-table"><dl>…</dl></div>`. | Replace with a small flex-column block of three `[label][value]` rows. Keep the `{{format-number … maximumFractionDigits=2}}ms` value formatting exactly. |
| **Status** | ⚠️ **Change required** — markup + typography + label copy. | — |

**Copy decision (resolved):** labels are kept as **inline literals** ("Minimum time" etc.),
consistent with the pre-migration template. No new i18n keys were added.

### 4. Tomography graph — one background fix

| | Current | Target |
|---|---|---|
| **Visual** | Radial SVG with a **grey filled disc** (`--token-color-surface-strong`), `neutral-300` dashed axis rings + solid border, `consul-foreground` (pink) spokes + center point, `neutral-300`/`foreground-strong` tick labels. | Radial graph on the card's **white field** — only dashed ring outlines, no grey disc; pink spokes; ms tick labels to the right. |
| **Code** | `consul/tomography/graph/index.scss` `.background { fill: var(--token-color-surface-strong); }`. | `.background { fill: transparent; }` — the only change to the component. SVG geometry, sampling, spokes, ticks, and the `336px` size are unchanged. This component is used **only** on the RTT tab, so the change is contained. |
| **Status** | ✅ **Done** — single-line fill change (small deviation from the original "no change" scope, to match the Figma white field). |

> Ring geometry differs slightly (current draws dashed rings at 25/50/75/100%; the Figma shows
> 33/66/100%). This was **left as-is** — changing it would also move the tick-label math, a deeper
> change than this presentational pass warrants.

---

## Task Checklist

- [x] Wrap the `rtt.hbs` body in `<Hds::Card::Container @level="mid" @hasBorder={{true}} @overflow="hidden">`
- [x] Make the card body a horizontal flex (`gap: 64px`, `align-items: start`) — stats left, graph right
- [x] Replace the `.definition-table` `<dl>` with a flex-column of three `[label 120px semibold][value regular]` rows, gap `20px`
- [x] Rename labels to "Minimum time" / "Median time" / "Maximum time" (kept as inline literals — see §3)
- [x] Preserve `{{format-number … maximumFractionDigits=2}}ms` value formatting
- [x] Add a vertical divider between stats and graph (`1px` bordered `<div>`; `Hds::Separator` is horizontal-only)
- [x] Fix the graph `.background` disc fill to `transparent` to match the Figma white field (§4)
- [x] Add scoped SCSS in `styles/routes/dc/nodes/index.scss` (`html[data-route^='dc.nodes.show.rtt']`) for layout + stats typography; global `%definition-table` untouched
- [x] Keep the `{{#if (gt tomography.distances.length 0)}}` guard and the `rtt.js` empty-distances redirect
- [ ] Run `ember test --filter "nodes/show"` — confirm no regressions
- [x] Visual QA at `http://localhost:4200/ui/dc1/nodes/node-1/round-trip-time` against the Figma

---

## Acceptance Criteria

1. The RTT tab body renders inside a single white `Hds::Card::Container` with a 1px border, low
   elevation shadow, `6px` radius, and `24px` interior padding.
2. Stats and graph are laid out side by side, stats on the left, graph on the right, with a vertical
   separator between them.
3. The stats show three rows — **Minimum time / Median time / Maximum time** — each with a `120px`
   semibold label and a regular value, using Body/300 (16px) typography.
4. Values keep the current formatting (`format-number` to 2 fraction digits + `ms`).
5. The radial tomography graph is visually unchanged from today.
6. When a node has no tomography distances, the tab still redirects to `dc.nodes.show` (unchanged).
7. No visual regressions on other node tabs or on any other page that uses `%definition-table`
   (the new styles must be scoped to `dc.nodes.show.rtt`).

---

## Testing Notes

- Run `ember test --filter "nodes/show"` to catch template/selector regressions on the node detail
  route.
- Visually verify at `http://localhost:4200/ui/dc1/nodes/node-1/round-trip-time` against the Figma
  frame (`3324:70738`), checking card chrome, the two-column layout, the vertical separator, and the
  "…​ time" labels.
- Confirm `%definition-table` styling elsewhere (e.g. intentions edit) is unaffected — the new rules
  must be route-scoped, not global.
- Confirm a node with no RTT data still bounces back to the node overview tab.

---

## Out of Scope (this ticket)

- `Consul::Tomography::Graph` internals (SVG geometry, sampling, colors) — reused unchanged.
- Node header (name / status badge / "Imported from billing" badge / address) and breadcrumbs —
  handled elsewhere.
- Tab navigation bar — already migrated.
- Backend / API / model / `rtt.js` redirect logic — no changes.

---

### Ui Packages Consul Ui Docs Service Tags Hds Migration (`ui/packages/consul-ui/docs/service-tags-hds-migration.md`)

# Tags (`TagList`) — HDS migration

Migrate the shared `TagList` component from the legacy `<dl>` / CSV-list layout
to the HDS **Card + Tag pills** design shown in the Figma
(`HDS-Migration`, node `3376-125619`).

`TagList` is shared, so this single change updates **both** consumers:

- **Service "Tags" tab** — `/ui/{dc}/services/{name}/tags`
  (`app/templates/dc/services/show/tags.hbs`).
- **Service-instance "Tags" section** — the metadata tab
  (`app/templates/dc/services/instance/metadata.hbs`, under its `<h2>Tags</h2>`).

## Goal / before → after

**Before:** a `<dl class="tag-list">` with a `<dt>` "Tags" label (tag-icon mask +
tooltip) and a `<dd>` rendering each tag as a bare `<span>` in a comma-separated
(`%csv-list`) run.

**After:** an `Hds::Card::Container` (white surface, `24px` padding, `6px`
radius, subtle border + low elevation) containing a `flex-wrap` row of
`Hds::Tag` pills. No inline "Tags" label — the tab/section heading already names
it, matching the Figma.

### Layout (from Figma node `3376-125670`)

| Property      | Value                                                   |
| ------------- | ------------------------------------------------------- |
| Card chrome   | `Hds::Card::Container @level="mid" @hasBorder`           |
| Card padding  | `24px`                                                   |
| Tags row      | `display: flex; flex-wrap: wrap;` — `gap: 12px 8px`      |
| Pill          | `Hds::Tag @text={{tag}}` (default `primary` color)       |

The Figma maps each pill to the Helios **Tag** component, and `Hds::Tag` is
already used for the tag pills in the Service / Service-instance tables — so the
pill styling (border, `20px` radius, `13px` medium text, `foreground-primary`)
comes from HDS directly with no bespoke CSS.

## File-by-file changes

### 1. `app/components/tag-list/index.hbs`

Non-block branch replaced: `<dl>` → `Hds::Card::Container` wrapping
`<div class="tag-list__tags" data-test-tags>` with one `<Hds::Tag @text />` per
tag. `data-test-tags` moves onto the tags container (the direct parent of the
pills). The recursive `has-block` yield branch is unchanged.

### 2. `app/components/tag-list/index.scss`

Rewritten: dropped the `%tag-list` / `%horizontal-kv-list` / `%csv-list`
`@extend`s and the unused `td.tags` selector. Now just card padding + the
flex-wrap tags row with the Figma gaps.

### 3. Tab templates — unchanged

`services/show/tags.hbs` and `services/instance/metadata.hbs` keep their `Route`
wrappers, empty states, and (for the instance) the `<h2>Tags</h2>` heading.

## Tests / page objects

`Hds::Tag` renders each pill as `<span class="hds-tag">…</span>`, so it remains
the first-child span under `[data-test-tags]` in order — the existing feature
assertions (`[data-test-tags] span:nth-child(n)` → "Tag1" / "Tag2" / "Tag3") in
`services/show.feature` and `services/instances/show.feature` continue to pass
via the `find()`-based `see the text … in …` step.

The one page-object collection that targeted the old `<dd> > span` markup was
updated:

- `tests/pages/dc/services/show.js` — `.tag-list dd > span` → `.tag-list .hds-tag`.

## Testing

- `ember test --filter "Show Service"` — 15 pass (includes the tags-display
  scenario and the instance tags-section assertions).
- Visual QA at `/ui/dc1/services/{name}/tags` and
  `/ui/dc1/services/{name}/instances/{id}/metadata`: tags render as pills inside
  the card, wrapping across rows; empty states unchanged.

## Out of scope

- No routing / data changes — `TagList`'s `@item` / `@tags` API is unchanged.
- The recursive block-form (`has-block`) usage is preserved as-is.

---

### Ui Packages Consul Ui Docs Significant Patterns (`ui/packages/consul-ui/docs/significant-patterns.mdx`)

# Significant patterns

## Ember specific

The Consul UI tries to follow typical conventional ember but differs
significantly in several places:

### Routing

We use a declarative configuration format for our routing rather than Ember's
DSL. The format is closely modeled on Ember's DSL and if you need to generate
Ember's DSL from this for some reason you can use one of our Debug Utilities
to do this.

All Route based configuration including paths, queryParams, simple redirects
and non-default templateNames are configured in this configuration format.
Please note redirects and template names use relative slash separated paths to
avoid copypasta and for future potential reuse purposes.

### Routes

We use a specific BaseRoute as a parent Route for **all** our Routes. This contains project wide
functionality for several important additions. If you use the normal `ember
generate route route-name` generator this inheritance is automatically added
for you. If you don't use the generator please ensure you use:

```js
import Route from 'consul-ui/routing/route';
export default class NameOfRoute extends Route {}
```

Query parameters should be added to Routes, not Controllers. If a controller
has no query parameters configured they are copied over from the Route.
Preferably we don't use Controllers, but this doesn't mean you shouldn't if
you need to.

### Routlets

We use have a concept of 'routlets', the combination of a `<Route />` and
`<Outlet />` components that we use within our route templates to achieve
several different pieces of functionality.

Every route template should be wrapped in a `<Route @name={{routeName}}>` component and ever
`{{outlet}}` should be wrapped in `<Outlet @name={{routeName}}>` component.
The `routeName` variable is made available in every single route template and
it equal to the routeName of the current template e.g. `dc.services.index`

### DataSources

In order to support our live updating long-polling blocking queries we use
'DataSources' which come in two flavours. A service backed
approach for use within javascript:

```js
class RouteName extends Route {
  @service('datasource/service') data;
  async model(params) {
    return this.data.source(uri => uri`/${params.nspace}/${params.dc}/services`)
  }
}
```

This method returns an Ember proxy that lets you access the data as if it was
'just the data', but is also reactive/auto updates when the data in the
backend updates, for example:

```hbs

{{@model.Name}}

```

And a component based approach for use within templates.

```hbs
<DataSource @src={{
  uri '/${nspace}/${dc}/services'
  (hash
    nspace="default"
    dc="dc"
  )
  }}
  @onchange=""
/>
```

See the relavant component/service documentation for more detail.

### Components

You could group our components into two different groups:

1. UI Components - generic components not necessarily specific to the product.
2. Consul Components - Components that are specific to Consul/the product.
These are mostly 'glorified partials'.

Mostly the CSS for a component lives in the component folder itself, but if it
makes sense for it not to live here, thats fine.

We currently use a mix of named/block slots and contextual components and its
fair to say that we use more named/block slots, but both are fine depending on
your use case.

### Significant Addons

- `ember-data` - model layer
- `ember-stargate` - wormhole/portal/put this DOM over there functionality
- `ember-can` - user abilities and permissions
- `ember-composable-helpers`, `ember-string-fns`, `ember-truth-helpers` - helpers x lots
- `ember-changeset` and `ember-changeset-validations` - form validation
- `ember-cli-flash` - notifications

Please see our package.json file dependencies.

## Consul specific

### Namespace support

Whilst Consul namespaces are a CE feature of Consul, we almost always build
things 'pretending' that namespaces are always enabled. Then there is code
within the data layer of the application that removes any namespace related
query parameters.

Namespaces are a little more complex to think about than other things in
Consul as we have the idea of 'the default' namespace, and then additionally
an ACL token can have 'a default' namespace (not the same as 'the default'
namespace) which is a little like a namespace that is assigned to the token,
or where the token originated from. The Consul backend can then use the tokens
default/origin/assigned namespace to figure out whether and how to filter any
results by namespace before sending those results to the UI.

When you are requesting data from the API you should always include a
namespace (if applicable to the API endpoint), but you should never default
the namespace value to 'the default' namespace - `default`. Conversly, when
receiving data from an API response, there are some places where we do default
a namespace to 'the default' namespace `default`. The easiest way to think
about this is: If its on the way out, don't add a default ever, if its on the
way in you may need to default any potentially empty Namespace parameters to
`default` (due to using Consul without namespaces enabled), there is a
NspaceAbility and/or an `env` feature flag to help you to discover this.

We've made various decisions in the UI to make it hard to omit a namespace
when you shouldn't, for example using our DataSource component requires you to
include a defined namespace parameter (even if the value for that is `''`),
and the route parameter `nspace` value will always default to `''`. It also
reminds you to be continually thinking about namespaces whilst adding new
features.

Lastly, we always refer to namespaces in code as the word `nspace` or
`nspaces`. Both `ns` and `namespace` have other purposes in Ember and Ember
Data (in various places one is significant but not the other and vice versa).
Always using the term `nspace` means we avoid any strange bugs or
clashes/overwrites with anything in Ember or Ember Data core. The only place
where we use `ns` or `Namespace` is to construct API requests/responses.
Sometimes when using this term in CSS or on plain javascript objects, this may
seem unnecessary but it just helps smooth over writing code around namespaces
as you don't have the split second decision of whether you write `ns` or
`namespace` - just use `nspace` and everything will be fine :)

---

### Ui Packages Consul Ui Docs Testing (`ui/packages/consul-ui/docs/testing.mdx`)

# Testing

## Running Tests

Our Ember application uses QUnit and QUnit's HTML runner in order to run tests.
As a human both of these should always be used to run tests. Currently you can
start the tests running by typing:

```bash
make test-oss-view
```

...in your terminal. This command will be changed to be the default `make test`
command at some point in the future.

As a human please always run the tests in a browser. You will sometimes spot
tiny details that are wrong with the application if you do this. We have other
`make` targets for running tests as a machine. Please don't use these machine
oriented targets as a human.

To re-run tests, simply refresh your browser or click the [Go] button in QUnit's
HTML test runner. If you then want to filter which tests to run you can use the
filter field in QUnit's HTML test runner to run a subset of tests (see below).
If you have started the tests with a filter, you don't have to quit everything
and start again in order to filter by something different.

## Browser support

Please check our current browser support when adding features or fixing bugs.

At the time of writing we use a rough ~2 years back support matrix for our
browser support. As most of our targetted browsers are 'evergreen' and
auto-update this feels like it strikes a balance between the folks that are
likely to be using the Consul UI and the desire to maintain a modern
codebase/feature set. This is a balance that should always be revisited and is
not set in stone. i.e. if you need to use something check-in with the team
first, it might be the time to bump again.

Please see [../config/targets.js](../config/targets.js) for our current support.

### Running a single or a range of tests

Please use the same `make test-oss-view` command to get the tests up and
running. Once you have opened your browser window to view the tests, click the
[Abort] button to stop the test running. Then use the QUnit filter input text
field to filter by the tests you want to run.

Alternatively you can also run a single test by bypassing `make` and using:

```bash
pnpm test:oss:view --filter=<test name substring>
```

Please note: If filtering using the CLI, normally you would expect a filter to
accept filenames. This is not the case here. When using Ember, tests are
filtered by test name not file name. You can get these test names by opening the
test file itself and finding the test you want run. You may need to escape
characters when doing that so be aware of that if you are copypasting.

Please be aware this that in bypassing `make` you will not have the project
setup for you automatically, certain housekeeping duties will not be performed
and you may skip some tests that are important for the thing (or things) you are
working on.

We also run a quick lint check for you (plus a few other things) that you should
fix up before you run your test.

If you do use pnpm --filter, please run these other checks before submitting a
PR. If you just use `make test-oss-view` you (and nobody else) needs to worry
about those things.

~~Additionally, there are ongoing discussions regarding moving to use npm and or
corepack) as a package manager, so if you are using yarn, we may break your
workflow.~~ We have moved to pnpm. If you are using `make`, don't worry as it stands right now nothing
will change.

If you specify your filter using the CLI and you make a typo or type your filter
wrong, or want to run a different filter. Don't quit the executing CLI command
and restart. That takes comparatively long time. You may aswell wait and then
amend the typo in QUnit's HTML test runner's filter input field.

Ember folks can also just use [`ember test`](https://cli.emberjs.com/release/basic-use/cli-commands/#testingyourapp)
and bypass both `pnpm` and `make` but all of the above regarding using `pnpm`
instead of `make` applies there also.

Please note: Currently the QUnit runner we depend on has an unfortunate UX
issue which means the filter input field is obscured by the frame used to run
your tests in. Until this is fixed in a version of QUnit we use, you can use
the Toggle QUnit Display bookmarklet to easily toggle the visibility of this window (you
should drag the below link to your browser bookmark bar, then clicking it
whilst on the test runner will hide the display):

| Link/Bookmarklet | Description |
| ---- | ----------- |
| [Toggle QUnit Display](javascript:(function()%7B(function()%7Bconst%20s%20%3D%20document.getElementById(%22ember-testing-container%22).style%3Bs.display%20%3D%20s.display%20%3D%3D%3D%20%22none%22%20%3F%20%22block%22%20%3A%20%22none%22%3B%7D)()%3B%7D)()%3B) | Toggle the QUnit display on or off during browser testing |

This also obscures test errors, messaging and the buttons to help you to re-run
a single test if you want to. So if you are doing a lot of testing for your
feature you might want to consider adding this to your bookmarks bar.

### Multiple 'spawning' test runners

Ember's testing support also has an unfortunate UX issue in that it can
sometimes spawn multiple test runners and once you have a few of these opened
for you things can get very confusing. To disable Ember from automatically
opening test runner windows for you, there is non-Ember environment variable we
added to tell it to stop doing this:

```bash
export TESTEM_AUTOLAUNCH=0
```

Suggestion: You could add this to your bash profile if you find it useful.

The above will prevent Ember's 'testem' implementation from automatically
opening windows for you. This of course means you have to navigate to the test
runner yourself, but that's sometimes a small wrinkle compared to having lots
and lots _and lots_ of test runner windows open.

### Testing different scenarios

Most tests are written to automatically switch between different scenarios
during the test so you don't usually have to worry about this, just run the
tests and you are done.

The exception is our namespace testing. We run our entire acceptance test
suite 4 or 5 times for different namespace settings. If you want to run some
acceptance tests with namespace scenarios enabled, this _could_ be run by
using:

```bash
make test-view
```

...but please don't do that, it takes a long time.

In CI these tests are run by a machine by using:

```bash
make test
```

...but again as a human, please don't do that, it takes a long time. PRs are
blocked for merge until these tests pass CI, i.e. the machine will run it for
you.

Of course there are times where you want to run these tests as a human. If you
want to run a specfic test _and_ include testing those tests whilst namespaces
are enabled then run the test suite as you usually would:

```bash
make test-view-oss
```

If you only want to run a filtered set of tests use the QUnit filter input
field to filter for the test you want run (for example maybe `dc / acls`)

Then use our development bookmarklet for turning on namespace support in the
UI:

| Link/Bookmarklet | Description |
| ---- | ----------- |
| [Enable Nspaces](javascript:Scenario('CONSUL_NSPACES_ENABLE=1')) | Enable Namespace Support |

When you click that it will run the filtered tests against all 'types' of
namespace scenarios.

Finally, for integration testing where we need to check for namespace support,
these tests are always run in our integration tests (these run a lot faster
than acceptance tests). You probably only want to use the above approach when
needing to test specific acceptance tests with namespace scenarios.

## Writing tests

Speaking generally, currently Consul favours unit tests (in `./tests/unit`)
and acceptance tests (in `./tests/acceptance`). We also have a fair amount of
integration tests (in `./tests/integration`) for testing that systems of
combined units work well together. What ember traditionally calls integration
tests, are mostly 'component' tests. We have far less of these as the vast
majority of our components are just 'glorified partials' and mostly contain
very simple template logic that is mostly (but not always) not very worthwhile
testing.

Overall, please don't test things we don't own for example don't spend time
testing that Ember's handlebars templating is printing out variables
correctly. Don't spend time testing that `ember-composable-helpers` `not`
helper is working correctly. The great thing about a declarative template
(basically a configuration file) is that it probably won't need much testing
in isolation.

### Acceptance tests

Our acceptance tests use a BDD style of testing. This means tests are
relatively simple to write, the writer does not need to think about
asynchronicity, and best of all they are very easy to read and understand what
is being tested, and therefore anyone can roughly understand what is being
tested/passing (or failing) tests.

We have roughly 20-30 reusable BDD steps you can use to write tests. We
occasionally add a new step but not very often. You should be able to write
the majority of your tests using the steps we already have. The steps
themselves are in `./tests/steps/{assertions,debug,double,interactions}` and
they are just simple javascript optionally asynchronous functions that are
specifically built to be __highly reusable__.

If you come to write a new step, please take some time to think about how
reusable it will be and try to word the title of the step taking that into
account. Look at some of the existing steps for examples.

We use the `yadda` npm package for our BDD support, just as the ember addon we
originally used for this used `yadda`. In the future we'd like to re-consider
and potentially use `cucumber.js`.

In order to create a new acceptance test/feature please use the ember
generator to do so:

```bash
ember generate feature dc/model/thing
```

This creates some additional JS that unfortunately is required by the ember
addon, so until we move away from that addon, this is the easiest way to get
started with a brand new acceptance test.

If you are not familiar with the steps available to write tests the best way
to write a new test is to look at the tests already written/and or scan
through the steps folders to see what's available. As mentioned above, there
are only around 30 steps.

We make some use of `ember-page-objects` to encapsulate some DOM
selecting/functionality. To be fair we kind of dip in and out of using
`ember-page-objects` and there is ongoing work to remove it entirely and
replace it with something simpler to work with. We also have
`ember-test-selectors` installed. So please use either a page object or a
test-selector for selecting/asserting DOM elements when writing tests. Prefer
page objects, but if you find it complicated/difficult or just plain smelly,
feel free to use a test-selector and use that directly in the test instead. At
some point we will reassess this and bring it all into line with one single
approach.

__Most importantly__: Please try to only use either a page-object or a
test-selector for DOM assertions/selections. Please try to avoid using any
complex selectors.

### Integration testing

We use `sinon` for making mocks for testing that systems work well together,
other than that please see Ember's own documentation for writing tests.

__Remember__: Integration testing does not mean 'component tests' it means
checking to make sure the units of a system work well together.

If you want to test a component in isolation whilst working on it, please create
an `.mdx` file in the component folder and use that to work on the component. We
use these `.mdx` files to isolate the component, work on it, manually/visually
test it and document it for others. If somebody needs to change the component in
future this means they already have test scenarios enabled/arranged for them to
use. Components should generally be small things, or made up of further small
components that are individually testable in isolation. There shouldn't be much
logic required in a new component. And if there is, and it's important enough to
need testing, you should consider splitting the logic off and testing it outside
of the component via integration testing and/or unit testing. This more manual
kind of component testing should be thought of as 'unit testing for a
component'. You are very unlikely to break something unless you change the code
of the actual component itself, and if you are doing this you are working with
the `mdx` file.and likely to notice. Whilst not perfect, this is where we are
right now.

We have very few single components that are an integrated system of further
components (where automated testing would be more necessary/useful) and our
application aims to stay that way for the foreseeable future. But if you write
something that may require more complicated and/or integrated testing consider
splitting those pieces into smaller more testable chunks that can be
automatically tested.

### Unit testing

Unit testing should be just that, _testing a single unit_, and therefore should
be as vanilla JS as possible. Our application generally favours using vanilla
JS where possible and then plumbing that functionality/logic into the Ember
framework. Therefore there should be very little mention of Ember in our Unit
tests (apart from Ember's test harness).

We do have coverage enabled for most of our unit tests, but it's not something
we generally use/keep an eye on. Coverage shouldn't necessarily be used as a
yard stick. We have it enabled for folks that find it useful/beneficial and
whilst good coverage is a positive it's not always an essential.

Coverage is run in CI but you can also run it locally with:

```bash
make test-coverage-view
```

We also have a fair amount of node based unit tests to test things that can't
be tested using Ember's testing approach or are just quicker to run using
node. You'll see these executed using TAP reporting when you run the `make
test-view-oss` command. But they can be executed on their own with:

```bash
make test-node
```

These node based unit tests live in `./node-tests` and use the beautifully
simple `tape` test runner with TAP reporting.

Lastly, just to reiterate when testing, just use:

```bash
make test-oss-view
```

If you need to filter to only run specific tests, use the QUnit HTML runner's
filter input field.

---

### Ui Packages Consul Ui Docs Upgrades (`ui/packages/consul-ui/docs/upgrades.mdx`)

# Upgrades

## Node

Node upgrades should be done when it is convienient, preferably using the
latest Active LTS version (https://nodejs.org/en/about/releases/) that also
corresponds with the ember version we are on (https://github.com/ember-cli/ember-cli/blob/master/docs/node-support.md)

Aswell as bumping our `.nvmrc` and `package.json` files, when bumping
node versions, please check with the rest of the team to see if there are
other repositories that should track the same node version as this repository.

## Ember

TBD

## Other

TBD

---

### Test Integration Connect Envoy Docs Windows Testing Architecture (`test/integration/connect/envoy/docs/windows-testing-architecture.md`)

# Windows Testing Architecture

## Index

- [About](#about)
- [Testing Architectures](#testing-architectures)
  - [Linux Test Architecture](#linux-test-architecture)
  - [Replicating the Linux Test Architecture on Windows](#replicating-the-linux-test-architecture-on-windows)
  - [Single Container Test Architecture](#single-container-test-architecture)
    - [Docker Image Components](#docker-image-components)
      - Main Components:
        - [Bats](#bats)
        - [Fortio](#fortio)
        - [Jaegertracing](#jaegertracing)
        - [Openzipkin](#openzipkin)
        - [Socat](#socat)
      - Additional tools:
        - [Git Bash](#git-bash)
        - [JQ](#jq)
        - [Netcat](#netcat)
        - [Openssl](#openssl)

## About

The purpose of this document is not only to explain why the testing architecture is different on Windows but also to describe how the Single Container test architecture is composed.

## Testing Architectures

### Linux Test Architecture

On Linux, tests take advantage of the Host network feature (only available for Linux containers). This means that every container within the network shares the host’s networking namespace. The network stack for every container that uses this network mode won’t be isolated from the Docker host and won’t get their own IP address.  



Every time a test is run, a directory called workdir is created, here all the required files to run the tests are copied. Then this same directory is mounted as a **named volume**, a container with a Kubernetes pause image tagged as *envoy_workdir_1* is run to keep the volume accessible as other containers start while running the tests. Linux containers allow file system operations on runtime unlike Windows containers.  

### Replicating the Linux Test Architecture on Windows

As we previously mentioned, on Windows there is no Host networking feature, so we went with NAT network instead. The main consequences of this is that now each container has their own networking stack (IP address) separated from each other, they can communicate among themselves using Docker's DNS feature (using the containers name) but no longer through localhost.  
Another problem we are facing while sticking to this architecture, is that configuration files assume that every service (services run by fortio and Envoy's sidecar proxy service) are running in localhost. Though we had some partial success on modifying those files on runtime still we are finding issues related to this.
Test's assertions are composed of either function calls or curl executions, we managed this by mapping those calls to the corresponding container name.



Above, the failing connections are depicted. We kept the same architecture as on Linux and worked around trying to solve those connectivity issues.  
Finally, after serveral tries, it was decided that instead of replicating the Linux architecture on Windows, it was more straightforward just to have a single container with all the required components to run the tests. This **single container** test architecture is the approach that works best on Windows.

## Single Container Test Architecture

As mentioned above, the single container approach, means building a Windows Docker image not only with Consul and Envoy, but also with all the tools required to execute the existing Envoy integration tests.  



Below you can find a list and a brief description of those components.  

### Docker Image Components

The Docker image used for the Consul - Envoy integration tests has several components needed to run those tests.

- Main Components:
  - [Bats](#bats)
  - [Fortio](#fortio)
  - [Jaegertracing](#jaegertracing)
  - [Openzipkin](#openzipkin)
  - [Socat](#socat)
- Additional tools:
  - [Git Bash](#git-bash)
  - [JQ](#jq)
  - [Netcat](#netcat)
  - [Openssl](#openssl)

#### Bats

BATS stands for Bash Automated Testing System and is the one in charge of executing the tests.

#### Fortio

Fortio is a microservices (http, grpc) load testing library, command line tool, advanced echo server, and web UI. It is used to run the services registered into Consul during the integration tests.

#### Jaegertracing

Jaeger is open source software for tracing transactions between distributed services. It's used for monitoring and troubleshooting complex microservices environments. It is used along with Openzipkin in some test cases.

#### Openzipkin

Zipkin is also a tracing software.

#### Socat

Socat is a command line based utility that establishes two bidirectional byte streams and transfers data between them. On this integration tests it is used to redirect Envoy's stats. There is no official Windows version. We are using this unofficial release available [here](https://github.com/tech128/socat-1.7.3.0-windows).

#### Git Bash

This tool is only used in Windows tests, it was added to the Docker image to be able to use some Linux commands during test execution.  

#### JQ

Jq is a lightweight and flexible command-line JSON processor. It is used in several tests to modify and filter JSON outputs.

#### Netcat

Netcat is a simple program that reads and writes data across networks, much the same way that cat reads and writes data to files.

#### Openssl

Open SSL is an all-around cryptography library that offers open-source application of the TLS protocol. It is used to verify that the correct tls certificates are being provisioned during tests.

---

--- METRICS ---
- Files Extracted: 36
- Estimated Token Budget: ~36991 tokens
- Canonical Reference: https://codewiki.google/github.com/hashicorp/consul
