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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions cmd/run/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,65 @@ var _ = Describe("Main", func() {
Entry("When the FABRIC_K8S_BUILDER_START_TIMEOUT is missing a duration unit", "3", `run \[\d+\]: The FABRIC_K8S_BUILDER_START_TIMEOUT environment variable must be a valid Go duration string, e\.g\. 3m40s: time: missing unit in duration "3"`),
Entry("When the FABRIC_K8S_BUILDER_START_TIMEOUT is not a valid duration string", "three minutes", `run \[\d+\]: The FABRIC_K8S_BUILDER_START_TIMEOUT environment variable must be a valid Go duration string, e\.g\. 3m40s: time: invalid duration "three minutes"`),
)
DescribeTable("Running the run command produces the correct error for invalid FABRIC_K8S_BUILDER_NAME_SERVERS environment variable values",
func(nameServersValue, expectedErrorMessage string) {
args := []string{"BUILD_OUTPUT_DIR", "RUN_METADATA_DIR"}
command := exec.Command(runCmdPath, args...)
command.Env = append(os.Environ(),
"CORE_PEER_ID=core-peer-id-abcdefghijklmnopqrstuvwxyz-0123456789",
"FABRIC_K8S_BUILDER_NAME_SERVERS="+nameServersValue,
)
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
Expect(err).NotTo(HaveOccurred())

Eventually(session).Should(gexec.Exit(1))
Eventually(
session.Err,
).Should(gbytes.Say(expectedErrorMessage))
},
Entry("When the FABRIC_K8S_BUILDER_NAME_SERVERS is not a valid IP address", "invalid-ip", `run \[\d+\]: The FABRIC_K8S_BUILDER_NAME_SERVERS environment variable must be a valid IP address`),
Entry("When the FABRIC_K8S_BUILDER_NAME_SERVERS is an incomplete IP", "192.168.1", `run \[\d+\]: The FABRIC_K8S_BUILDER_NAME_SERVERS environment variable must be a valid IP address`),
Entry("When the FABRIC_K8S_BUILDER_NAME_SERVERS contains invalid characters", "10.96.0.10.extra", `run \[\d+\]: The FABRIC_K8S_BUILDER_NAME_SERVERS environment variable must be a valid IP address`),
)
DescribeTable("Running the run command produces the correct error for invalid FABRIC_K8S_BUILDER_CUSTOM_ANNOTATIONS environment variable values",
func(customAnnotationsValue, expectedErrorMessage string) {
args := []string{"BUILD_OUTPUT_DIR", "RUN_METADATA_DIR"}
command := exec.Command(runCmdPath, args...)
command.Env = append(os.Environ(),
"CORE_PEER_ID=core-peer-id-abcdefghijklmnopqrstuvwxyz-0123456789",
"FABRIC_K8S_BUILDER_CUSTOM_ANNOTATIONS="+customAnnotationsValue,
)
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
Expect(err).NotTo(HaveOccurred())

Eventually(session).Should(gexec.Exit(1))
Eventually(
session.Err,
).Should(gbytes.Say(expectedErrorMessage))
},
Entry("When the FABRIC_K8S_BUILDER_CUSTOM_ANNOTATIONS contains invalid annotation key", "invalid*key=value", `run \[\d+\]: The FABRIC_K8S_BUILDER_CUSTOM_ANNOTATIONS environment variable contains an invalid annotation key 'invalid\*key': must be a valid Kubernetes annotation key`),
Entry("When the FABRIC_K8S_BUILDER_CUSTOM_ANNOTATIONS contains key starting with dash", "-key=value", `run \[\d+\]: The FABRIC_K8S_BUILDER_CUSTOM_ANNOTATIONS environment variable contains an invalid annotation key '-key': must be a valid Kubernetes annotation key`),
)

DescribeTable("Running the run command produces the correct error for invalid FABRIC_K8S_BUILDER_HOST_ALIASES environment variable values",
func(hostAliasesValue, expectedErrorMessage string) {
args := []string{"BUILD_OUTPUT_DIR", "RUN_METADATA_DIR"}
command := exec.Command(runCmdPath, args...)
command.Env = append(os.Environ(),
"CORE_PEER_ID=core-peer-id-abcdefghijklmnopqrstuvwxyz-0123456789",
"FABRIC_K8S_BUILDER_HOST_ALIASES="+hostAliasesValue,
)
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
Expect(err).NotTo(HaveOccurred())

Eventually(session).Should(gexec.Exit(1))
Eventually(
session.Err,
).Should(gbytes.Say(expectedErrorMessage))
},
Entry("When the FABRIC_K8S_BUILDER_HOST_ALIASES is not valid JSON", `[{"ip":"1.2.3.4"`, `run \[\d+\]: The FABRIC_K8S_BUILDER_HOST_ALIASES environment variable must be a valid JSON array`),
Entry("When the FABRIC_K8S_BUILDER_HOST_ALIASES contains invalid IP", `[{"ip":"invalid","hostnames":["foo.com"]}]`, `run \[\d+\]: The FABRIC_K8S_BUILDER_HOST_ALIASES environment variable contains an invalid IP address 'invalid' at index 0`),
Entry("When the FABRIC_K8S_BUILDER_HOST_ALIASES contains empty IP", `[{"ip":"","hostnames":["foo.com"]}]`, `run \[\d+\]: The FABRIC_K8S_BUILDER_HOST_ALIASES environment variable contains a host alias at index 0 with an empty IP address`),
Entry("When the FABRIC_K8S_BUILDER_HOST_ALIASES contains no hostnames", `[{"ip":"1.2.3.4","hostnames":[]}]`, `run \[\d+\]: The FABRIC_K8S_BUILDER_HOST_ALIASES environment variable contains a host alias at index 0 with no hostnames`),
)
})
213 changes: 213 additions & 0 deletions docs/configuring/configmap-env-vars.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
# ConfigMap Environment Variables

