Merge pull request #129 from Hungerdream/feat/mysql-single-root-account
feat: 调整 MySQL 交付账号 (#108, #97)
This commit is contained in:
@@ -65,7 +65,6 @@
|
||||
|
||||
# --- secrets (prefer launch extra_vars; fall back to AWX credential-injected env) ---
|
||||
mysql_root_password_value: "{{ mysql_root_password | default(lookup('ansible.builtin.env', 'XINFRA_MYSQL_ROOT_PASSWORD'), true) }}"
|
||||
mysql_admin_password_value: "{{ mysql_admin_password | default(lookup('ansible.builtin.env', 'XINFRA_MYSQL_ADMIN_PASSWORD'), true) }}"
|
||||
|
||||
# --- platform callback ---
|
||||
delivery_callback_url_value: "{{ delivery_callback_url | default('') }}"
|
||||
@@ -110,7 +109,6 @@
|
||||
- (mysql_storage_gb_value | int) <= 2000
|
||||
- (mysql_lower_case_table_names | int) in [0, 1]
|
||||
- mysql_root_password_value | length >= 16
|
||||
- mysql_admin_password_value | length >= 16
|
||||
fail_msg: >-
|
||||
Delivery parameters out of the supported target-state whitelist
|
||||
(topology / version-package-map / port pool 13306-13999 / memory 2-64G / storage 20-2000G).
|
||||
@@ -453,9 +451,9 @@
|
||||
/usr/bin/mysql --protocol=socket --socket={{ mysql_run_dir }}/mysql.sock -uroot <"$sql_file"
|
||||
fi
|
||||
cat >"$sql_file" <<'EOF'
|
||||
CREATE USER IF NOT EXISTS 'xinfra_admin'@'%' IDENTIFIED BY '{{ mysql_admin_password_value | replace("'", "''") }}';
|
||||
ALTER USER 'xinfra_admin'@'%' IDENTIFIED BY '{{ mysql_admin_password_value | replace("'", "''") }}';
|
||||
GRANT ALL PRIVILEGES ON *.* TO 'xinfra_admin'@'%' WITH GRANT OPTION;
|
||||
CREATE USER IF NOT EXISTS 'root'@'%' IDENTIFIED BY '{{ mysql_root_password_value | replace("'", "''") }}';
|
||||
ALTER USER 'root'@'%' IDENTIFIED BY '{{ mysql_root_password_value | replace("'", "''") }}';
|
||||
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;
|
||||
FLUSH PRIVILEGES;
|
||||
EOF
|
||||
/usr/bin/mysql --defaults-extra-file="$client_file" <"$sql_file"
|
||||
@@ -554,4 +552,5 @@
|
||||
- name: Restart MySQL delivery instance
|
||||
ansible.builtin.systemd_service:
|
||||
name: "mysql-delivery@{{ mysql_instance }}.service"
|
||||
daemon_reload: true
|
||||
state: restarted
|
||||
|
||||
@@ -65,7 +65,6 @@
|
||||
|
||||
# --- secrets (prefer launch extra_vars; fall back to AWX credential-injected env) ---
|
||||
mysql_root_password_value: "{{ mysql_root_password | default(lookup('ansible.builtin.env', 'XINFRA_MYSQL_ROOT_PASSWORD'), true) }}"
|
||||
mysql_admin_password_value: "{{ mysql_admin_password | default(lookup('ansible.builtin.env', 'XINFRA_MYSQL_ADMIN_PASSWORD'), true) }}"
|
||||
|
||||
pre_tasks:
|
||||
- name: Validate delivery parameters
|
||||
@@ -86,7 +85,6 @@
|
||||
- (mysql_storage_gb_value | int) <= 2000
|
||||
- (mysql_lower_case_table_names | int) in [0, 1]
|
||||
- mysql_root_password_value | length >= 16
|
||||
- mysql_admin_password_value | length >= 16
|
||||
fail_msg: >-
|
||||
Delivery parameters out of the supported target-state whitelist
|
||||
(topology / version-package-map / port pool 13306-13999 / memory 2-64G / storage 20-2000G).
|
||||
@@ -356,9 +354,9 @@
|
||||
/usr/bin/mysql --protocol=socket --socket={{ mysql_run_dir }}/mysql.sock -uroot <"$sql_file"
|
||||
fi
|
||||
cat >"$sql_file" <<'EOF'
|
||||
CREATE USER IF NOT EXISTS 'xinfra_admin'@'%' IDENTIFIED BY '{{ mysql_admin_password_value | replace("'", "''") }}';
|
||||
ALTER USER 'xinfra_admin'@'%' IDENTIFIED BY '{{ mysql_admin_password_value | replace("'", "''") }}';
|
||||
GRANT ALL PRIVILEGES ON *.* TO 'xinfra_admin'@'%' WITH GRANT OPTION;
|
||||
CREATE USER IF NOT EXISTS 'root'@'%' IDENTIFIED BY '{{ mysql_root_password_value | replace("'", "''") }}';
|
||||
ALTER USER 'root'@'%' IDENTIFIED BY '{{ mysql_root_password_value | replace("'", "''") }}';
|
||||
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;
|
||||
FLUSH PRIVILEGES;
|
||||
EOF
|
||||
/usr/bin/mysql --defaults-extra-file="$client_file" <"$sql_file"
|
||||
@@ -385,4 +383,5 @@
|
||||
- name: Restart MySQL delivery instance
|
||||
ansible.builtin.systemd_service:
|
||||
name: "mysql-delivery@{{ mysql_instance }}.service"
|
||||
daemon_reload: true
|
||||
state: restarted
|
||||
|
||||
@@ -45,7 +45,6 @@ export interface CreateMySQLDeliveryPayload {
|
||||
binlog_expire_logs_seconds?: number
|
||||
max_binlog_size?: string
|
||||
mysql_root_password?: string
|
||||
mysql_admin_password?: string
|
||||
}
|
||||
|
||||
export interface DeliveryTask {
|
||||
|
||||
@@ -788,6 +788,7 @@ const taskStateLoading = ref(false)
|
||||
const credentialRevealed = ref(false)
|
||||
const credentialDismissed = ref(false)
|
||||
const credentialRevealing = ref(false)
|
||||
const credentialAvailable = ref<boolean>()
|
||||
const revealedCredentials = ref<DeploymentCredential[]>([])
|
||||
const deliveryHistory = ref<DeliveryHistoryItem[]>(loadDeliveryHistory())
|
||||
let workbenchReady = false
|
||||
@@ -817,7 +818,7 @@ const canManageRollback = computed(() => authStore.isAdmin && lastDeliveryStatus
|
||||
const canRetryCloudDM = computed(() => deliveryRegisterFailed.value && Boolean(deploymentId.value))
|
||||
|
||||
const deliveryForm = reactive({
|
||||
instanceName: `mysql-${currentName.value}-billing-02`,
|
||||
instanceName: generateInstanceName(activeServiceKey.value, currentName.value),
|
||||
version: 'MySQL 8.0',
|
||||
mode: 'single',
|
||||
spec: '1C / 2G',
|
||||
@@ -852,6 +853,13 @@ const collationMap: Record<string, string[]> = {
|
||||
}
|
||||
|
||||
const steps = ref<DeliveryStep[]>([])
|
||||
const deliveryStageStepIndex: Record<string, number> = {
|
||||
precheck: 0,
|
||||
install: 1,
|
||||
configure: 2,
|
||||
healthcheck: 3,
|
||||
register: 4,
|
||||
}
|
||||
|
||||
const activeService = computed(() => basicServices.value.find((service) => service.key === activeServiceKey.value && !service.disabled))
|
||||
const selectedTarget = computed(() => deliveryTargets.value.find((target) => target.id === selectedTargetId.value))
|
||||
@@ -912,7 +920,10 @@ const runnerPreview = computed(() => {
|
||||
].join('\n')
|
||||
})
|
||||
const connectionReady = computed(() => Boolean(deliveredHost.value && deliveredPort.value))
|
||||
const credentialEligible = computed(() => connectionReady.value && ['finished', 'register_failed'].includes(lastDeliveryStatus.value))
|
||||
const credentialEligible = computed(() => {
|
||||
if (!connectionReady.value || !['finished', 'register_failed'].includes(lastDeliveryStatus.value)) return false
|
||||
return credentialAvailable.value !== false
|
||||
})
|
||||
const revealedRootCredential = computed(() => revealedCredentials.value.find((item) => item.username === 'root'))
|
||||
const resultReady = computed(() => !taskStateLoading.value && !running.value && isTerminalDeliveryStatus(lastDeliveryStatus.value))
|
||||
const previewAddress = computed(() => `${selectedHost.value?.ip || 'AWX 自动分配'}:${deliveryForm.port || '自动端口'}`)
|
||||
@@ -964,7 +975,7 @@ const deliveryStateClass = computed(() => {
|
||||
})
|
||||
|
||||
watch(currentName, (name) => {
|
||||
deliveryForm.instanceName = `${activeServiceKey.value === 'mysql' ? 'mysql' : 'nginx'}-${name}-billing-02`
|
||||
deliveryForm.instanceName = generateInstanceName(activeServiceKey.value, name)
|
||||
})
|
||||
|
||||
watch(selectedTargetId, () => {
|
||||
@@ -1116,8 +1127,11 @@ async function restoreWorkbench() {
|
||||
|
||||
restoringWorkbench = true
|
||||
Object.assign(deliveryForm, snapshot.form)
|
||||
if (!snapshot.deploymentId) {
|
||||
deliveryForm.instanceName = generateInstanceName(activeServiceKey.value, currentName.value)
|
||||
}
|
||||
deliveryForm.rootPassword = generateRootPassword()
|
||||
precheckPassed.value = snapshot.precheckPassed
|
||||
precheckPassed.value = snapshot.deploymentId ? snapshot.precheckPassed : false
|
||||
running.value = snapshot.running
|
||||
deliveryDone.value = snapshot.deliveryDone
|
||||
deliveryFailed.value = snapshot.deliveryFailed
|
||||
@@ -1130,6 +1144,7 @@ async function restoreWorkbench() {
|
||||
steps.value = snapshot.steps?.length ? snapshot.steps : defaultSteps()
|
||||
credentialRevealed.value = false
|
||||
credentialDismissed.value = Boolean(snapshot.deploymentId)
|
||||
credentialAvailable.value = undefined
|
||||
|
||||
if (snapshot.deploymentId) {
|
||||
taskStateLoading.value = true
|
||||
@@ -1138,8 +1153,10 @@ async function restoreWorkbench() {
|
||||
deliveredHost.value = data.task.target_host_ip || deliveredHost.value
|
||||
deliveredPort.value = data.task.mysql_port || deliveredPort.value
|
||||
lastDeliveryStatus.value = data.task.status
|
||||
credentialAvailable.value = data.task.credential_available
|
||||
deliveryError.value = data.task.error_message || deliveryError.value
|
||||
seenEventIds.value = new Set(data.events.map((event) => event.id))
|
||||
seenEventIds.value = new Set()
|
||||
applyTaskEvents(data.events, false)
|
||||
applyDeliveryStatus(data.task.status, data.task.error_message || '')
|
||||
if (isTerminalDeliveryStatus(data.task.status)) {
|
||||
activeView.value = 'result'
|
||||
@@ -1167,8 +1184,7 @@ async function restoreWorkbench() {
|
||||
function hydrateServiceDefaults() {
|
||||
const service = activeService.value
|
||||
if (!service) return
|
||||
const prefix = service.key === 'mysql' ? 'mysql' : 'nginx'
|
||||
deliveryForm.instanceName = `${prefix}-${currentName.value}-billing-02`
|
||||
deliveryForm.instanceName = generateInstanceName(service.key, currentName.value)
|
||||
deliveryForm.version = service.versions[0] || ''
|
||||
deliveryForm.mode = service.modes[1]?.value || service.modes[0]?.value || 'single'
|
||||
deliveryForm.spec = service.key === 'mysql'
|
||||
@@ -1220,6 +1236,7 @@ function resetExecutionState() {
|
||||
deliveryFailed.value = false
|
||||
credentialRevealed.value = false
|
||||
credentialDismissed.value = false
|
||||
credentialAvailable.value = undefined
|
||||
revealedCredentials.value = []
|
||||
deliveryError.value = ''
|
||||
lastDeliveryStatus.value = ''
|
||||
@@ -1228,6 +1245,7 @@ function resetExecutionState() {
|
||||
deliveredHost.value = ''
|
||||
deliveredPort.value = undefined
|
||||
deliveryLog.value = '[ready] 等待创建交付任务...'
|
||||
seenEventIds.value = new Set()
|
||||
steps.value = defaultSteps().map((step) => ({ ...step, state: 'pending' }))
|
||||
}
|
||||
|
||||
@@ -1345,11 +1363,12 @@ async function createTask() {
|
||||
try {
|
||||
const task = await deliveryApi.createMySQL(mysqlDeliveryPayload(businessLineId))
|
||||
deploymentId.value = task.id
|
||||
credentialAvailable.value = task.credential_available
|
||||
deliveryForm.rootPassword = ''
|
||||
upsertDeliveryHistory(task.id, task.status, task.error_message || '', task.created_at)
|
||||
deliveryLog.value += `\n[task] ${task.id} created by ${currentName.value}`
|
||||
applyDeliveryStatus(task.status, '')
|
||||
startTaskPolling(task.id)
|
||||
startTaskPolling(task.id, true)
|
||||
persistWorkbench()
|
||||
ElMessage.success('交付任务已创建')
|
||||
} catch (error) {
|
||||
@@ -1402,9 +1421,8 @@ function mysqlDeliveryPayload(businessLineId: number) {
|
||||
long_query_time: deliveryForm.longQueryTime,
|
||||
binlog_expire_logs_seconds: deliveryForm.binlogExpireSeconds,
|
||||
max_binlog_size: deliveryForm.maxBinlogSize,
|
||||
// root 密码以表单为准;后端要求 root/admin 成对传入,平台管理账号密码随机生成
|
||||
// 交付只提供一个允许远程访问的 root 管理员账号。
|
||||
mysql_root_password: deliveryForm.rootPassword,
|
||||
mysql_admin_password: generateRootPassword(),
|
||||
}
|
||||
return payload
|
||||
}
|
||||
@@ -1423,9 +1441,9 @@ function hostLabel(host: TargetHostOption) {
|
||||
return host.ip ? `${host.name} · ${host.ip}` : host.name
|
||||
}
|
||||
|
||||
function startTaskPolling(id: string) {
|
||||
function startTaskPolling(id: string, resetEvents = false) {
|
||||
stopPolling()
|
||||
seenEventIds.value = new Set()
|
||||
if (resetEvents) seenEventIds.value = new Set()
|
||||
pollTask(id)
|
||||
pollTimer.value = window.setInterval(() => pollTask(id), 3000)
|
||||
}
|
||||
@@ -1440,6 +1458,7 @@ async function pollTask(id: string) {
|
||||
const data = await deliveryApi.getTask(id)
|
||||
deliveredHost.value = data.task.target_host_ip || deliveredHost.value
|
||||
deliveredPort.value = data.task.mysql_port || deliveredPort.value
|
||||
credentialAvailable.value = data.task.credential_available
|
||||
upsertDeliveryHistory(id, data.task.status, data.task.error_message || '', data.task.created_at)
|
||||
applyTaskEvents(data.events)
|
||||
applyDeliveryStatus(data.task.status, data.task.error_message || '')
|
||||
@@ -1453,14 +1472,30 @@ async function pollTask(id: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function applyTaskEvents(events: TaskEvent[]) {
|
||||
function applyTaskEvents(events: TaskEvent[], writeLogs = true) {
|
||||
events.forEach((event) => {
|
||||
if (seenEventIds.value.has(event.id)) return
|
||||
seenEventIds.value.add(event.id)
|
||||
appendLog(`[${event.to_state}] ${event.message}`)
|
||||
applyStageEvent(event)
|
||||
if (writeLogs) appendLog(`[${event.to_state}] ${event.message}`)
|
||||
})
|
||||
}
|
||||
|
||||
function applyStageEvent(event: TaskEvent) {
|
||||
const stage = String(event.stage || '').toLowerCase()
|
||||
const eventStatus = String(event.event_status || '').toLowerCase()
|
||||
const stepIndex = deliveryStageStepIndex[stage]
|
||||
if (stepIndex === undefined) return
|
||||
const stateMap: Record<string, StepState> = {
|
||||
running: 'running',
|
||||
success: 'done',
|
||||
failed: 'failed',
|
||||
}
|
||||
const state = stateMap[eventStatus]
|
||||
if (!state) return
|
||||
setDeliveryStep(stepIndex, state)
|
||||
}
|
||||
|
||||
function appendLog(message: unknown) {
|
||||
if (!message) return
|
||||
deliveryLog.value += `\n${String(message)}`
|
||||
@@ -1479,23 +1514,41 @@ async function copyText(value: string, message: string) {
|
||||
function applyDeliveryStatus(status: string, message: string) {
|
||||
lastDeliveryStatus.value = status
|
||||
if (message) appendLog(message)
|
||||
if (['pending', 'validating', 'dispatching', 'running', 'registering', 'canceling', 'rollback_pending', 'rolling_back'].includes(status)) {
|
||||
if (['pending', 'validating', 'dispatching', 'running'].includes(status)) {
|
||||
running.value = true
|
||||
markStepRunning()
|
||||
if (!steps.value.slice(0, 5).some((step) => step.state !== 'pending')) {
|
||||
setDeliveryStep(0, 'running')
|
||||
}
|
||||
return
|
||||
}
|
||||
if (status === 'registering') {
|
||||
running.value = true
|
||||
setDeliveryStep(4, 'running')
|
||||
return
|
||||
}
|
||||
if (status === 'canceling') {
|
||||
running.value = true
|
||||
return
|
||||
}
|
||||
if (['rollback_pending', 'rolling_back'].includes(status)) {
|
||||
running.value = true
|
||||
setDeliveryStep(5, 'running')
|
||||
return
|
||||
}
|
||||
if (status === 'finished') {
|
||||
running.value = false
|
||||
deliveryDone.value = true
|
||||
deliveryFailed.value = false
|
||||
steps.value = steps.value.map((step) => ({ ...step, state: 'done' }))
|
||||
steps.value = steps.value.map((step, index) => (
|
||||
index < 5 ? { ...step, state: 'done' } : step
|
||||
))
|
||||
return
|
||||
}
|
||||
if (status === 'rolled_back') {
|
||||
running.value = false
|
||||
deliveryDone.value = false
|
||||
deliveryFailed.value = true
|
||||
steps.value = steps.value.map((step) => ({ ...step, state: 'done' }))
|
||||
setDeliveryStep(5, 'done')
|
||||
return
|
||||
}
|
||||
if (['execution_failed', 'validation_failed', 'register_failed', 'rollback_failed', 'rollback_acknowledged', 'canceled'].includes(status)) {
|
||||
@@ -1503,7 +1556,11 @@ function applyDeliveryStatus(status: string, message: string) {
|
||||
deliveryDone.value = false
|
||||
deliveryFailed.value = true
|
||||
deliveryError.value = message || deliveryError.value
|
||||
markCurrentStepFailed()
|
||||
if (status === 'validation_failed') setDeliveryStep(0, 'failed')
|
||||
else if (status === 'register_failed') setDeliveryStep(4, 'failed')
|
||||
else if (status === 'rollback_failed') setDeliveryStep(5, 'failed')
|
||||
else if (status === 'rollback_acknowledged') setDeliveryStep(5, 'done')
|
||||
else markCurrentStepFailed()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1511,7 +1568,8 @@ async function loadDeliveryTargets() {
|
||||
targetsLoading.value = true
|
||||
try {
|
||||
deliveryTargets.value = await deliveryApi.listTargets()
|
||||
const preferred = deliveryTargets.value.find((target) => !/callback|rollback/i.test(target.name))
|
||||
const preferred = deliveryTargets.value.find((target) => /callback/i.test(target.name))
|
||||
|| deliveryTargets.value.find((target) => !/rollback/i.test(target.name))
|
||||
selectedTargetId.value = preferred?.id || deliveryTargets.value[0]?.id
|
||||
} catch (error) {
|
||||
ElMessage.error(error instanceof Error ? error.message : '获取部署目标失败')
|
||||
@@ -1533,6 +1591,7 @@ function setupCredentialPreview() {
|
||||
deliveredPort.value = 13306
|
||||
credentialRevealed.value = false
|
||||
credentialDismissed.value = false
|
||||
credentialAvailable.value = true
|
||||
revealedCredentials.value = []
|
||||
upsertDeliveryHistory(deploymentId.value, 'finished', '', new Date().toISOString())
|
||||
if (deliveryHistory.value.length < 3) {
|
||||
@@ -1614,6 +1673,17 @@ function normalizeDNSLabel(value: string) {
|
||||
return normalized || 'default'
|
||||
}
|
||||
|
||||
function generateInstanceName(serviceKey: string, businessLineName: string) {
|
||||
const prefix = serviceKey === 'mysql' ? 'mysql' : 'nginx'
|
||||
// 限长业务线段,确保 63 字符截断不会吃掉随机后缀(prefix 5 + 连字符 2 + 后缀 6 = 13)
|
||||
const businessLine = normalizeDNSLabel(businessLineName).slice(0, 50).replace(/-+$/g, '')
|
||||
const alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789'
|
||||
const values = new Uint32Array(6)
|
||||
crypto.getRandomValues(values)
|
||||
const suffix = Array.from(values, (value) => alphabet[value % alphabet.length]).join('')
|
||||
return normalizeDNSLabel(`${prefix}-${businessLine}-${suffix}`)
|
||||
}
|
||||
|
||||
function generateRootPassword() {
|
||||
const groups = [
|
||||
'ABCDEFGHJKLMNPQRSTUVWXYZ',
|
||||
@@ -1657,13 +1727,7 @@ function regenerateRootPassword() {
|
||||
}
|
||||
|
||||
function regenerateInstanceName() {
|
||||
const prefix = activeServiceKey.value === 'mysql' ? 'mysql' : 'service'
|
||||
const businessLine = normalizeDNSLabel(currentName.value)
|
||||
const alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789'
|
||||
const values = new Uint32Array(6)
|
||||
crypto.getRandomValues(values)
|
||||
const suffix = Array.from(values, (value) => alphabet[value % alphabet.length]).join('')
|
||||
deliveryForm.instanceName = normalizeDNSLabel(`${prefix}-${businessLine}-${suffix}`)
|
||||
deliveryForm.instanceName = generateInstanceName(activeServiceKey.value, currentName.value)
|
||||
precheckPassed.value = false
|
||||
ElMessage.success('已生成新的实例名称,请执行预检查确认可用')
|
||||
}
|
||||
@@ -1679,7 +1743,7 @@ async function revealCredential() {
|
||||
host: deliveredHost.value || '10.24.18.21',
|
||||
port: deliveredPort.value || 13306,
|
||||
username: 'root',
|
||||
account_host: 'localhost',
|
||||
account_host: '%',
|
||||
password: deliveryForm.rootPassword,
|
||||
}]
|
||||
} else {
|
||||
@@ -1925,12 +1989,11 @@ async function releaseRollback() {
|
||||
}
|
||||
}
|
||||
|
||||
function markStepRunning() {
|
||||
const index = steps.value.findIndex((step) => step.state === 'pending' || step.state === 'running')
|
||||
if (index < 0) return
|
||||
function setDeliveryStep(index: number, state: StepState) {
|
||||
if (index < 0 || index >= steps.value.length) return
|
||||
steps.value = steps.value.map((step, stepIndex) => {
|
||||
if (stepIndex < index) return { ...step, state: 'done' }
|
||||
if (stepIndex === index) return { ...step, state: 'running' }
|
||||
if (stepIndex < index && step.state !== 'failed') return { ...step, state: 'done' }
|
||||
if (stepIndex === index) return { ...step, state }
|
||||
return step
|
||||
})
|
||||
}
|
||||
|
||||
@@ -65,7 +65,6 @@ type MySQLDeliveryInput struct {
|
||||
BinlogExpireLogsSeconds int64 `json:"binlog_expire_logs_seconds"`
|
||||
MaxBinlogSize string `json:"max_binlog_size"`
|
||||
MySQLRootPassword string `json:"mysql_root_password"`
|
||||
MySQLAdminPassword string `json:"mysql_admin_password"`
|
||||
}
|
||||
|
||||
type deliveryPayload struct {
|
||||
@@ -575,23 +574,18 @@ func (s *DeliveryService) CreateTask(ctx context.Context, userID uint64, isAdmin
|
||||
return nil, false, err
|
||||
}
|
||||
credentialInput := map[string]string{
|
||||
"root@localhost": strings.TrimSpace(input.MySQLRootPassword),
|
||||
"xinfra_admin@%": strings.TrimSpace(input.MySQLAdminPassword),
|
||||
"root@%": strings.TrimSpace(input.MySQLRootPassword),
|
||||
}
|
||||
hasCredentialInput := credentialInput["root@localhost"] != "" || credentialInput["xinfra_admin@%"] != ""
|
||||
hasCredentialInput := credentialInput["root@%"] != ""
|
||||
if hasCredentialInput {
|
||||
if credentialInput["root@localhost"] == "" || credentialInput["xinfra_admin@%"] == "" {
|
||||
return nil, false, fmt.Errorf("mysql_root_password and mysql_admin_password must be provided together")
|
||||
if len(credentialInput["root@%"]) < 16 {
|
||||
return nil, false, fmt.Errorf("mysql root password must be at least 16 characters")
|
||||
}
|
||||
if len(credentialInput["root@localhost"]) < 16 || len(credentialInput["xinfra_admin@%"]) < 16 {
|
||||
return nil, false, fmt.Errorf("mysql passwords must be at least 16 characters")
|
||||
}
|
||||
if !mysqlPasswordPattern.MatchString(credentialInput["root@localhost"]) || !mysqlPasswordPattern.MatchString(credentialInput["xinfra_admin@%"]) {
|
||||
if !mysqlPasswordPattern.MatchString(credentialInput["root@%"]) {
|
||||
return nil, false, fmt.Errorf("mysql passwords may only contain letters and digits")
|
||||
}
|
||||
}
|
||||
input.MySQLRootPassword = ""
|
||||
input.MySQLAdminPassword = ""
|
||||
|
||||
var existing model.DeliveryTask
|
||||
if err := s.db.WithContext(ctx).Where("idempotency_key = ?", idempotencyKey).First(&existing).Error; err == nil {
|
||||
@@ -990,14 +984,12 @@ func (s *DeliveryService) deploymentCredentialVars(ctx context.Context, taskID s
|
||||
return nil, err
|
||||
}
|
||||
switch item.Username + "@" + item.AccountHost {
|
||||
case "root@localhost":
|
||||
case "root@%", "root@localhost":
|
||||
values["mysql_root_password"] = password
|
||||
case "xinfra_admin@%":
|
||||
values["mysql_admin_password"] = password
|
||||
}
|
||||
}
|
||||
if values["mysql_root_password"] == "" || values["mysql_admin_password"] == "" {
|
||||
return nil, fmt.Errorf("deployment credentials are missing for task %s", taskID)
|
||||
if values["mysql_root_password"] == "" {
|
||||
return nil, fmt.Errorf("root deployment credential is missing for task %s", taskID)
|
||||
}
|
||||
return values, nil
|
||||
}
|
||||
@@ -2054,7 +2046,7 @@ func (s *DeliveryService) RegisterCloudDM(ctx context.Context, taskID string) er
|
||||
}
|
||||
var credential model.DeploymentCredential
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("task_id = ? AND username = ? AND account_host = ? AND status IN ?", taskID, "root", "localhost", []string{"pending", "available"}).
|
||||
Where("task_id = ? AND username = ? AND account_host IN ? AND status IN ?", taskID, "root", []string{"%", "localhost"}, []string{"pending", "available"}).
|
||||
First(&credential).Error; err != nil {
|
||||
return fmt.Errorf("CloudDM registration requires the MySQL root credential: %w", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user