Skip to content

Commit b4d52aa

Browse files
Merge pull request #1 from bytebase/a-branch-1
docs: update masking
2 parents 7da97f1 + 4e51083 commit b4d52aa

File tree

3 files changed

+201
-6
lines changed

3 files changed

+201
-6
lines changed

.github/workflows/bb-masking.yml

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
name: Bytebase Masking Policy Update
2+
on:
3+
pull_request:
4+
types: [closed]
5+
branches:
6+
- main
7+
workflow_dispatch:
8+
9+
jobs:
10+
bytebase-masking:
11+
if: github.event.pull_request.merged == true
12+
runs-on: ubuntu-latest
13+
permissions:
14+
pull-requests: write
15+
issues: write
16+
contents: read
17+
steps:
18+
- name: Checkout code
19+
uses: actions/checkout@v4
20+
21+
- name: Login Bytebase
22+
id: bytebase-login
23+
uses: bytebase/[email protected]
24+
with:
25+
bytebase-url: ${{ secrets.BYTEBASE_URL }}
26+
service-key: ${{ secrets.BYTEBASE_SERVICE_KEY }}
27+
service-secret: ${{ secrets.BYTEBASE_SERVICE_SECRET }}
28+
29+
- name: Get changed files
30+
id: changed-files
31+
uses: tj-actions/changed-files@v42
32+
with:
33+
files: |
34+
masking/databases/**/**/column-masking.json
35+
masking/projects/**/masking-exception.json
36+
37+
- name: Debug changed files in detail
38+
run: |
39+
echo "All changed files:"
40+
echo "${{ steps.changed-files.outputs.all_changed_files }}"
41+
echo "Contains column-masking.json: ${{ contains(steps.changed-files.outputs.all_changed_files, 'column-masking.json') }}"
42+
echo "Contains masking-exception.json: ${{ contains(steps.changed-files.outputs.all_changed_files, 'masking-exception.json') }}"
43+
echo "Raw output:"
44+
echo "${{ toJSON(steps.changed-files.outputs) }}"
45+
46+
- name: Apply column masking policy
47+
id: apply-column-masking
48+
if: ${{ steps.changed-files.outputs.any_changed == 'true' && contains(steps.changed-files.outputs.all_changed_files, '/column-masking.json') }}
49+
run: |
50+
# Process all column-masking.json files
51+
echo "${{ steps.changed-files.outputs.all_changed_files }}" | tr ' ' '\n' | grep "column-masking.json" | while read -r CHANGED_FILE; do
52+
echo "Processing: $CHANGED_FILE"
53+
INSTANCE_NAME=$(echo "$CHANGED_FILE" | sed -n 's/masking\/databases\/\([^/]*\)\/\([^/]*\).*/\1/p')
54+
DATABASE_NAME=$(echo "$CHANGED_FILE" | sed -n 's/masking\/databases\/\([^/]*\)\/\([^/]*\).*/\2/p')
55+
echo "INSTANCE_NAME=$INSTANCE_NAME"
56+
echo "DATABASE_NAME=$DATABASE_NAME"
57+
58+
response=$(curl -s -w "\n%{http_code}" --request PATCH "${{ steps.bytebase-login.outputs.api_url }}/instances/${INSTANCE_NAME}/databases/${DATABASE_NAME}/policies/masking?allow_missing=true&update_mask=payload" \
59+
--header "Authorization: Bearer ${{ steps.bytebase-login.outputs.token }}" \
60+
--header "Content-Type: application/json" \
61+
--data @"$CHANGED_FILE")
62+
63+
# Extract status code and response body
64+
status_code=$(echo "$response" | tail -n1)
65+
body=$(echo "$response" | sed '$d')
66+
67+
echo "Status code: $status_code"
68+
echo "Response body: $body"
69+
70+
# Append to outputs (with unique identifiers)
71+
echo "status_code_${DATABASE_NAME}=${status_code}" >> $GITHUB_OUTPUT
72+
echo "response_${DATABASE_NAME}<<EOF" >> $GITHUB_OUTPUT
73+
echo "${body}" >> $GITHUB_OUTPUT
74+
echo "EOF" >> $GITHUB_OUTPUT
75+
76+
if [[ $status_code -lt 200 || $status_code -ge 300 ]]; then
77+
echo "Failed with status code: $status_code for database: $DATABASE_NAME"
78+
exit 1
79+
fi
80+
done
81+
82+
- name: Apply masking exception policy
83+
id: apply-masking-exception
84+
if: ${{ steps.changed-files.outputs.any_changed == 'true' && contains(steps.changed-files.outputs.all_changed_files, '/masking-exception.json') }}
85+
run: |
86+
# Process all masking-exception.json files
87+
echo "${{ steps.changed-files.outputs.all_changed_files }}" | tr ' ' '\n' | grep "masking-exception.json" | while read -r CHANGED_FILE; do
88+
echo "Processing: $CHANGED_FILE"
89+
PROJECT_NAME=$(echo "$CHANGED_FILE" | sed -n 's/masking\/projects\/\([^/]*\).*/\1/p')
90+
echo "PROJECT_NAME=$PROJECT_NAME"
91+
92+
response=$(curl -s -w "\n%{http_code}" --request PATCH "${{ steps.bytebase-login.outputs.api_url }}/projects/${PROJECT_NAME}/policies/masking_exception?allow_missing=true&update_mask=payload" \
93+
--header "Authorization: Bearer ${{ steps.bytebase-login.outputs.token }}" \
94+
--header "Content-Type: application/json" \
95+
--data @"$CHANGED_FILE")
96+
97+
# Extract status code and response body
98+
status_code=$(echo "$response" | tail -n1)
99+
body=$(echo "$response" | sed '$d')
100+
101+
echo "Status code: $status_code"
102+
echo "Response body: $body"
103+
104+
# Append to outputs (with unique identifiers)
105+
echo "status_code_${PROJECT_NAME}=${status_code}" >> $GITHUB_OUTPUT
106+
echo "response_${PROJECT_NAME}<<EOF" >> $GITHUB_OUTPUT
107+
echo "${body}" >> $GITHUB_OUTPUT
108+
echo "EOF" >> $GITHUB_OUTPUT
109+
110+
if [[ $status_code -lt 200 || $status_code -ge 300 ]]; then
111+
echo "Failed with status code: $status_code for project: $PROJECT_NAME"
112+
exit 1
113+
fi
114+
done
115+
116+
- name: Comment on PR
117+
if: github.event_name == 'pull_request'
118+
uses: actions/github-script@v7
119+
env:
120+
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
121+
with:
122+
script: |
123+
const changedFiles = process.env.CHANGED_FILES || '';
124+
let commentBody = `### Masking Policy Update Summary\n\n`;
125+
126+
// Add changed files section
127+
commentBody += `📝 **Changed Files:**\n\n`;
128+
if (changedFiles.trim()) {
129+
commentBody += changedFiles.split(' ').map(f => `- ${f}`).join('\n');
130+
} else {
131+
commentBody += `None`;
132+
}
133+
commentBody += '\n\n';
134+
135+
// Add API calls summary
136+
commentBody += `🔄 **API Calls:**\n\n`;
137+
let apiCallsFound = false;
138+
139+
if (changedFiles.includes('column-masking.json')) {
140+
const maskingStatuses = Object.keys(${{ toJSON(steps.apply-column-masking.outputs) }} || {})
141+
.filter(key => key.startsWith('status_code_'))
142+
.map(key => ({
143+
name: key.replace('status_code_', ''),
144+
status: ${{ toJSON(steps.apply-column-masking.outputs) }}[key]
145+
}));
146+
147+
maskingStatuses.forEach(({name, status}) => {
148+
apiCallsFound = true;
149+
const success = status >= 200 && status < 300;
150+
commentBody += `- Column Masking (${name}): ${success ? '✅' : '❌'} ${status}\n`;
151+
});
152+
}
153+
154+
if (changedFiles.includes('masking-exception.json')) {
155+
const exceptionStatuses = Object.keys(${{ toJSON(steps.apply-masking-exception.outputs) }} || {})
156+
.filter(key => key.startsWith('status_code_'))
157+
.map(key => ({
158+
name: key.replace('status_code_', ''),
159+
status: ${{ toJSON(steps.apply-masking-exception.outputs) }}[key]
160+
}));
161+
162+
exceptionStatuses.forEach(({name, status}) => {
163+
apiCallsFound = true;
164+
const success = status >= 200 && status < 300;
165+
commentBody += `- Masking Exception (${name}): ${success ? '✅' : '❌'} ${status}\n`;
166+
});
167+
}
168+
169+
if (!apiCallsFound) {
170+
commentBody += `None`;
171+
}
172+
173+
await github.rest.issues.createComment({
174+
...context.repo,
175+
issue_number: context.issue.number,
176+
body: commentBody
177+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"inheritFromParent": false,
3+
"type": "MASKING",
4+
"maskingPolicy": {
5+
"maskData": [
6+
{
7+
"schema": "public",
8+
"table": "salary",
9+
"column": "amount",
10+
"maskingLevel": "FULL",
11+
"fullMaskingAlgorithmId": "",
12+
"partialMaskingAlgorithmId": ""
13+
}
14+
]
15+
},
16+
"enforce": true,
17+
"resourceType": "DATABASE"
18+
}

masking/projects/project-sample/masking-exception.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,29 @@
66
{
77
"action": "EXPORT",
88
"maskingLevel": "PARTIAL",
9-
"member": "user:dev1@example.com",
9+
"member": "user:dev@x.com",
1010
"condition": {
11-
"expression": "resource.database_name == \"hr_prod\" && resource.instance_id == \"prod-sample-instance\" && resource.schema_name == \"public\" && resource.table_name == \"salary\" && resource.column_name == \"amount\"",
11+
"expression": "resource.instance_id == \"prod-sample-instance\" && resource.database_name == \"hr_prod\" && resource.schema_name == \"public\" && resource.table_name == \"salary\" && resource.column_name == \"amount\"",
1212
"title": "",
1313
"description": ""
1414
}
1515
},
1616
{
1717
"action": "QUERY",
1818
"maskingLevel": "PARTIAL",
19-
"member": "user:dev2@example.com",
19+
"member": "user:dev2@x.com",
2020
"condition": {
21-
"expression": "resource.database_name == \"hr_prod\" && resource.instance_id == \"prod-sample-instance\" && resource.schema_name == \"public\" && resource.table_name == \"salary\" && resource.column_name == \"amount\"",
21+
"expression": "resource.instance_id == \"prod-sample-instance\" && resource.database_name == \"hr_prod\" && resource.schema_name == \"public\" && resource.table_name == \"salary\" && resource.column_name == \"amount\"",
2222
"title": "",
2323
"description": ""
2424
}
2525
},
2626
{
2727
"action": "QUERY",
2828
"maskingLevel": "NONE",
29-
"member": "group:contractor@example.com",
29+
"member": "group:contractor@x.com",
3030
"condition": {
31-
"expression": "resource.database_name == \"hr_prod\" && resource.instance_id == \"prod-sample-instance\" && resource.schema_name == \"public\" && resource.table_name == \"salary\" && resource.column_name == \"amount\"",
31+
"expression": "resource.instance_id == \"prod-sample-instance\" && resource.database_name == \"hr_prod\" && resource.schema_name == \"public\" && resource.table_name == \"salary\" && resource.column_name == \"amount\"",
3232
"title": "",
3333
"description": ""
3434
}

0 commit comments

Comments
 (0)