The Fabric K8s Builder supports injecting environment variables into chaincode pods from Kubernetes ConfigMaps. This allows you to configure chaincode behavior without rebuilding container images.

## Overview

Environment variables are automatically loaded from a ConfigMap if it exists with the same name as the chaincode label. No additional configuration is required on the peer.

## How It Works

When deploying chaincode:
1. The builder extracts the chaincode label from the package ID
2. It checks if a ConfigMap exists with that exact name
3. If found, all key-value pairs from the ConfigMap are mounted as environment variables
4. If not found, the chaincode pod starts normally without the additional environment variables

## Configuration

### 1. Package Your Chaincode

Package your chaincode with a label:

```bash
peer lifecycle chaincode package mycc.tar.gz \
--path ./chaincode \
--lang k8s \
--label mycc_v1
```

The label `mycc_v1` will be used as the ConfigMap name.

### 2. Create the ConfigMap

Create a ConfigMap with the **exact same name** as your chaincode label:

```bash
kubectl create configmap mycc_v1 \
--from-literal=LOG_LEVEL=debug \
--from-literal=DATABASE_URL=postgres://db:5432/mydb \
--from-literal=API_KEY=secret123 \
-n hyperledger
```

Or using a YAML file:

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: mycc_v1
namespace: hyperledger
data:
LOG_LEVEL: "debug"
DATABASE_URL: "postgres://db:5432/mydb"
API_KEY: "secret123"
FEATURE_FLAG_X: "enabled"
```

Apply it:

```bash
kubectl apply -f mycc-configmap.yaml
```

### 3. Deploy Your Chaincode

Install and deploy your chaincode normally:

```bash
peer lifecycle chaincode install mycc.tar.gz
peer lifecycle chaincode approveformyorg --channelID mychannel --name mycc --version 1.0 --package-id mycc_v1:hash...
peer lifecycle chaincode commit --channelID mychannel --name mycc --version 1.0
```

The chaincode pod will automatically have access to all environment variables from the ConfigMap.

## Naming Convention

**Important:** The ConfigMap name must **exactly match** the chaincode label.

### Examples

| Chaincode Label | ConfigMap Name | Status |
|----------------|----------------|--------|
| `mycc_v1` | `mycc_v1` | ✅ Valid |
| `asset-transfer` | `asset-transfer` | ✅ Valid |
| `basic_1.0` | `basic_1.0` | ✅ Valid |
| `mycc_v1` | `mycc-v1-config` | ❌ Invalid - doesn't match |
| `mycc_v1` | `mycc` | ❌ Invalid - doesn't match |

## Environment Variable Priority

Environment variables are loaded in this order (later values override earlier ones):

1. **Fabric Core Variables** - Built-in variables like `CORE_CHAINCODE_ID_NAME`, `CORE_PEER_ADDRESS`, etc.
2. **ConfigMap Variables** - Variables from the ConfigMap matching the chaincode label

## Complete Example

### Step 1: Create ConfigMap First

```bash
kubectl create configmap asset_transfer_v1 \
--from-literal=LOG_LEVEL=info \
--from-literal=MAX_CONNECTIONS=100 \
--from-literal=CACHE_TTL=3600 \
-n hyperledger
```

### Step 2: Package Chaincode

```bash
peer lifecycle chaincode package asset-transfer.tar.gz \
--path ./asset-transfer-chaincode \
--lang k8s \
--label asset_transfer_v1
```

### Step 3: Install and Deploy

```bash
# Install
peer lifecycle chaincode install asset-transfer.tar.gz

