Skip to content

Commit d1cfa9a

Browse files
feat: Proposal 2 - YAML anchors (native YAML feature)
- Add shared.yml with YAML anchors for shared configuration - Add environment configs (prod, rc, exp) using anchor references - Add load-yaml-config.js script to resolve cross-file anchors - Add build.yml workflow that uses YAML anchor-based configs - Add README explaining YAML anchor approach and limitations This proposal uses native YAML anchors and merge keys (<<) to share configuration across environments. Anchors are defined in shared.yml and referenced in environment configs using *anchorName syntax. Note: Standard YAML doesn't support cross-file anchors, so a custom loader script is needed to resolve anchors from shared.yml into environment configs. Co-authored-by: tomas.santos <[email protected]>
1 parent 7bb07c9 commit d1cfa9a

File tree

7 files changed

+529
-0
lines changed

7 files changed

+529
-0
lines changed

.github/workflows/build.yml

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
name: Build Mobile App
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
environment:
7+
required: true
8+
type: string
9+
platform:
10+
required: true
11+
type: choice
12+
options: [android, ios, both]
13+
build_type:
14+
required: true
15+
type: choice
16+
options: [main, flask]
17+
default: main
18+
workflow_dispatch:
19+
inputs:
20+
environment:
21+
required: true
22+
type: choice
23+
options: [prod, rc, exp]
24+
platform:
25+
required: true
26+
type: choice
27+
options: [android, ios, both]
28+
build_type:
29+
required: true
30+
type: choice
31+
options: [main, flask]
32+
default: main
33+
34+
jobs:
35+
load-environment-config:
36+
runs-on: ubuntu-latest
37+
outputs:
38+
config: ${{ steps.load-config.outputs.config }}
39+
requires_approval: ${{ steps.load-config.outputs.requires_approval }}
40+
github_environment: ${{ steps.load-config.outputs.github_environment }}
41+
secrets_mapping: ${{ steps.load-config.outputs.secrets_mapping }}
42+
steps:
43+
- uses: actions/checkout@v4
44+
45+
- name: Setup Node.js
46+
uses: actions/setup-node@v4
47+
with:
48+
node-version: '20'
49+
50+
- name: Install dependencies
51+
run: yarn install --immutable
52+
53+
- name: Load environment configuration (YAML anchors)
54+
id: load-config
55+
uses: actions/github-script@v7
56+
with:
57+
script: |
58+
const { loadConfigWithAnchors } = require('./scripts/load-yaml-config.js');
59+
const env = '${{ inputs.environment }}';
60+
const config = loadConfigWithAnchors(env);
61+
62+
// Extract secrets mapping
63+
const secretsMapping = JSON.stringify(config.secrets || {});
64+
65+
return {
66+
config: JSON.stringify(config),
67+
requires_approval: config.environment.requires_approval,
68+
github_environment: config.environment.github_environment,
69+
secrets_mapping: secretsMapping
70+
};
71+
72+
approval:
73+
if: needs.load-environment-config.outputs.requires_approval == 'true'
74+
needs: load-environment-config
75+
runs-on: ubuntu-latest
76+
environment: ${{ needs.load-environment-config.outputs.github_environment }}
77+
steps:
78+
- name: Wait for approval
79+
run: echo "Approval required for ${{ inputs.environment }} builds"
80+
81+
build:
82+
needs:
83+
- load-environment-config
84+
- approval
85+
if: always() && (needs.approval.result == 'success' || needs.approval.result == 'skipped')
86+
strategy:
87+
matrix:
88+
platform: ${{ inputs.platform == 'both' && fromJSON('["android", "ios"]') || fromJSON(format('["{0}"]', inputs.platform)) }}
89+
runs-on: ${{ matrix.platform == 'ios' && 'macos-latest' || 'ubuntu-latest' }}
90+
environment: ${{ needs.load-environment-config.outputs.github_environment }}
91+
steps:
92+
- uses: actions/checkout@v4
93+
94+
- name: Setup Node.js
95+
uses: actions/setup-node@v4
96+
with:
97+
node-version: '20'
98+
cache: 'yarn'
99+
100+
- name: Install dependencies
101+
run: yarn install --immutable
102+
103+
- name: Setup project
104+
run: yarn setup:github-ci
105+
106+
- name: Apply environment configuration
107+
id: apply-config
108+
uses: actions/github-script@v7
109+
with:
110+
script: |
111+
const { loadConfigWithAnchors } = require('./scripts/load-yaml-config.js');
112+
const env = '${{ inputs.environment }}';
113+
const config = loadConfigWithAnchors(env);
114+
115+
// Set non-secret environment variables from merged config
116+
const outputs = {};
117+
118+
// Server URLs
119+
Object.entries(config.servers).forEach(([key, value]) => {
120+
const envVar = key.toUpperCase();
121+
outputs[envVar] = value;
122+
core.exportVariable(envVar, value);
123+
});
124+
125+
// Feature flags
126+
Object.entries(config.features).forEach(([key, value]) => {
127+
const envVar = key.toUpperCase();
128+
outputs[envVar] = value.toString();
129+
core.exportVariable(envVar, value.toString());
130+
});
131+
132+
// Build configuration
133+
core.exportVariable('METAMASK_ENVIRONMENT', config.environment.name);
134+
core.exportVariable('METAMASK_BUILD_TYPE', '${{ inputs.build_type }}');
135+
136+
// Output for other steps
137+
Object.entries(outputs).forEach(([key, value]) => {
138+
core.setOutput(key.toLowerCase().replace(/_/g, '_'), value);
139+
});
140+
141+
- name: Set secrets as environment variables
142+
env:
143+
CONFIG_SECRETS: ${{ needs.load-environment-config.outputs.secrets_mapping }}
144+
run: |
145+
# Note: GitHub Actions doesn't allow dynamic secret access
146+
# Secrets must be configured in the GitHub environment and accessed directly
147+
echo "Secrets mapping loaded. Secrets should be configured in GitHub environment settings."
148+
echo "$CONFIG_SECRETS" | jq -r 'to_entries[] | "export \(.key)=\"${{ secrets.\(.value) }}\""' || true
149+
150+
- name: Build ${{ matrix.platform }}
151+
run: |
152+
./scripts/build.sh ${{ matrix.platform }} ${{ inputs.build_type }} ${{ inputs.environment }}

