jenkins插件开发(一)——h5发布到CDN

一、背景知识

h5服务的部署有两种方式,一是自己搭建web服务,再由nginx等反向代理;二是本方案中的CDN。【使用CDN的好处这里就不赘述了】

有些人可能会说,还有其他的好多容器可以做到,这里因为涉及到域名映射,对外访问的地址不能是ip,所以我们说大多是使用nginx反向代理来实现。
(但它也不是本文要讲述的方式)

本文主要讲述我们公司是如何实现h5发布到oss上的,因为我们打包构建使用的是jenkins,所以我写了一个Jenkins plugin。(源码已上传到github:https://github.com/zwp201301/jenkins-oss-plugin,希望能够帮助到类似需求的小伙伴!)

二、问题描述

  • 前后端跨域问题

云存储服务都有配置跨域的界面,包括Minio这样的内网文件系统。

  • 多环境部署

通过子目录的区分,比如dev-开发环境,test-测试环境,staging-预发环境,prod-生产环境。

  • 发布及回滚

因为不能做删除操作,发布的时候是每次进行覆盖全部,不做删除动作;遇到需要回滚,则对上一个版本的代码进行二次发布。

  • 多版本共存

这里没有像多环境那样,新建版本号的子目录去区分不同版本号。
那样可以做到同时有多个版本共存的情况,相对来说,占用的空间较大,维护的工作量也较大。
暂时没这样去做,看各自的实际需求而定。

三、jenkins plugin

代码实现逻辑分为三大步

  • 将需要发布的文件统一拷贝至目标目录下
  • 逐个读取文件,以及文件的相对路径
  • 调用云存储或minio等上传文件的api

详细的源码我将上传到github仓库,本文梳理出来几个关键实现:

1、插件的入口类

参考https://github.com/jenkinsci/ssh-steps-plugin的实现,入口类为UploadFileStep.java,它是一个Step。因为使用方式是pipeline,我们就没有实现由页面接收入参的方式。

因为jenkins插件运行在master节点,它需要去读取slave节点上的h5/css/js等文件,所以不能是普通的Builder。

  • 一定要有构造函数,并在它上面加注解@DataBoundConstructor。
  • 作为入口,接收用户的参数是它的一个主要功能,另外它实现了Step的start()方法。
@Override
    public StepExecution start(StepContext context) throws Exception {
        return new Execution(this, context);
    }
// 线程CommandCallable的实现:
            public Object execute() {
                return getService().upload(getListener(), getWorkspace());
            }

// 核心语句,调用了类UploadFileService的上传文件方法。

2、源码引用了minio的jar包,它依赖的guava版本比Jenkins应用原本依赖的guava要高很多,默认的加载机制是以jenkins应用为准的,也就是说不会使用插件指定的guava版本。所以,我们在pom.xml中必须设置插件优先。

<plugin>
                <groupId>org.jenkins-ci.tools</groupId>
                <artifactId>maven-hpi-plugin</artifactId>
                <configuration>
                    <minimumJavaVersion>8</minimumJavaVersion>
                    <pluginFirstClassLoader>true</pluginFirstClassLoader>
                </configuration>
            </plugin>

在调试的过程中,我遇到的报错有:

java.lang.NoSuchMethodError: com.google.common.base.Preconditions.checkArgument(ZLjava/lang/String;C)V
    at com.google.common.io.BaseEncoding$Alphabet.<init>(BaseEncoding.java:458)
    at com.google.common.io.BaseEncoding$Base64Encoding.<init>(BaseEncoding.java:919)
    at com.google.common.io.BaseEncoding.<clinit>(BaseEncoding.java:322)
    at io.minio.Digest.sha256Md5Hashes(Digest.java:89)
    at io.minio.MinioClient.createRequest(MinioClient.java:874)
    at io.minio.MinioClient.execute(MinioClient.java:981)
    at io.minio.MinioClient.execute(MinioClient.java:935)
    at io.minio.MinioClient.executeHead(MinioClient.java:1204)
    at io.minio.MinioClient.bucketExists(MinioClient.java:3592)
    at com.xhtech.tool.jenkins.service.MinioOssService.upload(MinioOssService.java:31)
    at com.xhtech.tool.jenkins.service.UploadFileService.iterateWorkspace(UploadFileService.java:80)
    at com.xhtech.tool.jenkins.service.UploadFileService.upload(UploadFileService.java:48)
    at com.xhtech.tool.jenkins.steps.UploadFileStep$Execution$CommandCallable.execute(UploadFileStep.java:104)
    at com.xhtech.tool.jenkins.util.SSHMasterToSlaveCallable.call(SSHMasterToSlaveCallable.java:38)
    at hudson.remoting.UserRequest.perform(UserRequest.java:212)
    at hudson.remoting.UserRequest.perform(UserRequest.java:54)
    at hudson.remoting.Request$2.run(Request.java:369)
    at hudson.remoting.InterceptingExecutorService$1.call(InterceptingExecutorService.java:72)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at hudson.remoting.Engine$1.lambda$newThread$0(Engine.java:93)
    at java.lang.Thread.run(Thread.java:748)
java.lang.NoSuchMethodError: com.google.common.base.CharMatcher.ascii()Lcom/google/common/base/CharMatcher;
    at com.google.common.io.BaseEncoding$Alphabet.<init>(BaseEncoding.java:452)
    at com.google.common.io.BaseEncoding$Base64Encoding.<init>(BaseEncoding.java:891)
    at com.google.common.io.BaseEncoding.<clinit>(BaseEncoding.java:316)
    at io.minio.Digest.sha256Md5Hashes(Digest.java:89)
    at io.minio.MinioClient.createRequest(MinioClient.java:874)
    at io.minio.MinioClient.execute(MinioClient.java:981)
    at io.minio.MinioClient.execute(MinioClient.java:935)
    at io.minio.MinioClient.executeHead(MinioClient.java:1204)
    at io.minio.MinioClient.bucketExists(MinioClient.java:3592)

报错原因是说guava的类没找到,其他插件依赖的guava版本已经升上去了。

查看jenkins的jar包.png
jenkins应用依赖的jar.png

从下图中可以看出,Jenkins应用依赖的guava版本比较低,11.0.1,远低于minio jar 7.1.4依赖的版本(25.1-jre)

guava.png

那么我们的自定义插件,是否已经是guava 25.1-jre?答案:是。

插件.png
guava版本.png

总结:报错的根由是Jenkins应用的guava版本替换了插件所引用的guava版本。

3、OSS工厂,根据用户的选择,采用不同的策略发布到不同的OSS。

  • MinioOssService.java(实现类)
  • AliyunOssService.java(实现类)
  • OssService.java(接口)
  • OssFactory.java(工程类)

4、核心实现类UploadFileService.java

作为核心实现,它会遍历工作空间下的目标文件,解析出环境和工程名,然后是调用具体的oss实现去上传文件。
需要注意的是,oss远程地址必须是区分路径层级。我们约定的规则,工程名+环境名+目标目录的相对路径,比如oms/test/img/logo.jpg, oms/test/index.html

  • 工程名是oms
  • 环境名是test
  • 目标目录下的相对路径是img/logo.jpg、index.html
private void iterateWorkspace(String workspacePath) {
        String distPath = workspacePath + File.separator + this.targetPath;
        LogUtil.println("上传的文件目录来源:" + distPath);
        String env = this.getEvnByWorkspace(workspacePath);
        LogUtil.println("发布至环境:" + env);
        String appName = this.getAppNameByWorkspace(workspacePath);
        LogUtil.println("工程名称:" + appName);

        List<File> files = PathUtil.loopFiles(Paths.get(distPath), null);

        LogUtil.println("上传至OSS【" + ossType + "】的bucket:【" + bucketName + "】");

        for (File file : files) {
            String ossFileName = new StringBuilder(appName)
                    .append(File.separator)
                    .append(env)
                    .append(file.getPath().replaceAll(distPath, ""))
                    .toString();

            if (this.debug) {
                LogUtil.println("上传到OSS的文件名是" + ossFileName + ",本地路径是" + file.getAbsolutePath());
            }

            OssFactory.getOssService(this.ossType).upload(this.bucketName,
                    ossFileName,
                    new File(file.getAbsolutePath()));
        }
    }

四、pipeline使用示例

我们使用的Jenkins集群是采用K8S部署的Master-Slave结构。

自定义插件的pipeline写法.png

注意:我们在jenkinsfile文件中,写法见下:

-- ossType是由jenkins job的参数传入
stage('Upload File To OSS') {
            when {
                expression { "aliyun" == ossType || "minio" == ossType }
            }
            steps {
                script {
                    tools.PrintMes("Upload File To OSS!!!", "green")
                    uploadFile debug: true, ossType: ossType
                }
            }
            post {
                failure {
                    //当此Pipeline失败时打印消息
                    script {
                        tools.PrintMes("Upload File To OSS failure!!!", "red")
                        http.imNotfiy(projectName, "FAIL", buildEnv, "Upload File To OSS failure", branch, buildUser)
                    }
                }
            }
        }

上面的pipeline语句"uploadFile debug: true, ossType: ossType"对应源码是:

@Extension
    public static class DescriptorImpl extends SSHStepDescriptorImpl {

        @Override
        public String getFunctionName() {
            return "uploadFile";
        }

        @Override
        public String getDisplayName() {
            return getPrefix() + getFunctionName();
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天草二十六_简村人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值