# Get package ID
peer lifecycle chaincode queryinstalled

# Approve (replace PACKAGE_ID with actual value)
peer lifecycle chaincode approveformyorg \
--channelID mychannel \
--name asset-transfer \
--version 1.0 \
--package-id asset_transfer_v1:abc123...

# Commit
peer lifecycle chaincode commit \
--channelID mychannel \
--name asset-transfer \
--version 1.0
```

The chaincode pod will now have `LOG_LEVEL`, `MAX_CONNECTIONS`, and `CACHE_TTL` environment variables available.

## Updating Configuration

To update environment variables without redeploying chaincode:

### Option 1: Edit ConfigMap Directly

```bash
kubectl edit configmap mycc_v1 -n hyperledger
```

### Option 2: Apply Updated YAML

```bash
kubectl apply -f mycc-configmap.yaml
```

### Restart Chaincode Pod

After updating the ConfigMap, restart the chaincode pod to pick up changes:

```bash
# Find the pod
kubectl get pods -l fabric-builder-k8s-cclabel=mycc_v1 -n hyperledger

# Delete it (peer will recreate it automatically)
kubectl delete pod <pod-name> -n hyperledger
```

Or delete by label:

```bash
kubectl delete pod -l fabric-builder-k8s-cclabel=mycc_v1 -n hyperledger
```

## Multiple Versions

You can have different configurations for different versions of the same chaincode:

```bash
# Version 1
kubectl create configmap mycc_v1 \
--from-literal=FEATURE_X=disabled \
-n hyperledger