build/environments/README.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Environment Configuration Management (YAML Anchors)
2+
3+
This directory contains YAML configuration files using native YAML anchors feature.
4+
5+
## Structure
6+
7+
- `shared.yml` - Defines YAML anchors for shared configuration
8+
- `prod/config.yml` - Production configuration using anchors
9+
- `rc/config.yml` - Release Candidate configuration using anchors
10+
- `exp/config.yml` - Experimental configuration using anchors
11+
12+
## How It Works
13+
14+
1. **Shared Anchors**: `shared.yml` defines anchors (using `x-` prefix) for:
15+
- `x-servers``*servers`
16+
- `x-features``*features`
17+
- `x-build-base``*build-base`
18+
- `x-common-secrets``*common-secrets`
19+
20+
2. **Environment Configs**: Each environment config references anchors using `*anchorName` syntax
21+
3. **Merge Keys**: Use `<<: *anchorName` to merge anchor content into objects
22+
4. **Loading**: The `scripts/load-yaml-config.js` script loads and resolves anchors
23+
24+
## Usage
25+
26+
### Load configuration
27+
28+
```bash
29+
node scripts/load-yaml-config.js <environment>
30+
```
31+
32+
Example:
33+
```bash
34+
node scripts/load-yaml-config.js prod
35+
```
36+
37+
This outputs the resolved JSON configuration with all anchors expanded.
38+
39+
### In GitHub Actions
40+
41+
The workflow would use `load-yaml-config.js` to load the configuration, similar to Proposal 1.
42+
43+
## YAML Anchor Syntax
44+
45+
### Defining Anchors (in shared.yml)
46+
```yaml
47+
x-servers: &servers
48+
portfolio_api: "https://..."
49+
auth_service: "https://..."
50+
```
51+
52+
### Using Anchors (in config.yml)
53+
```yaml
54+
# Direct reference
55+
servers: *servers
56+
57+
# Merge into object
58+
secrets:
59+
<<: *common-secrets
60+
# Additional secrets here
61+
```
62+
63+
## Benefits
64+
65+
- **Native YAML**: Uses standard YAML features (anchors and merge keys)
66+
- **No Custom Scripts**: Leverages YAML parser's built-in anchor support
67+
- **DRY**: Shared values defined once as anchors
68+
- **Clear**: Anchor references make dependencies explicit
69+
70+
## Limitations
71+
72+
- **Cross-file Anchors**: Standard YAML doesn't support anchors across files
73+
- Solution: `load-yaml-config.js` loads both files and resolves anchors
74+
- **Complexity**: Anchor resolution can be complex for deeply nested structures
75+
- **Tool Support**: Some YAML tools may not fully support merge keys (`<<`)
76+
77+
## Comparison with Proposal 1
78+
79+
| Feature | Proposal 1 (Base Config) | Proposal 2 (Anchors) |
80+
|---------|-------------------------|----------------------|
81+
| Standard YAML | ✅ Yes | ⚠️ Requires cross-file support |
82+
| Custom Script | ✅ Simple merge | ⚠️ Anchor resolution needed |
83+
| Clarity | ✅ Very clear | ⚠️ Can be complex |
84+
| Maintenance | ✅ Easy | ⚠️ Moderate |

