在 Jenkins 中,节点是指属于 Jenkins 环境并用于执行构建任务的计算机。节点可以分为两种类型:内置节点和 agent 节点。
Jenkins基础服务中支持的代理节点从运行状态上来说可以分为固定节点&&动态生成节点。默认情况下Jenkins服务内置的代理节点只有固定节点,动态节点基本上是通过安装其他三方插件实现的,常见的动态节点插件有(K8S&&DOCKER等)
所以默认情况下JenkinsApi中只支持基于固定节点的管理操作。
针对Jenkins默认支持的固定节点,在启动的时候有以下几种方式实现:
<slave>
<name>sshsshname>
<description>description>
<remoteFS>/dataremoteFS>
<numExecutors>1numExecutors>
<mode>NORMALmode>
<retentionStrategy class="hudson.slaves.RetentionStrategy$Always"/>
<launcher class="hudson.plugins.sshslaves.SSHLauncher" plugin="[email protected]_c390e">
<host>1.1.1.1host>
<port>22port>
<credentialsId>312313credentialsId>
<launchTimeoutSeconds>60launchTimeoutSeconds>
<maxNumRetries>10maxNumRetries>
<retryWaitTime>15retryWaitTime>
<sshHostKeyVerificationStrategy class="hudson.plugins.sshslaves.verifiers.KnownHostsFileKeyVerificationStrategy"/>
<tcpNoDelay>truetcpNoDelay>
launcher>
<label>aaaalabel>
<nodeProperties/>
slave>
<slave>
<name>2222name>
<description>stringdescription>
<remoteFS>/var/remoteFS>
<numExecutors>0numExecutors>
<mode>NORMALmode>
<retentionStrategy class="hudson.slaves.RetentionStrategy$Always"/>
<launcher class="hudson.slaves.JNLPLauncher">
<workDirSettings>
<disabled>falsedisabled>
<internalDir>remotinginternalDir>
<failIfWorkDirIsMissing>falsefailIfWorkDirIsMissing>
workDirSettings>
<webSocket>truewebSocket>
launcher>
<label>Linuxlabel>
<nodeProperties/>
slave>
<slave>
<name>{{nodeName}}name>
<description>{{nodeDescription}}description>
<remoteFS>{{workDir}}remoteFS>
<numExecutors>{{numbers}}numExecutors>
<mode>NORMALmode>
<retentionStrategy class="hudson.slaves.RetentionStrategy$Always"/>
<launcher class="hudson.slaves.CommandLauncher" plugin="[email protected]_7c31">
<agentCommand>{{Command}}agentCommand>
launcher>
<label>{{Labels}}label>
<nodeProperties/>
slave>
'''Create a node
:param name: name of node to create, ``str`` 节点名字
:param numExecutors: number of executors for node, ``int`` 节点最多能同时运行多少个流水线
:param nodeDescription: Description of node, ``str`` 节点说明
:param remoteFS: Remote filesystem location to use, ``str`` 远程节点干活的目录
:param labels: Labels to associate with node, ``str`` #节点标签
:param exclusive: Use this node for tied jobs only, ``bool`` 是否尽可能使用该节点
:param launcher: The launch method for the slave, ``jenkins.LAUNCHER_COMMAND``, \ ``jenkins.LAUNCHER_SSH``, ``jenkins.LAUNCHER_JNLP``, ``jenkins.LAUNCHER_WINDOWS_SERVICE`` 节点支持的启动类型
:param launcher_params: Additional parameters for the launcher, ``dict`` 节点启动类型的额外参数 '''
import jenkins
j = jenkins.Jenkins(url="http://jenkins.demonlg.cn",username="admin",password="xxxx")
j.create_node(name="sssss",numExecutors=10,nodeDescription="这是一个测试节点",
remoteFS="/home/node_agent",labels="aaa",exclusive=False,
launcher=jenkins.LAUNCHER_JNLP,launcher_params={})
import jenkins
import xml.etree.ElementTree as ET
j = jenkins.Jenkins(url="http://jenkins.demonlg.cn",username="admin",password="xxxx")
node_config = j.get_node_config(name="sssss") #这个方法返回的是 节点xml文件的字符串
root = ET.fromstring(node_config)
for i in root.iter('description'):
new_rank = "我被修改了"
i.text = str(我被修改了)
new_config_xml = ET.tostring(new_config).decode('utf-8')
j.reconfig_node("sssss",new_config_xml)
2、如果节点的属性参数均已可以固化,则可以利用模版+动态传参的形式实现相关的替换
import jenkins
from jinja2 import Template
new_xml_tempalte = """
{{nodeName}}
{{nodeDescription}}
{{workDir}}
{{numbers}}
NORMAL
{{Command}}
"""
j = jenkins.Jenkins(url="http://jenkins.demonlg.cn",username="admin",password="xxxx")
template = Template(new_xml_tempalte).render({"nodeName":"sssss","nodeDescription":"我是要修改的节点","workDir":"/var",
"numbers":10,"Command":"ls -al","Laebles":"text"})
j.reconfig_node("sssss",template)
import jenkins
j = jenkins.Jenkins(url="http://jenkins.demonlg.cn",username="admin",password="xxxx")
j.delete_node("sssss")
import jenkins
j = jenkins.Jenkins(url="http://jenkins.demonlg.cn",username="admin",password="xxxx")
node_list = j.get_nodes(depth=0) ##depth越大代表获取的信息越多
print(node_list)
import jenkins
j = jenkins.Jenkins(url="http://jenkins.demonlg.cn",username="admin",password="xxxx")
config = j.get_node_config("sssss")
print(config)
import jenkins
j = jenkins.Jenkins(url="http://jenkins.demonlg.cn",username="admin",password="xxxx")
info = j.get_node_info(name="sssss",depth=0)
print(info)
import jenkins
j = jenkins.Jenkins(url="http://jenkins.demonlg.cn",username="admin",password="xxxx")
j.enable(name="sssss")
import jenkins
j = jenkins.Jenkins(url="http://jenkins.demonlg.cn",username="admin",password="xxxx")
j.disable(name="sssss")
import jenkins
j = jenkins.Jenkins(url="http://jenkins.demonlg.cn",username="admin",password="xxxx")
j.assert_node_exists(name="sssss",exception_message="不存在") ###exception_message 自定义的消息体
本文介绍的动态节点主要涉及到2个方向(Docker Cloud &&Kubernetes Cloud)这2类动态节点都是基于第三方的jenkins插件,需要提前在jenkins服务器上安装对应插件。
安装完相应插件后在jenkins管理后台的节点管理中会多出一个云节点功能。此时就可以通过云节点功能去添加相关的配置然后实现部署动态的节点。
遇到这种问题时,有的时候需要转化一下思路。Jenkins本身服务支持脚本命令行对服务进行一系列操作,恰巧JenkinsApi的run_script()方法也支持这种功能的调用,想到这里初步确定了一种跨语言调用等方式实现功能开发。
经过查看DockerPlugins源码&&Kubernetes Cloud源码找到了最终的解决办法。
解决办法:
编写groovy方法实现添加DockerCloud及Kubernetes Cloud等功能
# GROOVY操作使用的依赖
JENKINS_CLASS_IMPORT_STR = """
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import groovy.json.JsonBuilder
import com.nirima.jenkins.plugins.docker.DockerCloud
import com.nirima.jenkins.plugins.docker.DockerDisabled
import com.nirima.jenkins.plugins.docker.DockerImagePullStrategy
import com.nirima.jenkins.plugins.docker.DockerTemplate
import com.nirima.jenkins.plugins.docker.DockerTemplateBase
import com.nirima.jenkins.plugins.docker.launcher.AttachedDockerComputerLauncher
import hudson.slaves.Cloud
import io.jenkins.docker.client.DockerAPI
import io.jenkins.docker.connector.DockerComputerAttachConnector
import io.jenkins.docker.connector.DockerComputerConnector
import io.jenkins.docker.connector.DockerComputerJNLPConnector
import jenkins.model.Jenkins
import org.jenkinsci.plugins.docker.commons.credentials.DockerServerEndpoint
\n
"""
# 列出所有的dockerCloud的名字
JENKINS_DOCKER_LIST = JENKINS_CLASS_IMPORT_STR + """
Jenkins jenkins = Jenkins.get()
clouds_list = []
for (i in jenkins.clouds){
if (i){
clouds_list.add("\'"+i.name+"\'")
}
}
println clouds_list
"""
# 查询dockerCloud是否存在
DOCKER_CLOUD_EXIST = JENKINS_CLASS_IMPORT_STR + '''
Jenkins jenkins = Jenkins.get()
Cloud oldCloudOrNull = jenkins.clouds.getByName('%s')
if (oldCloudOrNull){
println 'True'
}
else{
println 'False'
}
'''
# 新增容器云节点
ADD_DOCKER_CLOUD = JENKINS_CLASS_IMPORT_STR + """
Jenkins jenkins = Jenkins.get()
def cloudParameters = [
serverUrl: 'tcp://%s',
name: '%s',
containerCap: 0, // 0 means no cap
credentialsId: '',
connectTimeout: 60,
readTimeout: 60,
version: '',
dockerHostname: '',
exposeDockerHost: false,
disabled: false,
errorDuration: (Integer)null,
]
DockerComputerConnector computerConnector = new DockerComputerAttachConnector()
Set cloudParametersHandledSpecially = [ 'serverUrl', 'credentialsId' ,'serverUrl' ,'credentialsId' ,'connectTimeout' ,'readTimeout' ,'version' ,'connectTimeout' ,'dockerHostname' ,'name' ]
DockerAPI api = new DockerAPI(new DockerServerEndpoint(cloudParameters.serverUrl, cloudParameters.credentialsId))
api.with {
connectTimeout = cloudParameters.connectTimeout
readTimeout = cloudParameters.readTimeout
apiVersion = cloudParameters.version
hostname = cloudParameters.dockerHostname
}
DockerCloud newCloud = new DockerCloud(
cloudParameters.name,
api,
[]
)
def a= jenkins.clouds.add(newCloud)
println a
println 'True'
"""
# 删除容器云主机
REMOVE_DOCKER_CLOUD = JENKINS_CLASS_IMPORT_STR + """
Jenkins jenkins = Jenkins.get()
def res = jenkins.clouds.remove(jenkins.clouds.getByName(\'%s\'))
if (res){
println 'True'
}else{
println 'False'
}
"""
# 列出所有的容器云的名字
JENKINS_DOCKER_CLOUD_LIST = JENKINS_CLASS_IMPORT_STR + """
Jenkins jenkins = Jenkins.get()
clouds_list = []
for (i in jenkins.clouds){
if (i){
clouds_list.add("\'"+i.name+"\'")
}
}
println clouds_list
"""
# 列出Docker Cloud下所有模版的信息
DOCKER_TEMPLATE_LIST = JENKINS_CLASS_IMPORT_STR + """
Jenkins jenkins = Jenkins.get()
templates_list = []
def a = jenkins.clouds.getByName(\'%s\')
for (i in a.templates){
//println i.labelString
templates_list.add("\'"+i.labelString+"\'")
}
println templates_list
"""
import jenkins
class DockerCloud(object):
"""jenkins中DockerCloud节点管理模块"""
def __init__(self, conn):
self.conn = conn
def add(self, name, url):
"""
向jenkins中添加dockerCloud节点,前提要素是jenkins中已经安装docker commons,docker plugins等相关插件
:param name: dockerCloud节点的名字,exp: node1
:param url: dockerCloud节点的链接方式,exp:1.1.1.1:2042
:return:
"""
if not self.exist(name):
docker_could_instance = ADD_DOCKER_CLOUD % (name, url)
c = self.conn.run_script(docker_could_instance)
print(c)
if not c:
print('节点添加失败!')
else:
print("节点添加成功!")
elif self.exist(name):
print("节点已经存在,无法再次添加!")
def remove(self, name):
"""
删除dockerCloud节点
:param name: dockerCloud节点的名字,exp: node1
:param url: dockerCloud节点的链接方式,exp:1.1.1.1:2042
:return:
"""
if self.conn.run_script(REMOVE_DOCKER_CLOUD % name):
print('节点删除成功!')
else:
print('节点删除失败!')
def exist(self, name):
"""
检查 dockerCloud是否已经存在
:param name: dockerCloud节点的名字,exp: node1
:return: 存在 True,不存在False,其他异常报错,None
"""
res = self.conn.run_script(DOCKER_CLOUD_EXIST % name)
if ast.literal_eval(res):
return True
else:
return False
def list(self, ):
"""
查询docker节点相关详细配置内容
:return:
"""
res = ast.literal_eval(self.conn.run_script(JENKINS_DOCKER_CLOUD_LIST))
if len(res) == 0:
raise jenkins.EmptyResponseException('没找到可用的容器云节点!')
return res
class DockerTemplate(object):
"""
DockerCLoud中的docker模板管理功能
"""
def __init__(self, conn, node_name):
self.conn = conn
self.name = node_name
res = self.conn.run_script(DOCKER_CLOUD_EXIST % self.name)
if not ast.literal_eval(res):
raise NotDockerCloudException('jenkins上没有对应的容器云节点%s' % self.name)
def exist(self, label_name):
"""
:param label_name: 模板的标签名,此参数是要求唯一根目录已存在!的
:return:
"""
if label_name in self.list():
print('模板已经在dockerCloud中')
return True
else:
print('模板不在dockerCloud中')
return False
def list(self, ):
res = ast.literal_eval(self.conn.run_script(DOCKER_TEMPLATE_LIST % self.name))
if len(res) == 0:
raise EmptyResponseException('没有对应的模板信息!')
return res
j = jenkins.Jenkins(url="http://jenkins.demonlg.cn",username="admin",passowrd="xxxxx00")
docker_cloud = DockerCloud(j)
#添加Docker Cloud
docker_cloud.add("测试容器节点","http://1.1.1.1:2375")
#显示所有的Docker Cloud
docker_cloud.list()
#删除指定DockerCloud
docker_cloud.remove("测试容器节点")
#向特定Docker Cloud中添加模版
docker_template = DockerTemplate(j,"测试容器节点")
#显示特定Docker Cloud下所有的模版
docker_template.list()
####新增k8s云节点相关功能
import jenkins
class K8sNode(BaseModel):
"""
k8s云节点数据结构
"""
uid: str = Field(default=str(uuid.uuid1()).replace('-', ''),alias="Uid")
name: str = Field(alias="Name")
kubernetes_url: str = Field(alias="KubernetesUrl",default="\"\"")
kubernetes_cert: str = Field(alias="KubernetesCert",default="\"\"")
is_https: bool = Field(alias="IsHttps",default=False)
use_proxy: bool = Field(alias="UseProxy",default=False)
namespace: str = Field(alias="Namespace",default="")
jnlp_docker_registry: str = Field(alias="JnlpDockerRegistry",default="\"\"")
kubernetes_secret: str = Field(alias="KubernetesSecret",default="对应k8s相关的授权凭据id")
is_websocket: bool = Field(alias="IsWebsocket",default=False)
is_direct: bool = Field(alias="IsDirect",default=False)
jenkins_url: str = Field(alias="JenkinsUrl",default="\"\"")
connection_timeout: int = Field(alias="ConnectionTimeout",default=5)
read_timeout: int = Field(alias="ReadTimeout",default=15)
max_containter_num: int = Field(alias="MaxContainerNum",default=100)
retention_timout: int = Field(alias="RetentionTimout",default=120)
class K8SCould(jenkins.Jenkins):
"""
显示K8SCould插件类型节点信息, 全查
"""
def get_node(self,uid):
"""
单独查询
"""
display_command = """import groovy.json.JsonOutput
def res = [:]
def data = [:]
try{
def k8s = Jenkins.get().clouds.getByName(\'%s\')
data.Uid = \"%s\"
data.KubernetesUrl = k8s.getServerUrl()
data.KubernetesCert = k8s.getServerCertificate()
data.IsHttps = k8s.isSkipTlsVerify()
data.UseProxy = k8s.isUseJenkinsProxy()
data.Namespace = k8s.getNamespace()
data.JnlpDockerRegistry = k8s.getJnlpregistry()
data.KubernetesSecret = k8s.getCredentialsId()
data.IsWebsocket = k8s.isWebSocket()
data.IsDirect = k8s.isDirectConnection()
data.JenkinsUrl = k8s.getJenkinsUrlOrNull()
data.ConnectionTimeout = k8s.getConnectTimeout()
data.ReadTimeout = k8s.getReadTimeout()
data.MaxContainerNum = k8s.getContainerCap()
data.RetentionTimout = k8s.getRetentionTimeout()
res.code = 1000
res.msg = "操作成功"
res.data = data
}catch(e){
res.code = 1002
res.msg = "操作异常:" +e
}
println( JsonOutput.toJson(res))"""%(uid,uid)
try:
match self.is_exist(uid).get("code"):
case 1000:
return orjson.loads(self.run_script(display_command))
case _:
return self.is_exist(uid)
except orjson.JSONDecodeError:
return {'code': 1002, 'msg': '脚本返回数据异常无法解析'}
except Exception:
return {'code': 1002, 'msg': '未知错误:'+traceback.format_exc()}
def list(self,):
"""
显示jenkins中存在的k8s云节点控制器
"""
nodes = """import groovy.json.JsonOutput
def res = [:]
def a = []
try{
jenkins = Jenkins.get().clouds
for ( i in jenkins){
if (i instanceof org.csanchez.jenkins.plugins.kubernetes.KubernetesCloud ){
def aa = [:]
aa.name = i.name
aa.url = i.serverUrl
a.add(aa)
}
res.code = 1000
res.mgs = "操作成功"
res.data = a
}}catch(e){
res.code = 1002
res.msg = "操作失败"
res.data = a
}
println( JsonOutput.toJson(res)) """
try:
return orjson.loads( self.run_script(nodes))
except orjson.JSONDecodeError:
return {'code': 1002, 'msg': '脚本返回数据异常无法解析'}
except Exception:
return {'code': 1002, 'msg': '未知错误'+traceback.format_exc()}
def add_node(self,k8s_info :K8sNode ):
"""
用于添加Jenkins云节点。 新增
k8s_info: 添加k8s云节点相关的参数
"""
add_command = """import org.csanchez.jenkins.plugins.kubernetes.KubernetesCloud
import groovy.json.JsonOutput
def res = [:]
try{
def a = new KubernetesCloud(name=\'%s\',templates=[],serverUrl=%s,namespace=%s,jenkinsUrl=%s,containerCapStr='%s',connectTimeout=%s,readTimeout=%s,retentionTimout=%s)
a.setSkipTlsVerify(%s)
a.setWebSocket(%s)
a.setUsageRestricted(%s)
a.setCredentialsId(\'%s\')
Jenkins.get().clouds.add(a)
res.msg ='操作成功'
res.code = 1000
println(JsonOutput.toJson(res))
}catch(e){
res.msg ='操作失败:'+e
res.code = 1001
println(JsonOutput.toJson(res))
}"""%(k8s_info.uid,k8s_info.kubernetes_url,k8s_info.namespace,
k8s_info.jenkins_url,k8s_info.max_containter_num,k8s_info.connection_timeout,
k8s_info.read_timeout,k8s_info.retention_timout,"true" if k8s_info.is_https else "false","true" if k8s_info.is_websocket else "false",
"true" if k8s_info.is_direct else "false",k8s_info.kubernetes_secret)
try:
match self.is_exist(k8s_info.uid).get('code'):
case 1001:
return orjson.loads(self.run_script(add_command))
case 1000:
return {'code': 1001, 'msg': '节点存在'}
case _:
return self.is_exist(k8s_info.uid)
except orjson.JSONDecodeError:
return {'code': 1002, 'msg': '脚本返回数据异常无法解析'}
except Exception:
print(traceback.print_exc())
return {'code': 1002, 'msg': '未知错误'}
def remove_node(self,uid):
"""
删除节点信息
"""
delete_command = """
import groovy.json.JsonOutput
def res = [:]
try{
Jenkins.get().clouds.remove(Jenkins.get().clouds.getByName(\'%s\'))
res.code = 1000
res.msg = "操作成功"}catch(e){
res.code = 1001
res.msg = "操作失败:"+e
}
println(JsonOutput.toJson(res))
"""%uid
try:
match self.is_exist(uid).get('code'):
case 1000:
return orjson.loads(self.run_script(delete_command))
case _:
return self.is_exist(uid)
except orjson.JSONDecodeError:
return {'code': 1002, 'msg': '脚本返回数据异常无法解析'}
except Exception:
return {'code': 1002, 'msg': '未知错误'}
def is_exist(self, uid: str):
"""
用于查找k8s节点是否存在。
:param uid: uid k8s阶段的唯一标识
"""
is_exist_command= '''import groovy.json.JsonOutput
def res = [:]
Jenkins jenkins = Jenkins.get()
try{
def oldCloudOrNull = jenkins.clouds.getByName('%s')
if (oldCloudOrNull) {
if (oldCloudOrNull instanceof org.csanchez.jenkins.plugins.kubernetes.KubernetesCloud) {
res.code = 1000
res.msg = "节点存在"
} else {
res.code = 1001
res.msg = "不属于云节点类型:"+oldCloudOrNull.class.name
}
}
else{
res.code = 1001
res.msg = "节点不存在"
}}catch(e){
res.code = 1002
res.msg = "操作失败:" +e
}
println JsonOutput.toJson(res)
'''%uid
try:
return orjson.loads(self.run_script(is_exist_command))
except orjson.JSONDecodeError:
return {'code': 1002, 'msg': '脚本返回数据异常无法解析'}
except Exception:
return {'code': 1002, 'msg': '脚本返回数据异常无法解析'}
def update_node(self,k8s_info:K8sNode):
try:
update_command= """
import groovy.json.JsonOutput
def res = [:]
try{
def k8s = Jenkins.get().clouds.getByName("%s")
k8s.setServerUrl(\"%s\")
k8s.setServerCertificate(\'\'\'%s\'\'\')
k8s.setSkipTlsVerify(%s)
k8s.setUseJenkinsProxy(%s)
k8s.setNamespace(\"%s\")
k8s.setJnlpregistry(\"%s\")
k8s.setWebSocket(%s)
k8s.setDirectConnection(%s)
k8s.setJenkinsUrl(\"%s\")
k8s.setConnectTimeout(%s)
k8s.setReadTimeout(%s)
k8s.setContainerCapStr(\"%s\")
k8s.setRetentionTimeout(%s)
res.code = 1000
res.msg = "操作成功"
}catch(e){
res.code = 1002
res.msg = "操作异常:" +e
}
println(JsonOutput.toJson(res))
"""%(k8s_info.uid,k8s_info.kubernetes_url,k8s_info.kubernetes_cert,("true" if k8s_info.is_https else "false"),
("true" if k8s_info.use_proxy else "false"),k8s_info.namespace,k8s_info.jnlp_docker_registry,("true" if k8s_info.is_websocket else "false"),
("true" if k8s_info.is_direct else "false"),k8s_info.jenkins_url,k8s_info.connection_timeout,k8s_info.read_timeout,
k8s_info.max_containter_num,k8s_info.retention_timout
)
match self.get_node(k8s_info.uid).get("code"):
case 1000:
return orjson.loads(self.run_script(update_command))
case _:
return self.get_node(k8s_info.name)
except orjson.JSONDecodeError:
return {'code': 1002, 'msg': '脚本返回数据异常无法解析'}
except Exception:
return {'code': 1002, 'msg': '未知错误'+traceback.format_exc()}
##添加Kubernetes Cloud
k_node = K8sNode(Uid="abcabc",Name="测试集群",KubernetesSecret="Jenkins中已经创建的凭据id",KubernetesUrl="https://1.2.3.4:6443",Namespace="default",JenkinsUrl="http:\\jenkins.demonlg.cn",)
k = K8SCould(url="http://jenkins.demonlg.cn",username="admin",password="xxxxx")
k.add_node(k_node) #添加K8S集群
k.remove_node(uid="abcabc") #删除K8S集群
k.list() #获取所有Kubernetes Cloud 节点
PS:在Jenkins节点管理页面配置固定节后后,页面会生成一条在agent节点上使用的连接命令,但是在JenkinsApi调用接口生成的时候无法直接生成此命令,如果需要生成此命令需要自行扩展相关内容
import jenkins
class JenkinsPlugins(jenkins.Jenkins):
def create_agent_command(self, node_name, url, remote_path):
"""
:param node_name: 节点的名字
:param url: 这个是agent连接Jenkins的地址 http://,
:param remote_path: 这个是生成节点时输入的远程工作目录字段
:return:
"""
secret_str = self.run_script(
f"println(jenkins.model.Jenkins.getInstance().getComputer('{node_name}')?.getJnlpMac())")
res_info = f"""方法一:
java -jar agent.jar -jnlpUrl {url}/computer/{node_name}/jenkins-agent.jnlp -secret {secret_str} -workDir {remote_path}
方法二:
echo {secret_str} > secret-file
java -jar agent.jar -jnlpUrl {url}/computer/{node_name}/jenkins-agent.jnlp -secret @secret-file -workDir {remote_path} """
return res_info if res_info.find("https://") == -1 else res_info.replace("-O", "-sO")