zioinfo-mail/scripts/setup/cicd_full_setup.py
DESKTOP-TKLFCPR\ython 28d3ba4836 refactor(cleanup): commit folder reorganization - scripts/, _archive/, docs/ restructure
- Move backend/frontend/messenger/ old paths to _archive/
- Reorganize scripts into scripts/deploy, check, push, setup, misc
- Move docs (pptx, docx) to docs/
- Add .claude agents and skills for fullstack/folder-cleanup harness
- workspace/ projects remain intact

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 19:43:09 +09:00

345 lines
13 KiB
Python

"""CI/CD 전체 구축: 플러그인 → Credential → Jenkinsfile → Job 생성"""
import paramiko, sys, time, json
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect('101.79.17.164', username='root', password='1q2w3e!Q', timeout=30)
sftp = client.open_sftp()
JENKINS = 'http://127.0.0.1:9080'
AUTH = 'admin:Admin@2026!'
def run(label, cmd, timeout=120):
print(f'\n[{label}]')
_, o, e = client.exec_command(cmd, timeout=timeout)
out = o.read().decode('utf-8', errors='replace').strip()
if out: print(out[:400])
return out
def jenkins_script(label, script, timeout=30):
"""Jenkins Script Console 실행"""
print(f'\n[Script: {label}]')
# crumb 가져오기
_, o, _ = client.exec_command(
f'curl -sf "{JENKINS}/crumbIssuer/api/json" -u {AUTH} 2>/dev/null', timeout=10)
crumb_data = o.read().decode('utf-8', errors='replace').strip()
try:
crumb = json.loads(crumb_data)['crumb']
crumb_field = json.loads(crumb_data)['crumbRequestField']
except:
crumb = ''
crumb_field = 'Jenkins-Crumb'
escaped = script.replace("'", "'\\''")
cmd = f"""curl -sf -X POST "{JENKINS}/scriptText" -u {AUTH} \
-H "{crumb_field}: {crumb}" \
--data-urlencode 'script={escaped}' 2>/dev/null"""
_, o, _ = client.exec_command(cmd, timeout=timeout)
out = o.read().decode('utf-8', errors='replace').strip()
if out: print(f' {out[:200]}')
return out
# ── 1. 플러그인 설치 ──────────────────────────────────────────────────────────
run('필수 플러그인 설치', f"""
java -jar /tmp/jenkins-cli.jar -s {JENKINS} -auth {AUTH} install-plugin \
git workflow-aggregator pipeline-stage-view credentials-binding \
ssh-agent nodejs timestamper ansicolor http_request junit \
2>&1 | tail -3
echo "플러그인 설치 요청"
""")
# ── 2. Credential 등록 ────────────────────────────────────────────────────────
jenkins_script('Gitea Credential 등록', """
import jenkins.model.*
import com.cloudbees.plugins.credentials.*
import com.cloudbees.plugins.credentials.domains.*
import com.cloudbees.plugins.credentials.impl.*
def store = Jenkins.instance.getExtensionList(
"com.cloudbees.plugins.credentials.SystemCredentialsProvider")[0]?.getStore()
if (store) {
// 기존 삭제
def existing = store.getCredentials(Domain.global()).find { it.id == "gitea-zio" }
if (existing) store.removeCredentials(Domain.global(), existing)
def cred = new UsernamePasswordCredentialsImpl(
CredentialsScope.GLOBAL, "gitea-zio", "Gitea zio account",
"zio", "Zio@Admin2026!"
)
store.addCredentials(Domain.global(), cred)
println "gitea-zio credential 등록 완료"
} else {
println "Store 없음 - credentials 플러그인 미설치"
}
""")
# ── 3. 글로벌 환경변수 설정 ───────────────────────────────────────────────────
jenkins_script('글로벌 환경변수', """
import jenkins.model.*
import hudson.slaves.*
def instance = Jenkins.instance
def globalProps = instance.globalNodeProperties
def existing = globalProps.getAll(EnvironmentVariablesNodeProperty.class)
EnvironmentVariablesNodeProperty prop
if (existing) {
prop = existing[0]
} else {
prop = new EnvironmentVariablesNodeProperty()
globalProps.add(prop)
}
def env = prop.getEnvVars()
env.put("ITSM_BASE_URL", "http://127.0.0.1:9001")
env.put("GITEA_URL", "http://127.0.0.1:9003")
env.put("SERVER_HOST", "101.79.17.164")
instance.save()
println "환경변수 설정 완료"
""")
# ── 4. Jenkinsfile 작성 ───────────────────────────────────────────────────────
print('\n[Jenkinsfile 작성]')
# zioinfo-web Jenkinsfile (이미 있으면 업데이트)
ZIOINFO_JF = '''pipeline {
agent any
environment {
SRC = '/opt/zioinfo/src'
JAR_DIR = '/opt/zioinfo/app'
STATIC = '/var/www/zioinfo'
MVN = '/usr/bin/mvn'
NOTIFY = "${ITSM_BASE_URL}/api/messenger/webhook"
}
options {
buildDiscarder(logRotator(numToKeepStr: '5'))
timeout(time: 20, unit: 'MINUTES')
timestamps()
}
stages {
stage('Checkout') {
steps {
checkout([
$class: 'GitSCM', branches: scm.branches,
userRemoteConfigs: scm.userRemoteConfigs,
extensions: [[$class: 'SparseCheckoutPaths',
sparseCheckoutPaths: [[path: 'frontend'], [path: 'backend']]
]]
])
}
}
stage('Frontend Build') {
steps {
dir('frontend') {
sh 'npm ci --legacy-peer-deps --prefer-offline 2>/dev/null || npm install --legacy-peer-deps'
sh 'npm run build'
}
}
}
stage('Backend Build') {
steps {
dir('backend') {
sh "${MVN} clean package -DskipTests -q"
}
}
}
stage('Deploy') {
when { branch 'main' }
steps {
sh """
cp backend/target/*.jar ${JAR_DIR}/app.jar
cp -r backend/src/main/resources/static/. ${STATIC}/
systemctl restart zioinfo
sleep 4
systemctl is-active zioinfo || exit 1
"""
}
}
}
post {
success { sh "curl -sf -X POST ${NOTIFY} -H 'Content-Type:application/json' -d '{\\"event\\":\\"build_result\\",\\"room\\":\\"ops\\",\\"success\\":true,\\"result_summary\\":\\"✅ zioinfo-web 배포 완료 #${BUILD_NUMBER}\\"}' 2>/dev/null || true" }
failure { sh "curl -sf -X POST ${NOTIFY} -H 'Content-Type:application/json' -d '{\\"event\\":\\"build_result\\",\\"room\\":\\"ops\\",\\"success\\":false,\\"result_summary\\":\\"❌ zioinfo-web 빌드 실패 #${BUILD_NUMBER}\\"}' 2>/dev/null || true" }
}
}'''
ITSM_JF = '''pipeline {
agent any
environment {
APP = '/opt/guardia/app'
VENV = '/opt/guardia/venv'
NOTIFY = "${ITSM_BASE_URL}/api/messenger/webhook"
}
options {
buildDiscarder(logRotator(numToKeepStr: '5'))
timeout(time: 15, unit: 'MINUTES')
timestamps()
}
stages {
stage('Checkout') { steps { checkout scm } }
stage('Install') {
steps { sh "${VENV}/bin/pip install -r requirements.txt -q" }
}
stage('Test') {
when { expression { fileExists('tests/') } }
steps {
sh "${VENV}/bin/pytest tests/ -q --tb=short --junitxml=test-results.xml || true"
junit allowEmptyResults: true, testResults: 'test-results.xml'
}
}
stage('Deploy') {
when { branch 'main' }
steps {
sh """
rsync -a --exclude=__pycache__ --exclude=.git \\
--exclude=rpa_rules.json --exclude='*.pyc' \\
. ${APP}/
systemctl restart guardia
sleep 4
systemctl is-active guardia || exit 1
"""
}
}
}
post {
success { sh "curl -sf -X POST ${NOTIFY} -H 'Content-Type:application/json' -d '{\\"event\\":\\"build_result\\",\\"room\\":\\"ops\\",\\"success\\":true,\\"result_summary\\":\\"✅ guardia-itsm 배포 완료 #${BUILD_NUMBER}\\"}' 2>/dev/null || true" }
failure { sh "curl -sf -X POST ${NOTIFY} -H 'Content-Type:application/json' -d '{\\"event\\":\\"build_result\\",\\"room\\":\\"ops\\",\\"success\\":false,\\"result_summary\\":\\"❌ guardia-itsm 빌드 실패 #${BUILD_NUMBER}\\"}' 2>/dev/null || true" }
}
}'''
MANAGER_JF = '''pipeline {
agent any
environment {
STATIC = '/var/www/manager'
NOTIFY = "${ITSM_BASE_URL}/api/messenger/webhook"
}
options { buildDiscarder(logRotator(numToKeepStr: '5')); timeout(time: 15, unit: 'MINUTES') }
stages {
stage('Checkout') { steps { checkout scm } }
stage('Build') {
steps {
dir('frontend') {
sh 'npm ci 2>/dev/null || npm install'
sh 'npm run build'
}
}
}
stage('Deploy') {
when { branch 'main' }
steps {
sh """
cp -r frontend/dist/. ${STATIC}/
systemctl restart guardia-manager 2>/dev/null || true
"""
}
}
}
post {
success { sh "curl -sf -X POST ${NOTIFY} -H 'Content-Type:application/json' -d '{\\"event\\":\\"build_result\\",\\"room\\":\\"ops\\",\\"success\\":true,\\"result_summary\\":\\"✅ guardia-manager 배포 완료\\"}' 2>/dev/null || true" }
failure { sh "curl -sf -X POST ${NOTIFY} -H 'Content-Type:application/json' -d '{\\"event\\":\\"build_result\\",\\"room\\":\\"ops\\",\\"success\\":false,\\"result_summary\\":\\"❌ guardia-manager 빌드 실패\\"}' 2>/dev/null || true" }
}
}'''
DOCS_JF = '''pipeline {
agent any
environment { DOCS = '/var/www/docs' }
options { buildDiscarder(logRotator(numToKeepStr: '3')) }
stages {
stage('Checkout') { steps { checkout scm } }
stage('Deploy') {
when { branch 'main' }
steps { sh 'mkdir -p ${DOCS} && cp -r . ${DOCS}/' }
}
}
}'''
# 서버의 독립 repo에 Jenkinsfile 작성
REPO_FILES = [
('/opt/zioinfo/src/Jenkinsfile', ZIOINFO_JF),
('/opt/guardia/app/Jenkinsfile', ITSM_JF),
('/opt/manager/Jenkinsfile', MANAGER_JF),
('/opt/docs/Jenkinsfile', DOCS_JF),
]
for path, content in REPO_FILES:
dir_path = '/'.join(path.split('/')[:-1])
client.exec_command(f'mkdir -p {dir_path}')
time.sleep(0.05)
try:
with sftp.open(path, 'w') as f:
f.write(content)
print(f' OK: {path}')
except Exception as ex:
print(f' SKIP {path}: {ex}')
# 로컬 workspace에도 Jenkinsfile 저장
import os
JF_MAP = {
r'C:\GUARDiA\workspace\zioinfo-web\Jenkinsfile': ZIOINFO_JF,
r'C:\GUARDiA\workspace\guardia-itsm\Jenkinsfile': ITSM_JF,
r'C:\GUARDiA\workspace\guardia-manager\Jenkinsfile': MANAGER_JF,
r'C:\GUARDiA\workspace\guardia-docs\Jenkinsfile': DOCS_JF,
}
for path, content in JF_MAP.items():
with open(path, 'w', encoding='utf-8') as f:
f.write(content)
print(f' local OK: {path.split(chr(92))[-2]}/Jenkinsfile')
# ── 5. Jenkins Job 생성 ───────────────────────────────────────────────────────
REPOS = {
'zioinfo-web': ('http://127.0.0.1:9003/zio/zioinfo-web.git', 'Jenkinsfile'),
'guardia-itsm': ('http://127.0.0.1:9003/zio/guardia-itsm.git', 'Jenkinsfile'),
'guardia-manager': ('http://127.0.0.1:9003/zio/guardia-manager.git', 'Jenkinsfile'),
'guardia-docs': ('http://127.0.0.1:9003/zio/guardia-docs.git', 'Jenkinsfile'),
}
for job_name, (git_url, jf_path) in REPOS.items():
job_xml = f'''<?xml version='1.1' encoding='UTF-8'?>
<flow-definition plugin="workflow-job">
<description>{job_name} CI/CD Pipeline</description>
<keepDependencies>false</keepDependencies>
<definition class="org.jenkinsci.plugins.workflow.cps.CpsScmFlowDefinition">
<scm class="hudson.plugins.git.GitSCM">
<userRemoteConfigs>
<hudson.plugins.git.UserRemoteConfig>
<url>{git_url}</url>
<credentialsId>gitea-zio</credentialsId>
</hudson.plugins.git.UserRemoteConfig>
</userRemoteConfigs>
<branches>
<hudson.plugins.git.BranchSpec>
<name>*/main</name>
</hudson.plugins.git.BranchSpec>
</branches>
</scm>
<scriptPath>{jf_path}</scriptPath>
<lightweight>true</lightweight>
</definition>
<triggers>
<com.cloudbees.jenkins.GitHubPushTrigger plugin="github">
<spec></spec>
</com.cloudbees.jenkins.GitHubPushTrigger>
</triggers>
</flow-definition>'''
# Job 생성
r = run(f'Job 생성: {job_name}', f"""
echo '{job_xml}' | java -jar /tmp/jenkins-cli.jar -s {JENKINS} -auth {AUTH} \
create-job {job_name} 2>/dev/null \
|| java -jar /tmp/jenkins-cli.jar -s {JENKINS} -auth {AUTH} \
update-job {job_name} << 'EOF'
{job_xml}
EOF
""")
run('Jenkins 플러그인 재시작', f"""
java -jar /tmp/jenkins-cli.jar -s {JENKINS} -auth {AUTH} restart 2>/dev/null || true
sleep 5
systemctl is-active jenkins
""")
sftp.close()
client.close()
print('\n=== CI/CD 파이프라인 구축 완료 ===')