build/environments/exp/config.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Experimental configuration using YAML anchors
2+
# References anchors from shared.yml
3+
4+
# Use anchors from shared.yml
5+
servers: *servers
6+
features: *features
7+
8+
environment:
9+
name: exp
10+
display_name: Experimental
11+
requires_approval: false
12+
approval_team: null
13+
github_environment: build-exp
14+
15+
# Extend build-base anchor with Exp-specific overrides
16+
build:
17+
<<: *build-base
18+
android:
19+
<<: *build-base.android
20+
keystore: "mainExp"
21+
signing_config: "mainExp"
22+
23+
# Merge common secrets with Exp-specific secrets
24+
secrets:
25+
<<: *common-secrets
26+
# Exp-specific overrides
27+
SEGMENT_WRITE_KEY: "SEGMENT_WRITE_KEY_QA"
28+
SEGMENT_PROXY_URL: "SEGMENT_PROXY_URL_QA"
29+
SEGMENT_DELETE_API_SOURCE_ID: "SEGMENT_DELETE_API_SOURCE_ID_QA"
30+
SEGMENT_REGULATIONS_ENDPOINT: "SEGMENT_REGULATIONS_ENDPOINT_QA"
31+
MM_SENTRY_DSN: "MM_SENTRY_DSN_TEST"
32+
IOS_GOOGLE_CLIENT_ID: "MAIN_IOS_GOOGLE_CLIENT_ID_UAT"
33+
IOS_GOOGLE_REDIRECT_URI: "MAIN_IOS_GOOGLE_REDIRECT_URI_UAT"
34+
ANDROID_APPLE_CLIENT_ID: "MAIN_ANDROID_APPLE_CLIENT_ID_UAT"
35+
ANDROID_GOOGLE_CLIENT_ID: "MAIN_ANDROID_GOOGLE_CLIENT_ID_UAT"
36+
ANDROID_GOOGLE_SERVER_CLIENT_ID: "MAIN_ANDROID_GOOGLE_SERVER_CLIENT_ID_UAT"
37+
WEB3AUTH_NETWORK: "MAIN_WEB3AUTH_NETWORK_PROD"
38+
MM_CARD_BAANX_API_CLIENT_KEY: "MM_CARD_BAANX_API_CLIENT_KEY_UAT"