# Version 2
kubectl create configmap mycc_v2 \
--from-literal=FEATURE_X=enabled \
-n hyperledger
```

Each version will use its own ConfigMap based on the label.


### Wrong ConfigMap Name

Ensure the ConfigMap name exactly matches the chaincode label. Check your package label:

```bash
peer lifecycle chaincode queryinstalled
```

The label is shown in the package ID: `label:hash`

## Best Practices

1. **Create ConfigMap Before Deployment**: Create the ConfigMap before installing the chaincode to ensure it's available immediately
2. **Use Descriptive Labels**: Use clear, version-specific labels like `mycc_v1`, `mycc_v2` instead of generic names
3. **Document Variables**: Add comments in your ConfigMap YAML to document what each variable does
4. **Version Control**: Store ConfigMap YAML files in version control alongside your chaincode
5. **Environment-Specific ConfigMaps**: Use different ConfigMaps for dev, staging, and production environments
75 changes: 66 additions & 9 deletions docs/configuring/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ externalBuilders:
- FABRIC_K8S_BUILDER_OBJECT_NAME_PREFIX
- FABRIC_K8S_BUILDER_SERVICE_ACCOUNT
- FABRIC_K8S_BUILDER_START_TIMEOUT
- FABRIC_K8S_BUILDER_NAME_SERVERS
- FABRIC_K8S_BUILDER_CUSTOM_ANNOTATIONS
- FABRIC_K8S_BUILDER_HOST_ALIASES
- KUBERNETES_SERVICE_HOST
- KUBERNETES_SERVICE_PORT
```
Expand All @@ -41,14 +44,68 @@ For more information, see [Configuring external builders and launchers](https://

The k8s builder is configured using the following environment variables.

| Name | Default | Description |
| ------------------------------------- | -------------------------------- | ---------------------------------------------------- |
| CORE_PEER_ID | | The Fabric peer ID (required) |
| FABRIC_K8S_BUILDER_NAMESPACE | The peer namespace or `default` | The Kubernetes namespace to run chaincode with |
| FABRIC_K8S_BUILDER_NODE_ROLE | | Use dedicated Kubernetes nodes to run chaincode |
| FABRIC_K8S_BUILDER_OBJECT_NAME_PREFIX | `hlfcc` | Eye-catcher prefix for Kubernetes object names |
| FABRIC_K8S_BUILDER_SERVICE_ACCOUNT | `default` | The Kubernetes service account to run chaincode with |
| FABRIC_K8S_BUILDER_START_TIMEOUT | `3m` | The timeout when waiting for chaincode pods to start |
| FABRIC_K8S_BUILDER_DEBUG | `false` | Set to `true` to enable k8s builder debug messages |
| Name | Default | Description |
| ---------------------------------------- | -------------------------------- | -------------------------------------------------------------------------------- |
| CORE_PEER_ID | | The Fabric peer ID (required) |
| FABRIC_K8S_BUILDER_NAMESPACE | The peer namespace or `default` | The Kubernetes namespace to run chaincode with |
| FABRIC_K8S_BUILDER_NODE_ROLE | | Use dedicated Kubernetes nodes to run chaincode |
| FABRIC_K8S_BUILDER_OBJECT_NAME_PREFIX | `hlfcc` | Eye-catcher prefix for Kubernetes object names |
| FABRIC_K8S_BUILDER_SERVICE_ACCOUNT | `default` | The Kubernetes service account to run chaincode with |
| FABRIC_K8S_BUILDER_START_TIMEOUT | `3m` | The timeout when waiting for chaincode pods to start |
| FABRIC_K8S_BUILDER_NAME_SERVERS | | Custom DNS nameserver IP for chaincode pods (optional, enables custom DNS) |
| FABRIC_K8S_BUILDER_CUSTOM_ANNOTATIONS | | Custom annotations for chaincode pods (optional, comma-separated key=value pairs)|
| FABRIC_K8S_BUILDER_HOST_ALIASES | | Host aliases for chaincode pods (optional, JSON array format) |
| FABRIC_K8S_BUILDER_DEBUG | `false` | Set to `true` to enable k8s builder debug messages |

The k8s builder can be run in cluster using the `KUBERNETES_SERVICE_HOST` and `KUBERNETES_SERVICE_PORT` environment variables, or it can connect using a `KUBECONFIG_PATH` environment variable.


## Host Aliases Configuration

The `FABRIC_K8S_BUILDER_HOST_ALIASES` environment variable allows you to add custom host-to-IP mappings in chaincode pods. This is useful when chaincode needs to resolve custom hostnames that are not available in DNS.

### Format

The value must be a valid JSON array of host alias objects. Each object contains:
- `ip`: The IP address (string)
- `hostnames`: Array of hostnames that should resolve to this IP (array of strings)

### Example

```bash
export FABRIC_K8S_BUILDER_HOST_ALIASES='[{"ip":"1.2.3.4","hostnames":["foo.com","bar.com"]},{"ip":"5.6.7.8","hostnames":["example.org"]}]'
```

This configuration will add the following entries to the chaincode pod's `/etc/hosts` file:
```
1.2.3.4 foo.com bar.com
5.6.7.8 example.org
```

### Use Cases

- **Private Services**: Resolve internal service names to private IP addresses
- **Testing**: Override DNS resolution for testing purposes
- **Legacy Systems**: Connect to systems with non-standard hostname requirements
- **Service Mesh**: Custom routing for service mesh configurations

### Validation

The builder will validate the JSON format when creating chaincode pods. Invalid JSON will cause the chaincode deployment to fail with an error message indicating the parsing issue.

### Example Configurations

**Single host alias:**
```bash
export FABRIC_K8S_BUILDER_HOST_ALIASES='[{"ip":"192.168.1.100","hostnames":["database.local"]}]'
```

**Multiple hosts to same IP:**
```bash
export FABRIC_K8S_BUILDER_HOST_ALIASES='[{"ip":"10.0.0.50","hostnames":["api.internal","api.local","api"]}]'
```

**Multiple IP mappings:**
```bash
export FABRIC_K8S_BUILDER_HOST_ALIASES='[{"ip":"10.0.1.10","hostnames":["db1.local"]},{"ip":"10.0.1.11","hostnames":["db2.local"]},{"ip":"10.0.1.12","hostnames":["cache.local"]}]'
```
Loading