Skip to content

Commit b85f8e0

Browse files
authored
feat(cli): impl policy cmd (#756)
* feat(cli): impl policy cmd * fix: update name * fix: update name
1 parent 95b0c6e commit b85f8e0

File tree

8 files changed

+252
-0
lines changed

8 files changed

+252
-0
lines changed

cli/src/action/application/index.ts

+7
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ export async function init(appid: string, options: { sync: boolean }) {
4747
// init function
4848
initFunction()
4949

50+
// init policy
51+
initPolicy()
52+
5053
// init secret
5154
refreshSecretConfig()
5255

@@ -87,4 +90,8 @@ function initFunction() {
8790
const fromTsConfigFile = path.resolve(templateDir, TSCONFIG_FILE)
8891
const outTsConfigFile = path.resolve(process.cwd(), TSCONFIG_FILE)
8992
fs.writeFileSync(outTsConfigFile, fs.readFileSync(fromTsConfigFile, 'utf-8'))
93+
}
94+
95+
function initPolicy() {
96+
ensureDirectory(path.join(process.cwd(), 'policies'))
9097
}

cli/src/action/policy/dto.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
interface PolicyRule {
2+
collectionName: string
3+
rules: Rules
4+
}
5+
6+
interface Rules {
7+
read: boolean,
8+
count: boolean,
9+
update: boolean,
10+
remove: boolean,
11+
add: boolean
12+
}

cli/src/action/policy/index.ts

+173
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import { readApplicationConfig } from "../../config/application";
2+
import * as Table from 'cli-table3'
3+
import { policyControllerCreate, policyControllerFindAll, policyControllerRemove, policyRuleControllerCreate, policyRuleControllerFindAll, policyRuleControllerRemove, policyRuleControllerUpdate } from "../../api/v1/database";
4+
import { formatDate } from "../../util/format";
5+
import * as path from "node:path";
6+
import * as fs from "node:fs";
7+
import { POLICIES_DIRECTORY_NAME } from "../../common/constant";
8+
import { writeYamlFile, loadYamlFile } from "../../util/file";
9+
import { CreatePolicyDto, CreatePolicyRuleDto, UpdatePolicyRuleDto } from "../../api/v1/data-contracts";
10+
import { getEmoji } from "../../util/print";
11+
import { getApplicationPath } from "../../util/sys";
12+
import { confirm } from "../../common/prompts";
13+
14+
export async function list() {
15+
const appConfig = readApplicationConfig()
16+
const policies = await policyControllerFindAll(appConfig.appid)
17+
const table = new Table({
18+
head: ['name', 'ruleCount', 'createdAt'],
19+
})
20+
for (let item of policies) {
21+
table.push([item.name, item.rules?.length, formatDate(item.createdAt)])
22+
}
23+
console.log(table.toString());
24+
}
25+
26+
export async function pullOne(policyName: string) {
27+
await pull(policyName)
28+
console.log(`${getEmoji('✅')} pull policy ${policyName} success`)
29+
}
30+
31+
export async function pullAll() {
32+
const appConfig = readApplicationConfig()
33+
const policies = await policyControllerFindAll(appConfig.appid)
34+
35+
for (let item of policies) {
36+
await pull(item.name)
37+
}
38+
39+
console.log(`${getEmoji('✅')} pull all policies success`)
40+
}
41+
42+
43+
44+
async function pull(policyName: string) {
45+
const appConfig = readApplicationConfig()
46+
const rules= await policyRuleControllerFindAll(appConfig.appid, policyName)
47+
const rulePath = path.join(process.cwd(), POLICIES_DIRECTORY_NAME, policyName + '.yaml')
48+
const ruleList: PolicyRule[] = []
49+
for (let item of rules) {
50+
ruleList.push({
51+
collectionName: item.collectionName,
52+
rules: {
53+
read: item.value.read,
54+
count: item.value.count,
55+
update: item.value.update,
56+
remove: item.value.remove,
57+
add: item.value.add
58+
}
59+
})
60+
}
61+
writeYamlFile(rulePath, ruleList)
62+
}
63+
64+
65+
66+
export async function pushOne(policyName: string) {
67+
const appConfig = readApplicationConfig()
68+
const policies = await policyControllerFindAll(appConfig.appid)
69+
let isCreate = true
70+
for (let item of policies) {
71+
if (item.name === policyName) {
72+
isCreate = false
73+
break
74+
}
75+
}
76+
await push(policyName, isCreate)
77+
console.log(`${getEmoji('✅')} push policy ${policyName} success`)
78+
}
79+
80+
export async function pushAll(options: { force: boolean}) {
81+
const appConfig = readApplicationConfig()
82+
83+
// get server policies
84+
const serverPolicies = await policyControllerFindAll(appConfig.appid)
85+
const serverPoliciesMap = new Map<string, boolean>()
86+
for (let item of serverPolicies) {
87+
serverPoliciesMap.set(item.name, true)
88+
}
89+
90+
// get local policies
91+
const localPolicies = getLocalPolicies()
92+
const localPoliciesMap = new Map<string, boolean>()
93+
for (let item of localPolicies) {
94+
localPoliciesMap.set(item, true)
95+
}
96+
97+
// push local policies
98+
for (let item of localPolicies) {
99+
await push(item, !serverPoliciesMap.has(item))
100+
}
101+
102+
// delete server policies
103+
for (let item of serverPolicies) {
104+
if (!localPoliciesMap.has(item.name)) {
105+
if (options.force) {
106+
await policyControllerRemove(appConfig.appid, item.name)
107+
} else {
108+
const res = await confirm('confirm remove policy ' + item.name + '?')
109+
if (res.value) {
110+
await policyControllerRemove(appConfig.appid, item.name)
111+
} else {
112+
console.log(`${getEmoji('🎃')} cancel remove policy ${item.name}`)
113+
}
114+
}
115+
}
116+
}
117+
console.log(`${getEmoji('✅')} push all policies success`)
118+
}
119+
120+
async function push(policyName: string, isCreate: boolean){
121+
const appConfig = readApplicationConfig()
122+
if (isCreate) {
123+
const createPolicyDto: CreatePolicyDto = {
124+
name: policyName
125+
}
126+
await policyControllerCreate(appConfig.appid, createPolicyDto)
127+
}
128+
const serverRules = await policyRuleControllerFindAll(appConfig.appid, policyName)
129+
const serverRulesMap = new Map<string, boolean>()
130+
for (let item of serverRules) {
131+
serverRulesMap.set(item.collectionName, true)
132+
}
133+
const rulePath = path.join(process.cwd(), POLICIES_DIRECTORY_NAME, policyName + '.yaml')
134+
const localRules: PolicyRule[] = loadYamlFile(rulePath)
135+
const localRulesMap = new Map<string, boolean>()
136+
137+
// update or create rule
138+
for (let item of localRules) {
139+
if (serverRulesMap.has(item.collectionName)) { // rule exist, update
140+
const updateRuleDto: UpdatePolicyRuleDto = {
141+
value: JSON.stringify(item.rules)
142+
}
143+
await policyRuleControllerUpdate(appConfig.appid, policyName, item.collectionName, updateRuleDto)
144+
} else { // rule not exist, create
145+
const createRuleDto: CreatePolicyRuleDto = {
146+
collectionName: item.collectionName,
147+
value: JSON.stringify(item.rules)
148+
}
149+
await policyRuleControllerCreate(appConfig.appid, policyName, createRuleDto)
150+
}
151+
localRulesMap.set(item.collectionName, true)
152+
}
153+
154+
// delete rule
155+
for (let item of serverRules) {
156+
if (!localRulesMap.has(item.collectionName)) {
157+
await policyRuleControllerRemove(appConfig.appid, policyName, item.collectionName)
158+
}
159+
}
160+
}
161+
162+
163+
function getLocalPolicies(): string[] {
164+
const dir = path.join(getApplicationPath(), POLICIES_DIRECTORY_NAME)
165+
const files = fs.readdirSync(dir)
166+
const policies: string[] = []
167+
for (let item of files) {
168+
if (item.endsWith('.yaml')) {
169+
policies.push(item.replace('.yaml', ''))
170+
}
171+
}
172+
return policies
173+
}

cli/src/command/policy/index.ts

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { Command, program } from "commander"
2+
import { list, pullOne, pushOne, pullAll, pushAll } from "../../action/policy"
3+
import { checkApplication } from "../../common/hook"
4+
5+
export function policyCommand(): Command {
6+
const cmd = program.command('policy')
7+
.hook('preAction', () => {
8+
checkApplication()
9+
})
10+
11+
cmd.command('list')
12+
.description('policy list')
13+
.action(() => {
14+
list()
15+
})
16+
17+
cmd.command('pull [policyName]')
18+
.description('pull police from server')
19+
.action((policyName) => {
20+
if (policyName) {
21+
pullOne(policyName)
22+
} else {
23+
pullAll()
24+
}
25+
})
26+
27+
cmd.command('push [policyName]')
28+
.description('push police to server')
29+
.option('-f, --force', 'force to overwrite the server', false)
30+
.action((policyName, options) => {
31+
if (policyName) {
32+
pushOne(policyName)
33+
} else {
34+
pushAll(options)
35+
}
36+
})
37+
38+
39+
40+
41+
return cmd
42+
}

cli/src/common/constant.ts

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export const FUNCTIONS_DIRECTORY_NAME = 'functions'
77
export const FUNCTIONS_CONFIG_FILE_SUFFIX_NAME = '.meta.yaml'
88
export const COLLECTIONS_DIRECTORY_NAME = 'collections'
99
export const COLLECTIONS_CONFIG_FILE_SUFFIX_NAME = '.meta.yaml'
10+
export const POLICIES_DIRECTORY_NAME = 'policies'
1011

1112
// token expire time 7 days
1213
export const TOKEN_EXPIRE = 3600 * 24 * 7

cli/src/common/prompts.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import * as prompts from 'prompts';
2+
3+
export async function confirm(message: string) {
4+
return await prompts({
5+
type: 'confirm',
6+
name: 'value',
7+
message: message,
8+
initial: false
9+
})
10+
}

cli/src/main.ts

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { command as dependencyCommand } from './command/dependency/'
77

88
import { loginCommand, logoutCommand } from './command/auth'
99
import { bucketCommand } from './command/storage'
10+
import { policyCommand } from './command/policy'
1011

1112

1213
const program = new Command()
@@ -27,6 +28,7 @@ program.addCommand(applicationCommand())
2728
program.addCommand(functionCommand())
2829
program.addCommand(bucketCommand())
2930
program.addCommand(dependencyCommand())
31+
program.addCommand(policyCommand())
3032

3133
program.parse(process.argv)
3234

cli/src/util/sys.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
3+
export function getApplicationPath(): string {
4+
return process.cwd()
5+
}

0 commit comments

Comments
 (0)