build/environments/prod/config.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Production configuration using YAML anchors
2+
# References anchors from shared.yml
3+
4+
# Use anchors from shared.yml
5+
servers: *servers
6+
features: *features
7+
8+
environment:
9+
name: production
10+
display_name: Production
11+
requires_approval: true
12+
approval_team: mobile-platform
13+
github_environment: build-production
14+
15+
# Extend build-base anchor with production-specific overrides
16+
build:
17+
<<: *build-base
18+
android:
19+
<<: *build-base.android
20+
keystore: "mainProd"
21+
signing_config: "mainProd"
22+
package_name: "io.metamask" # Explicit override
23+
24+
# Merge common secrets with production-specific secrets
25+
secrets:
26+
<<: *common-secrets
27+
# Production-specific overrides
28+
SEGMENT_WRITE_KEY: "SEGMENT_WRITE_KEY"
29+
SEGMENT_PROXY_URL: "SEGMENT_PROXY_URL"
30+
SEGMENT_DELETE_API_SOURCE_ID: "SEGMENT_DELETE_API_SOURCE_ID"
31+
SEGMENT_REGULATIONS_ENDPOINT: "SEGMENT_REGULATIONS_ENDPOINT"
32+
MM_SENTRY_DSN: "MM_SENTRY_DSN"
33+
IOS_GOOGLE_CLIENT_ID: "IOS_GOOGLE_CLIENT_ID"
34+
IOS_GOOGLE_REDIRECT_URI: "IOS_GOOGLE_REDIRECT_URI"
35+
ANDROID_APPLE_CLIENT_ID: "ANDROID_APPLE_CLIENT_ID"
36+
ANDROID_GOOGLE_CLIENT_ID: "ANDROID_GOOGLE_CLIENT_ID"
37+
ANDROID_GOOGLE_SERVER_CLIENT_ID: "ANDROID_GOOGLE_SERVER_CLIENT_ID"
38+
MM_BRANCH_KEY_LIVE: "MM_BRANCH_KEY_LIVE"
39+
MM_CARD_BAANX_API_CLIENT_KEY: "MM_CARD_BAANX_API_CLIENT_KEY"

build/environments/rc/config.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Release Candidate configuration using YAML anchors
2+
# References anchors from shared.yml
3+
4+
# Use anchors from shared.yml
5+
servers: *servers
6+
features: *features
7+
8+
environment:
9+
name: rc
10+
display_name: Release Candidate
11+
requires_approval: false
12+
approval_team: null
13+
github_environment: build-rc
14+
15+
# Extend build-base anchor with RC-specific overrides
16+
build:
17+
<<: *build-base
18+
android:
19+
<<: *build-base.android
20+
keystore: "mainRc"
21+
signing_config: "mainRc"
22+
23+
# Merge common secrets with RC-specific secrets
24+
secrets:
25+
<<: *common-secrets
26+
# RC-specific overrides
27+
SEGMENT_WRITE_KEY: "SEGMENT_WRITE_KEY_QA"
28+
SEGMENT_PROXY_URL: "SEGMENT_PROXY_URL_QA"
29+
SEGMENT_DELETE_API_SOURCE_ID: "SEGMENT_DELETE_API_SOURCE_ID_QA"
30+
SEGMENT_REGULATIONS_ENDPOINT: "SEGMENT_REGULATIONS_ENDPOINT_QA"
31+
MM_SENTRY_DSN: "MM_SENTRY_DSN_TEST"
32+
IOS_GOOGLE_CLIENT_ID: "MAIN_IOS_GOOGLE_CLIENT_ID_PROD"
33+
IOS_GOOGLE_REDIRECT_URI: "MAIN_IOS_GOOGLE_REDIRECT_URI_PROD"
34+
ANDROID_APPLE_CLIENT_ID: "MAIN_ANDROID_APPLE_CLIENT_ID_PROD"
35+
ANDROID_GOOGLE_CLIENT_ID: "MAIN_ANDROID_GOOGLE_CLIENT_ID_PROD"
36+
ANDROID_GOOGLE_SERVER_CLIENT_ID: "MAIN_ANDROID_GOOGLE_SERVER_CLIENT_ID_PROD"
37+
MM_CARD_BAANX_API_CLIENT_KEY: "MM_CARD_BAANX_API_CLIENT_KEY_PROD"

0 commit comments

Comments
 (0)