Selaa lähdekoodia

feat: basic build

wanghongkai 5 vuotta sitten
commit
699df4b23f
94 muutettua tiedostoa jossa 5706 lisäystä ja 0 poistoa
  1. 35 0
      .gitignore
  2. 118 0
      .mvn/wrapper/MavenWrapperDownloader.java
  3. BIN
      .mvn/wrapper/maven-wrapper.jar
  4. 13 0
      Dockerfile
  5. 310 0
      mvnw
  6. 182 0
      mvnw.cmd
  7. 112 0
      pom.xml
  8. 13 0
      settings.xml
  9. 15 0
      src/main/java/cn/seecoder/paas/SeecoderPaasApplication.java
  10. 9 0
      src/main/java/cn/seecoder/paas/data/dao/ApplicationDAO.java
  11. 14 0
      src/main/java/cn/seecoder/paas/data/dao/ConfigDAO.java
  12. 14 0
      src/main/java/cn/seecoder/paas/data/dao/EnvironmentDAO.java
  13. 33 0
      src/main/java/cn/seecoder/paas/data/entity/Application.java
  14. 43 0
      src/main/java/cn/seecoder/paas/data/entity/Config.java
  15. 61 0
      src/main/java/cn/seecoder/paas/data/entity/Environment.java
  16. 17 0
      src/main/java/cn/seecoder/paas/service/ApplicationService.java
  17. 10 0
      src/main/java/cn/seecoder/paas/service/ConfigService.java
  18. 22 0
      src/main/java/cn/seecoder/paas/service/EnvironmentService.java
  19. 16 0
      src/main/java/cn/seecoder/paas/service/facade/docker/DockerApi.java
  20. 90 0
      src/main/java/cn/seecoder/paas/service/facade/docker/impl/DockerApiImpl.java
  21. 9 0
      src/main/java/cn/seecoder/paas/service/facade/git/GitApi.java
  22. 32 0
      src/main/java/cn/seecoder/paas/service/facade/git/impl/GitApiImpl.java
  23. 17 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/AbstractApi.java
  24. 6 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/ConfigMapApi.java
  25. 19 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/DeploymentApi.java
  26. 37 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/ExecApi.java
  27. 13 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/IngressApi.java
  28. 46 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/K8sConstants.java
  29. 85 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/LogApi.java
  30. 7 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/NamespaceApi.java
  31. 6 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/PodApi.java
  32. 82 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/SecretApi.java
  33. 8 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/ServiceApi.java
  34. 17 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/WatchApi.java
  35. 68 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/exception/K8sApiException.java
  36. 171 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/impl/ConfigMapApiImpl.java
  37. 203 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/impl/DeploymentApiImpl.java
  38. 70 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/impl/ExecApiImpl.java
  39. 208 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/impl/IngressApiImpl.java
  40. 105 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/impl/LogApiImpl.java
  41. 144 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/impl/NamespaceApiImpl.java
  42. 104 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/impl/PodApiImpl.java
  43. 149 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/impl/SecretApiImpl.java
  44. 172 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/impl/ServiceApiImpl.java
  45. 71 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/impl/WatchApiImpl.java
  46. 48 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/model/ConfigMap.java
  47. 104 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/model/Container.java
  48. 46 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/model/ContainerPort.java
  49. 72 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/model/CpuQuantity.java
  50. 141 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/model/Deployment.java
  51. 65 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/model/DeploymentStatus.java
  52. 53 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/model/HttpGetAction.java
  53. 150 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/model/Ingress.java
  54. 84 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/model/K8sAbstractObject.java
  55. 42 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/model/K8sObject.java
  56. 24 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/model/K8sObjectRequest.java
  57. 6 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/model/K8sObjectTransformer.java
  58. 170 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/model/LabelSelector.java
  59. 34 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/model/Namespace.java
  60. 48 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/model/Pod.java
  61. 10 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/model/PodStatus.java
  62. 67 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/model/Service.java
  63. 51 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/model/ServicePort.java
  64. 41 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/util/SecretTypeEnum.java
  65. 46 0
      src/main/java/cn/seecoder/paas/service/facade/k8s/vo/SecretVO.java
  66. 73 0
      src/main/java/cn/seecoder/paas/service/impl/ApplicationServiceImpl.java
  67. 40 0
      src/main/java/cn/seecoder/paas/service/impl/ConfigServiceImpl.java
  68. 373 0
      src/main/java/cn/seecoder/paas/service/impl/EnvironmentServiceImpl.java
  69. 20 0
      src/main/java/cn/seecoder/paas/service/model/converter/ApplicationConverter.java
  70. 34 0
      src/main/java/cn/seecoder/paas/service/model/converter/ConfigConverter.java
  71. 21 0
      src/main/java/cn/seecoder/paas/service/model/converter/EnvironmentConverter.java
  72. 22 0
      src/main/java/cn/seecoder/paas/service/model/vo/ApplicationVO.java
  73. 86 0
      src/main/java/cn/seecoder/paas/service/model/vo/ConfigVO.java
  74. 36 0
      src/main/java/cn/seecoder/paas/service/model/vo/EnvironmentVO.java
  75. 55 0
      src/main/java/cn/seecoder/paas/util/ApplicationProperties.java
  76. 26 0
      src/main/java/cn/seecoder/paas/util/AsyncConfig.java
  77. 34 0
      src/main/java/cn/seecoder/paas/util/AsyncWrapper.java
  78. 18 0
      src/main/java/cn/seecoder/paas/util/DateUtil.java
  79. 114 0
      src/main/java/cn/seecoder/paas/util/K8sApiConfiguration.java
  80. 42 0
      src/main/java/cn/seecoder/paas/util/LoggerUtil.java
  81. 27 0
      src/main/java/cn/seecoder/paas/util/ServiceException.java
  82. 28 0
      src/main/java/cn/seecoder/paas/util/enums/BuildStatus.java
  83. 27 0
      src/main/java/cn/seecoder/paas/util/enums/BuildType.java
  84. 27 0
      src/main/java/cn/seecoder/paas/util/enums/ConfigBelongsToType.java
  85. 37 0
      src/main/java/cn/seecoder/paas/util/enums/ResourceLabel.java
  86. 44 0
      src/main/java/cn/seecoder/paas/web/CustomerExceptionHandler.java
  87. 15 0
      src/main/java/cn/seecoder/paas/web/WebSecurityConfig.java
  88. 46 0
      src/main/java/cn/seecoder/paas/web/controller/api/ApplicationController.java
  89. 28 0
      src/main/java/cn/seecoder/paas/web/controller/api/ConfigController.java
  90. 60 0
      src/main/java/cn/seecoder/paas/web/controller/api/EnvironmentController.java
  91. 38 0
      src/main/java/cn/seecoder/paas/web/model/Response.java
  92. 36 0
      src/main/resources/application.yml
  93. 8 0
      src/main/resources/docker-config.json
  94. 13 0
      src/test/java/cn/seecoder/paas/SeecoderPaasApplicationTests.java

+ 35 - 0
.gitignore

@@ -0,0 +1,35 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+*.log
+

+ 118 - 0
.mvn/wrapper/MavenWrapperDownloader.java

@@ -0,0 +1,118 @@
+/*
+ * Copyright 2007-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.net.*;
+import java.io.*;
+import java.nio.channels.*;
+import java.util.Properties;
+
+public class MavenWrapperDownloader {
+
+    private static final String WRAPPER_VERSION = "0.5.6";
+    /**
+     * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
+     */
+    private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
+            + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
+
+    /**
+     * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
+     * use instead of the default one.
+     */
+    private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
+            ".mvn/wrapper/maven-wrapper.properties";
+
+    /**
+     * Path where the maven-wrapper.jar will be saved to.
+     */
+    private static final String MAVEN_WRAPPER_JAR_PATH =
+            ".mvn/wrapper/maven-wrapper.jar";
+
+    /**
+     * Name of the property which should be used to override the default download url for the wrapper.
+     */
+    private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
+
+    public static void main(String args[]) {
+        System.out.println("- Downloader started");
+        File baseDirectory = new File(args[0]);
+        System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
+
+        // If the maven-wrapper.properties exists, read it and check if it contains a custom
+        // wrapperUrl parameter.
+        File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
+        String url = DEFAULT_DOWNLOAD_URL;
+        if (mavenWrapperPropertyFile.exists()) {
+            FileInputStream mavenWrapperPropertyFileInputStream = null;
+            try {
+                mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
+                Properties mavenWrapperProperties = new Properties();
+                mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
+                url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
+            } catch (IOException e) {
+                System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
+            } finally {
+                try {
+                    if (mavenWrapperPropertyFileInputStream != null) {
+                        mavenWrapperPropertyFileInputStream.close();
+                    }
+                } catch (IOException e) {
+                    // Ignore ...
+                }
+            }
+        }
+        System.out.println("- Downloading from: " + url);
+
+        File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
+        if (!outputFile.getParentFile().exists()) {
+            if (!outputFile.getParentFile().mkdirs()) {
+                System.out.println(
+                        "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
+            }
+        }
+        System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
+        try {
+            downloadFileFromURL(url, outputFile);
+            System.out.println("Done");
+            System.exit(0);
+        } catch (Throwable e) {
+            System.out.println("- Error downloading");
+            e.printStackTrace();
+            System.exit(1);
+        }
+    }
+
+    private static void downloadFileFromURL(String urlString, File destination) throws Exception {
+        if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
+            String username = System.getenv("MVNW_USERNAME");
+            char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
+            Authenticator.setDefault(new Authenticator() {
+                @Override
+                protected PasswordAuthentication getPasswordAuthentication() {
+                    return new PasswordAuthentication(username, password);
+                }
+            });
+        }
+        URL website = new URL(urlString);
+        ReadableByteChannel rbc;
+        rbc = Channels.newChannel(website.openStream());
+        FileOutputStream fos = new FileOutputStream(destination);
+        fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
+        fos.close();
+        rbc.close();
+    }
+
+}

BIN
.mvn/wrapper/maven-wrapper.jar


+ 13 - 0
Dockerfile

@@ -0,0 +1,13 @@
+FROM maven:3.6.1 AS compile_stage
+ENV PROJECT_NAME seecoder-paas
+ENV WORK_PATH /opt/$PROJECT_NAME
+COPY settings.xml /root/.m2/settings.xml
+COPY . $WORK_PATH
+RUN cd $WORK_PATH && mvn clean package -DskipTests -DfinalName=$PROJECT_NAME
+
+FROM java:8-jre-alpine
+ENV PROJECT_NAME seecoder-paas
+ENV WORK_PATH /opt/$PROJECT_NAME
+WORKDIR /app
+COPY --from=compile_stage $WORK_PATH/target/${PROJECT_NAME}.jar .
+CMD ["sh", "-c", "java -Xdebug -Xrunjdwp:transport=dt_socket,address=8888,server=y,suspend=n -jar -server /app/${PROJECT_NAME}.jar"]

+ 310 - 0
mvnw

@@ -0,0 +1,310 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#    https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+#   JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+#   M2_HOME - location of maven2's installed home dir
+#   MAVEN_OPTS - parameters passed to the Java VM when running Maven
+#     e.g. to debug Maven itself, use
+#       set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+#   MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+  if [ -f /etc/mavenrc ] ; then
+    . /etc/mavenrc
+  fi
+
+  if [ -f "$HOME/.mavenrc" ] ; then
+    . "$HOME/.mavenrc"
+  fi
+
+fi
+
+# OS specific support.  $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+  CYGWIN*) cygwin=true ;;
+  MINGW*) mingw=true;;
+  Darwin*) darwin=true
+    # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+    # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+    if [ -z "$JAVA_HOME" ]; then
+      if [ -x "/usr/libexec/java_home" ]; then
+        export JAVA_HOME="`/usr/libexec/java_home`"
+      else
+        export JAVA_HOME="/Library/Java/Home"
+      fi
+    fi
+    ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+  if [ -r /etc/gentoo-release ] ; then
+    JAVA_HOME=`java-config --jre-home`
+  fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+  ## resolve links - $0 may be a link to maven's home
+  PRG="$0"
+
+  # need this for relative symlinks
+  while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+      PRG="$link"
+    else
+      PRG="`dirname "$PRG"`/$link"
+    fi
+  done
+
+  saveddir=`pwd`
+
+  M2_HOME=`dirname "$PRG"`/..
+
+  # make it fully qualified
+  M2_HOME=`cd "$M2_HOME" && pwd`
+
+  cd "$saveddir"
+  # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+  [ -n "$M2_HOME" ] &&
+    M2_HOME=`cygpath --unix "$M2_HOME"`
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+  [ -n "$CLASSPATH" ] &&
+    CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+  [ -n "$M2_HOME" ] &&
+    M2_HOME="`(cd "$M2_HOME"; pwd)`"
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+  javaExecutable="`which javac`"
+  if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+    # readlink(1) is not available as standard on Solaris 10.
+    readLink=`which readlink`
+    if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+      if $darwin ; then
+        javaHome="`dirname \"$javaExecutable\"`"
+        javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+      else
+        javaExecutable="`readlink -f \"$javaExecutable\"`"
+      fi
+      javaHome="`dirname \"$javaExecutable\"`"
+      javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+      JAVA_HOME="$javaHome"
+      export JAVA_HOME
+    fi
+  fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+  if [ -n "$JAVA_HOME"  ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+      # IBM's JDK on AIX uses strange locations for the executables
+      JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+      JAVACMD="$JAVA_HOME/bin/java"
+    fi
+  else
+    JAVACMD="`which java`"
+  fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+  echo "Error: JAVA_HOME is not defined correctly." >&2
+  echo "  We cannot execute $JAVACMD" >&2
+  exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+  echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+  if [ -z "$1" ]
+  then
+    echo "Path not specified to find_maven_basedir"
+    return 1
+  fi
+
+  basedir="$1"
+  wdir="$1"
+  while [ "$wdir" != '/' ] ; do
+    if [ -d "$wdir"/.mvn ] ; then
+      basedir=$wdir
+      break
+    fi
+    # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+    if [ -d "${wdir}" ]; then
+      wdir=`cd "$wdir/.."; pwd`
+    fi
+    # end of workaround
+  done
+  echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+  if [ -f "$1" ]; then
+    echo "$(tr -s '\n' ' ' < "$1")"
+  fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+  exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+    if [ "$MVNW_VERBOSE" = true ]; then
+      echo "Found .mvn/wrapper/maven-wrapper.jar"
+    fi
+else
+    if [ "$MVNW_VERBOSE" = true ]; then
+      echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+    fi
+    if [ -n "$MVNW_REPOURL" ]; then
+      jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+    else
+      jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+    fi
+    while IFS="=" read key value; do
+      case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+      esac
+    done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+    if [ "$MVNW_VERBOSE" = true ]; then
+      echo "Downloading from: $jarUrl"
+    fi
+    wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+    if $cygwin; then
+      wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+    fi
+
+    if command -v wget > /dev/null; then
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Found wget ... using wget"
+        fi
+        if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+            wget "$jarUrl" -O "$wrapperJarPath"
+        else
+            wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
+        fi
+    elif command -v curl > /dev/null; then
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Found curl ... using curl"
+        fi
+        if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+            curl -o "$wrapperJarPath" "$jarUrl" -f
+        else
+            curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+        fi
+
+    else
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Falling back to using Java to download"
+        fi
+        javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+        # For Cygwin, switch paths to Windows format before running javac
+        if $cygwin; then
+          javaClass=`cygpath --path --windows "$javaClass"`
+        fi
+        if [ -e "$javaClass" ]; then
+            if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+                if [ "$MVNW_VERBOSE" = true ]; then
+                  echo " - Compiling MavenWrapperDownloader.java ..."
+                fi
+                # Compiling the Java class
+                ("$JAVA_HOME/bin/javac" "$javaClass")
+            fi
+            if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+                # Running the downloader
+                if [ "$MVNW_VERBOSE" = true ]; then
+                  echo " - Running MavenWrapperDownloader.java ..."
+                fi
+                ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+            fi
+        fi
+    fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+  echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+  [ -n "$M2_HOME" ] &&
+    M2_HOME=`cygpath --path --windows "$M2_HOME"`
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+  [ -n "$CLASSPATH" ] &&
+    CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+  [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+    MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+  $MAVEN_OPTS \
+  -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+  "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+  ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

+ 182 - 0
mvnw.cmd

@@ -0,0 +1,182 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements.  See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership.  The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License.  You may obtain a copy of the License at
+@REM
+@REM    https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied.  See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM     e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on"  echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+
+FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+    IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Found %WRAPPER_JAR%
+    )
+) else (
+    if not "%MVNW_REPOURL%" == "" (
+        SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+    )
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Couldn't find %WRAPPER_JAR%, downloading it ...
+        echo Downloading from: %DOWNLOAD_URL%
+    )
+
+    powershell -Command "&{"^
+		"$webclient = new-object System.Net.WebClient;"^
+		"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+		"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+		"}"^
+		"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+		"}"
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Finished downloading %WRAPPER_JAR%
+    )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%

+ 112 - 0
pom.xml

@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.3.4.RELEASE</version>
+        <relativePath/> <!-- lookup parent from repository -->
+    </parent>
+    <groupId>cn.seecoder</groupId>
+    <artifactId>seecoder-paas</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>seecoder-paas</name>
+    <description>seecoder paas platform</description>
+
+    <properties>
+        <java.version>1.8</java.version>
+        <finalName>${project.artifactId}-${project.version}</finalName>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-jpa</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-security</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.junit.vintage</groupId>
+                    <artifactId>junit-vintage-engine</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <!-- https://mvnrepository.com/artifact/com.spotify/docker-client -->
+        <dependency>
+            <groupId>com.spotify</groupId>
+            <artifactId>docker-client</artifactId>
+            <version>8.16.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jersey.inject</groupId>
+            <artifactId>jersey-hk2</artifactId>
+            <version>2.26</version>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jersey.bundles.repackaged</groupId>
+            <artifactId>jersey-guava</artifactId>
+            <version>2.25.1</version>
+        </dependency>
+        <!-- https://mvnrepository.com/artifact/io.kubernetes/client-java -->
+        <dependency>
+            <groupId>io.kubernetes</groupId>
+            <artifactId>client-java</artifactId>
+            <version>10.0.0</version>
+        </dependency>
+        <!-- https://mvnrepository.com/artifact/org.eclipse.jgit/org.eclipse.jgit -->
+        <dependency>
+            <groupId>org.eclipse.jgit</groupId>
+            <artifactId>org.eclipse.jgit</artifactId>
+            <version>5.9.0.202009080501-r</version>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <finalName>${finalName}</finalName>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 13 - 0
settings.xml

@@ -0,0 +1,13 @@
+<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
+		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
+					http://maven.apache.org/xsd/settings-1.0.0.xsd">
+	<mirrors>
+		<mirror>
+			<id>nexus-aliyun</id>
+			<mirrorOf>central</mirrorOf>
+			<name>Nexus aliyun</name>
+			<url>http://maven.aliyun.com/nexus/content/groups/public</url>
+		</mirror>
+	</mirrors>
+</settings>

+ 15 - 0
src/main/java/cn/seecoder/paas/SeecoderPaasApplication.java

@@ -0,0 +1,15 @@
+package cn.seecoder.paas;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+
+@SpringBootApplication
+@EnableJpaRepositories(basePackages = "cn.seecoder")
+public class SeecoderPaasApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(SeecoderPaasApplication.class, args);
+    }
+
+}

+ 9 - 0
src/main/java/cn/seecoder/paas/data/dao/ApplicationDAO.java

@@ -0,0 +1,9 @@
+package cn.seecoder.paas.data.dao;
+
+import cn.seecoder.paas.data.entity.Application;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface ApplicationDAO extends JpaRepository<Application, Integer> {
+}

+ 14 - 0
src/main/java/cn/seecoder/paas/data/dao/ConfigDAO.java

@@ -0,0 +1,14 @@
+package cn.seecoder.paas.data.dao;
+
+import cn.seecoder.paas.data.entity.Config;
+import cn.seecoder.paas.util.enums.ConfigBelongsToType;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface ConfigDAO extends JpaRepository<Config, Integer> {
+
+    Config findByConfigBelongsAndAndEntityId(ConfigBelongsToType configBelongs, Integer entityId);
+
+    void deleteByConfigBelongsAndAndEntityId(ConfigBelongsToType configBelongs, Integer entityId);
+}

+ 14 - 0
src/main/java/cn/seecoder/paas/data/dao/EnvironmentDAO.java

@@ -0,0 +1,14 @@
+package cn.seecoder.paas.data.dao;
+
+import cn.seecoder.paas.data.entity.Environment;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+public interface EnvironmentDAO extends JpaRepository<Environment, Integer> {
+
+    List<Environment> findAllByAppId(Integer appId);
+
+}

+ 33 - 0
src/main/java/cn/seecoder/paas/data/entity/Application.java

@@ -0,0 +1,33 @@
+package cn.seecoder.paas.data.entity;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.persistence.*;
+
+@Data
+@ToString
+@EqualsAndHashCode
+@Entity
+@Table(name = "application")
+public class Application {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "id")
+    private Integer id;
+
+    @Basic
+    @Column(name = "name", nullable = false)
+    private String name;
+
+    @Basic
+    @Column(name = "description", nullable = false)
+    private String description;
+
+    @Basic
+    @Column(name = "git_url", nullable = false)
+    private String gitUrl;
+
+}

+ 43 - 0
src/main/java/cn/seecoder/paas/data/entity/Config.java

@@ -0,0 +1,43 @@
+package cn.seecoder.paas.data.entity;
+
+import cn.seecoder.paas.util.enums.ConfigBelongsToType;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.persistence.*;
+
+@Data
+@ToString
+@EqualsAndHashCode
+@Entity
+@Table(name = "config")
+public class Config {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "id")
+    private Integer id;
+
+    /**
+     * 表明配置所属的对象
+     */
+    @Basic
+    @Enumerated
+    @Column(name = "config_belongs")
+    private ConfigBelongsToType configBelongs;
+
+    /**
+     * 所属对象的id
+     */
+    @Basic
+    @Column(name = "entity_id")
+    private Integer entityId;
+
+    /**
+     * 配置内容,JSON格式
+     */
+    @Basic
+    @Column(name = "content", columnDefinition = "MEDIUMTEXT")
+    private String content;
+}

+ 61 - 0
src/main/java/cn/seecoder/paas/data/entity/Environment.java

@@ -0,0 +1,61 @@
+package cn.seecoder.paas.data.entity;
+
+import cn.seecoder.paas.util.enums.BuildStatus;
+import cn.seecoder.paas.util.enums.BuildType;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.hibernate.annotations.DynamicUpdate;
+import org.springframework.data.annotation.CreatedDate;
+import org.springframework.data.annotation.LastModifiedDate;
+
+import javax.persistence.*;
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "environment")
+@ToString
+@EqualsAndHashCode
+@DynamicUpdate
+@Data
+public class Environment {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "id")
+    private Integer id;
+
+    @Basic
+    @Column(name = "name")
+    private String name;
+
+    @Basic
+    @Column(name = "app_id")
+    private Integer appId;
+
+    @Basic
+    @Enumerated
+    @Column(name = "build_type")
+    private BuildType buildType;
+
+    @Basic
+    @Column(name = "build_type_value")
+    private String buildTypeValue;
+
+    @Basic
+    @Column(name = "build_output", columnDefinition = "MEDIUMTEXT")
+    private String buildOutput;
+
+    @CreatedDate
+    @Column(name = "created_at")
+    private LocalDateTime createdAt;
+
+    @LastModifiedDate
+    @Column(name = "modified_at")
+    private LocalDateTime modifiedAt;
+
+    @Basic
+    @Column(name = "build_status")
+    private BuildStatus buildStatus = BuildStatus.NOT_STARTED;
+
+}

+ 17 - 0
src/main/java/cn/seecoder/paas/service/ApplicationService.java

@@ -0,0 +1,17 @@
+package cn.seecoder.paas.service;
+
+import cn.seecoder.paas.service.model.vo.ApplicationVO;
+import cn.seecoder.paas.util.ServiceException;
+
+import java.util.List;
+
+public interface ApplicationService {
+
+    ApplicationVO createOrUpdate(ApplicationVO vo) throws ServiceException;
+
+    void delete(Integer id);
+
+    List<ApplicationVO> getAll();
+
+    ApplicationVO getDetailById(Integer id) throws ServiceException;
+}

+ 10 - 0
src/main/java/cn/seecoder/paas/service/ConfigService.java

@@ -0,0 +1,10 @@
+package cn.seecoder.paas.service;
+
+import cn.seecoder.paas.service.model.vo.ConfigVO;
+import cn.seecoder.paas.util.ServiceException;
+
+public interface ConfigService {
+
+    ConfigVO createOrUpdate(ConfigVO configVO) throws ServiceException;
+
+}

+ 22 - 0
src/main/java/cn/seecoder/paas/service/EnvironmentService.java

@@ -0,0 +1,22 @@
+package cn.seecoder.paas.service;
+
+import cn.seecoder.paas.service.model.vo.EnvironmentVO;
+import cn.seecoder.paas.util.ServiceException;
+
+import java.util.List;
+import java.util.Map;
+
+public interface EnvironmentService {
+
+    EnvironmentVO createOrUpdate(EnvironmentVO environmentVO) throws ServiceException;
+
+    void delete(Integer id);
+
+    EnvironmentVO get(Integer id) throws ServiceException;
+
+    List<EnvironmentVO> getListByAppId(Integer appId);
+
+    EnvironmentVO deploy(Integer id) throws ServiceException;
+
+    Map<String,String> getLog(Integer id, String podName) throws ServiceException;
+}

+ 16 - 0
src/main/java/cn/seecoder/paas/service/facade/docker/DockerApi.java

@@ -0,0 +1,16 @@
+package cn.seecoder.paas.service.facade.docker;
+
+import com.spotify.docker.client.ProgressHandler;
+import com.spotify.docker.client.messages.Image;
+import java.util.List;
+
+public interface DockerApi {
+
+    void buildAndPush(String directory, String imageName, String imageTag, ProgressHandler handler) throws Exception;
+
+    void buildAndPush(String directory, String imageName, String imageTag) throws Exception;
+
+    List<Image> listImage();
+
+    void delete(String imageId) throws Exception;
+}

+ 90 - 0
src/main/java/cn/seecoder/paas/service/facade/docker/impl/DockerApiImpl.java

@@ -0,0 +1,90 @@
+package cn.seecoder.paas.service.facade.docker.impl;
+
+import cn.seecoder.paas.service.facade.docker.DockerApi;
+import cn.seecoder.paas.util.ApplicationProperties;
+import com.spotify.docker.client.DefaultDockerClient;
+import com.spotify.docker.client.DockerClient;
+import com.spotify.docker.client.DockerConfigReader;
+import com.spotify.docker.client.ProgressHandler;
+import com.spotify.docker.client.auth.ConfigFileRegistryAuthSupplier;
+import com.spotify.docker.client.auth.RegistryAuthSupplier;
+import com.spotify.docker.client.exceptions.DockerException;
+import com.spotify.docker.client.messages.Image;
+import com.spotify.docker.client.messages.ProgressMessage;
+import lombok.extern.apachecommons.CommonsLog;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.system.ApplicationHome;
+import org.springframework.stereotype.Component;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+
+@Component
+@CommonsLog
+public class DockerApiImpl implements DockerApi {
+
+    private final DockerClient client;
+    private final ProgressHandler defaultHandler;
+    private final RegistryAuthSupplier registryAuthSupplier;
+    private final String registry;
+
+    @Autowired
+    public DockerApiImpl(ApplicationProperties properties) throws IOException {
+        registry = properties.getDocker().getRegistry();
+        client = new DefaultDockerClient(properties.getDocker().getHost());
+        defaultHandler = new NoActionProgressHandler();
+        ApplicationHome applicationHome = new ApplicationHome(getClass());
+        Path path = Paths.get(applicationHome.getDir().getAbsolutePath(), "docker-config.json");
+        registryAuthSupplier = new ConfigFileRegistryAuthSupplier(new DockerConfigReader(), path);
+    }
+
+    @Override
+    public void buildAndPush(String directory, String imageName, String imageTag, ProgressHandler handler) throws Exception {
+        String imageId = client.build(new File(directory).toPath(), handler, DockerClient.BuildParam.name(registry + "/" + imageName + ":" + imageTag), DockerClient.BuildParam.forceRm());
+        if (imageId == null) {
+            throw new IOException("Failed to build docker image under [" + directory + "]");
+        }
+        client.push(registry + "/" + imageName + ":" + imageTag, new LogActionProgressHandler(), registryAuthSupplier.authFor(registry + "/" + imageName + ":" + imageTag));
+
+    }
+
+    @Override
+    public void buildAndPush(String directory, String imageName, String imageTag) throws Exception {
+        buildAndPush(directory, imageName, imageTag, defaultHandler);
+    }
+
+    public List<Image> listImage() {
+        List<Image> images = new ArrayList<>();
+        try {
+            images = client.listImages(DockerClient.ListImagesParam.allImages());
+        } catch (DockerException e) {
+            e.printStackTrace();
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+        return images;
+    }
+
+    public void delete(String imageId) throws DockerException, InterruptedException {
+        client.removeImage(imageId);
+    }
+
+    private class NoActionProgressHandler implements ProgressHandler {
+        @Override
+        public void progress(ProgressMessage message) {
+            //IGNORE THE MESSAGE
+        }
+    }
+
+    private class LogActionProgressHandler implements ProgressHandler {
+        @Override
+        public void progress(ProgressMessage message) {
+//            if (message.error() != null)
+//                log.error(message.error());
+        }
+    }
+}

+ 9 - 0
src/main/java/cn/seecoder/paas/service/facade/git/GitApi.java

@@ -0,0 +1,9 @@
+package cn.seecoder.paas.service.facade.git;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.GitAPIException;
+
+public interface GitApi {
+
+    Git clone(String repoUrl, String directory) throws Exception;
+}

+ 32 - 0
src/main/java/cn/seecoder/paas/service/facade/git/impl/GitApiImpl.java

@@ -0,0 +1,32 @@
+package cn.seecoder.paas.service.facade.git.impl;
+
+import cn.seecoder.paas.service.facade.git.GitApi;
+import cn.seecoder.paas.util.ApplicationProperties;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.transport.CredentialsProvider;
+import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.io.File;
+
+@Component
+public class GitApiImpl implements GitApi {
+
+    private CredentialsProvider credentialsProvider;
+
+    @Autowired
+    public GitApiImpl(ApplicationProperties properties) {
+        ApplicationProperties.Git gitProperties = properties.getGit();
+        credentialsProvider = new UsernamePasswordCredentialsProvider(gitProperties.getUsername(), gitProperties.getPassword());
+    }
+
+    @Override
+    public Git clone(String repoUrl, String directory) throws Exception {
+        Git git = Git.cloneRepository()
+                .setCredentialsProvider(credentialsProvider)
+                .setURI(repoUrl)
+                .setDirectory(new File(directory)).call();
+        return git;
+    }
+}

+ 17 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/AbstractApi.java

@@ -0,0 +1,17 @@
+package cn.seecoder.paas.service.facade.k8s;
+
+import cn.seecoder.paas.service.facade.k8s.model.K8sAbstractObject;
+import cn.seecoder.paas.service.facade.k8s.model.K8sObjectRequest;
+
+import java.util.List;
+
+public interface AbstractApi<T extends K8sAbstractObject> {
+
+    T create(T t);
+
+    T update(T t);
+
+    void delete(T t);
+
+    List<T> getByCondition(K8sObjectRequest request);
+}

+ 6 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/ConfigMapApi.java

@@ -0,0 +1,6 @@
+package cn.seecoder.paas.service.facade.k8s;
+
+import cn.seecoder.paas.service.facade.k8s.model.ConfigMap;
+
+public interface ConfigMapApi extends AbstractApi<ConfigMap> {
+}

+ 19 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/DeploymentApi.java

@@ -0,0 +1,19 @@
+package cn.seecoder.paas.service.facade.k8s;
+
+import cn.seecoder.paas.service.facade.k8s.model.Deployment;
+import cn.seecoder.paas.service.facade.k8s.exception.K8sApiException;
+import cn.seecoder.paas.service.facade.k8s.model.DeploymentStatus;
+
+/**
+ * author: rale
+ * createdAt: 12/23/18
+ */
+public interface DeploymentApi extends AbstractApi<Deployment>{
+
+    /**
+     * 查询deployment的状态
+     * @return 如果该deployment不存在,则返回null
+     * @throws K8sApiException SYSTEM_ERROR 系统异常
+     */
+    DeploymentStatus getStatus(String namespace, String name);
+}

+ 37 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/ExecApi.java

@@ -0,0 +1,37 @@
+package cn.seecoder.paas.service.facade.k8s;
+
+
+import cn.seecoder.paas.service.facade.k8s.exception.K8sApiException;
+
+import java.util.List;
+
+/**
+ * 进入容器执行指令
+ *
+ * @todo 传入输入流,获得输出流
+ */
+public interface ExecApi {
+
+    /**
+     * 选择namespace下podName中的containerName的容器执行commands指令
+     * @param namespace
+     * @param podName
+     * @param containerName
+     * @param commands
+     * @return exitCode=0 则返回true,其它情况返回false
+     * @throws K8sApiException NOT_FOUND 命名空间/pod/container不存在
+     * @throws K8sApiException SYSTEM_ERROR 系统异常
+     */
+    boolean exec(String namespace, String podName, String containerName, List<String> commands);
+
+    /**
+     * 当且仅当pod下只有一个container时,该指令才会执行成功,否则会抛出异常
+     * @param namespace
+     * @param podName
+     * @param commands
+     * @return exitCode=0 则返回true,其它情况返回false
+     * @throws K8sApiException NOT_FOUND 命名空间不存在/pod不存在/pod下没有container
+     * @throws K8sApiException SYSTEM_ERROR pod存在多个container/系统异常/指令执行异常
+     */
+    boolean exec(String namespace, String podName, List<String> commands);
+}

+ 13 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/IngressApi.java

@@ -0,0 +1,13 @@
+package cn.seecoder.paas.service.facade.k8s;
+
+
+import cn.seecoder.paas.service.facade.k8s.model.Ingress;
+import cn.seecoder.paas.service.facade.k8s.exception.K8sApiException;
+
+/**
+ * author: rale
+ * createdAt: 12/24/18
+ */
+public interface IngressApi extends AbstractApi<Ingress>{
+    
+}

+ 46 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/K8sConstants.java

@@ -0,0 +1,46 @@
+package cn.seecoder.paas.service.facade.k8s;
+
+public class K8sConstants {
+
+    public static final String NAME_PATTERN = "^[0-9a-zA-Z-]{8,}$";
+
+    //标签域名前缀
+    private static final String LABEL_PREFIX = "demo.seec.nju.cn";
+    //课程前缀
+    public static final String TYPE_LABEL = LABEL_PREFIX + "/type";
+    //描述
+    public static final String DESCRIPTION_LABEL = LABEL_PREFIX + "/description";
+    //应用
+    public static final String APPLICATION_LABEL = LABEL_PREFIX + "/app";
+
+    public static final String DEPLOY_ID_LABEL = LABEL_PREFIX + "/deployid";
+    // 用户namespace的label
+    public static final String USERSPACE_LABEL = LABEL_PREFIX +"/userspace";
+    // pod的label
+    public static final String SELECTOR_LABEL = LABEL_PREFIX + "/app";
+    // 非对外service的label
+    public static final String INTERNAL_LABEL = LABEL_PREFIX + "/internal";
+    // 对外service的label
+    public static final String EXTERNAL_LABEL = LABEL_PREFIX + "/external";
+    // 持久卷访问模式
+    public static final String STORAGE_ACCESS_MODE = "ReadWriteMany";
+    // 持久卷存储类型
+    public static final String STORAGE_CLASS = "glusterfs";
+
+    public static final String RESOURCE_STORAGE = "storage";
+
+    public static final String STARTS_AT_LABEL = LABEL_PREFIX + "/start";
+
+    public static final String ENDS_AT_LABEL = LABEL_PREFIX + "/end";
+
+    public static final String PRETTY_FORMAT = "true";
+
+    //cascade清除策略
+    //Deployment必须使用Foreground,否则只会删除Replica Set 不会删除底下的Pod
+    //https://github.com/kubernetes/kubeadm/issues/149#issuecomment-284766613
+    public static final String FOREGROUND_PROPAGATION_POLICY = "Foreground";
+
+    public static final String METADATA_NAME_SELECTOR = "metadata.name=";
+
+    public static final String DB_SECRET_NAME = "db-secret";
+}

+ 85 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/LogApi.java

@@ -0,0 +1,85 @@
+package cn.seecoder.paas.service.facade.k8s;
+
+import java.time.LocalDateTime;
+
+/**
+ * author: rale
+ * createdAt: 1/6/19
+ */
+public interface LogApi{
+
+    /**
+     * 获取namespace下pod中名为containerName的日志
+     * @param namespace
+     * @param pod
+     * @param containerName
+     * @return
+     */
+    String getAll(String namespace, String pod, String containerName);
+
+    /**
+     * 只适用于pod中仅有单个container的场景,它将会返回该container的日志
+     * 如果pod中有多个container,将抛出异常
+     * @param namespace
+     * @param pod
+     * @return
+     */
+    String getAll(String namespace, String pod);
+
+    /**
+     * 获取最后N条日志
+     * @param namespace
+     * @param pod
+     * @param containerName
+     * @param N
+     * @return
+     */
+    String getLastNLines(String namespace, String pod, String containerName, int N);
+
+    /**
+     * 获取最后N条日志
+     * @param namespace
+     * @param pod
+     * @param N
+     * @return
+     */
+    String getLastNLines(String namespace, String pod, int N);
+
+    /**
+     * 获取至今N秒内的日志
+     * @param namespace
+     * @param pod
+     * @param N
+     * @return
+     */
+    String getBeforeNSeconds(String namespace,String pod, String containerName, int N);
+
+    /**
+     * 获取至今N秒内的日志
+     * @param namespace
+     * @param pod
+     * @param N
+     * @return
+     */
+    String getBeforeNSeconds(String namespace, String pod, int N);
+
+    /**
+     * 获取time时间点之后namespace下pod中container的日志
+     * @param namespace
+     * @param pod
+     * @param container
+     * @param time
+     * @return
+     */
+    String getAfterTime(String namespace, String pod, String container, LocalDateTime time);
+
+    /**
+     * 获取time时间点之后namespace下pod中的日志
+     * 仅适用于pod中仅有一个容器
+     * @param namespace
+     * @param pod
+     * @param time
+     * @return
+     */
+    String getAfterTime(String namespace, String pod, LocalDateTime time);
+}

+ 7 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/NamespaceApi.java

@@ -0,0 +1,7 @@
+package cn.seecoder.paas.service.facade.k8s;
+
+import cn.seecoder.paas.service.facade.k8s.model.Namespace;
+
+public interface NamespaceApi extends AbstractApi<Namespace> {
+
+}

+ 6 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/PodApi.java

@@ -0,0 +1,6 @@
+package cn.seecoder.paas.service.facade.k8s;
+
+import cn.seecoder.paas.service.facade.k8s.model.Pod;
+
+public interface PodApi extends AbstractApi<Pod>{
+}

+ 82 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/SecretApi.java

@@ -0,0 +1,82 @@
+package cn.seecoder.paas.service.facade.k8s;
+
+import cn.seecoder.paas.service.facade.k8s.util.SecretTypeEnum;
+import cn.seecoder.paas.service.facade.k8s.vo.SecretVO;
+
+import java.util.List;
+import java.util.Map;
+
+public interface SecretApi {
+
+    /**
+     * 创建通用密钥,如数据库的用户名,密码,默认type为Opaque
+     * @param namespace
+     * @param name
+     * @param data
+     * @return ApiResult
+     */
+    SecretVO createGenericSecret(String namespace, String name, Map<String, byte[]> data);
+
+    /**
+     * 创建通用密钥,自己定义Secret的type,如Service Account、Opaque、kubernetes.io/dockerconfigjson.
+     * @param namespace
+     * @param name
+     * @param data
+     * @param type
+     * @return ApiResult
+     */
+    SecretVO createGenericSecret(String namespace, String name, Map<String, byte[]> data, SecretTypeEnum type);
+
+    /**
+     * 在namespace下生成私有仓库server的密钥
+     * 密钥名称将自动生成
+     * @param namespace
+     * @param server
+     * @param user
+     * @param password
+     */
+    SecretVO createPrivateRegistrySecret(String namespace, String server, String user, String password);
+
+
+    /**
+     * 在namespace下生成私有仓库server的密钥
+     * 密钥名称自己指定
+     * @param namespace
+     * @param name
+     * @param server
+     * @param user
+     * @param password
+     */
+    SecretVO createPrivateRegistrySecret(String namespace, String name, String server, String user, String password);
+
+    /**
+     * 获取指定namespace下的Secret列表
+     * @param namespace
+     */
+    List<SecretVO> getSecretList(String namespace);
+
+    /**
+     * 获取指定namespace下的指定type的Secret列表
+     *
+     * @param namespace
+     * @param type
+     */
+    List<SecretVO> getSecretListByType(String namespace, SecretTypeEnum type);
+
+
+    /**
+     * 获取指定namespace下的密钥
+     * 返回指定命名的Secret
+     * @param namespace
+     * @param name
+     */
+    SecretVO getSecretByName(String namespace, String name);
+
+    /**
+     * 删除指定namespace下指定名字的Secret
+     * @param namespace
+     * @param name
+     */
+    void deleteSecretByName(String namespace, String name);
+
+}

+ 8 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/ServiceApi.java

@@ -0,0 +1,8 @@
+package cn.seecoder.paas.service.facade.k8s;
+
+
+import cn.seecoder.paas.service.facade.k8s.model.Service;
+
+public interface ServiceApi extends AbstractApi<Service>{
+
+}

+ 17 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/WatchApi.java

@@ -0,0 +1,17 @@
+package cn.seecoder.paas.service.facade.k8s;
+
+/**
+ * author: rale
+ * createdAt: 1/13/19
+ */
+public interface WatchApi {
+
+    /**
+     * 会监听deployment的状态
+     * 超时时间十分钟
+     */
+    void watchDeployment();
+
+    void watchPod();
+
+}

+ 68 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/exception/K8sApiException.java

@@ -0,0 +1,68 @@
+package cn.seecoder.paas.service.facade.k8s.exception;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class K8sApiException extends RuntimeException {
+
+    private final int code;
+
+    private final String description;
+
+    public static final Map<Integer, String> codeMap = new HashMap<>();
+
+    public static final int NAMESPACE_NOT_EXIST = 400;
+
+    public static final int DEPLOYMENT_NOT_EXIST = 401;
+
+    public static final int NOT_FOUND = 404;
+
+    public static final int RESOURCE_QUOTA_NOT_EXIST = 402;
+
+    public static final int ALREADY_EXIST = 409;
+    //LimitRange的参数配置:default值不大于max的值,default request的值不小于min的值
+    public static final int INVALID_VALUE = 422;
+
+    public static final int BAD_REQUEST = 200;
+
+    public static final int SYSTEM_ERROR = 500;
+
+    public static final int COMMAND_FAIL = 300;
+
+    public static final int NOT_SUPPORT_METHOD = 100;
+
+
+    public static final K8sApiException K8s_SYSTEM_ERROR_EXCEPTION = new K8sApiException(SYSTEM_ERROR);
+
+    public static final K8sApiException K8s_NAMESPACE_NOT_EXIST = new K8sApiException(NAMESPACE_NOT_EXIST);
+
+    public static final K8sApiException K8s_NOT_SUPPORT_METHOD = new K8sApiException(NOT_SUPPORT_METHOD);
+
+    //系统故障
+    static {
+        codeMap.put(NOT_FOUND, "资源不存在");
+        codeMap.put(RESOURCE_QUOTA_NOT_EXIST, "资源限额不存在");
+        codeMap.put(ALREADY_EXIST, "资源已存在");
+        codeMap.put(INVALID_VALUE, "参数设置无效");
+        codeMap.put(BAD_REQUEST, "请求参数非法");
+        codeMap.put(SYSTEM_ERROR, "系统异常");
+        codeMap.put(COMMAND_FAIL, "指令执行失败");
+        codeMap.put(NOT_SUPPORT_METHOD, "不支持的方法");
+    }
+    public K8sApiException(int code, String description){
+        this.code = code;
+        this.description = description;
+    }
+
+    public K8sApiException(int code) {
+        this.code = code;
+        this.description = codeMap.getOrDefault(code, "操作执行失败");
+    }
+
+
+}

+ 171 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/impl/ConfigMapApiImpl.java

@@ -0,0 +1,171 @@
+package cn.seecoder.paas.service.facade.k8s.impl;
+
+import cn.seecoder.paas.service.facade.k8s.ConfigMapApi;
+import cn.seecoder.paas.service.facade.k8s.exception.K8sApiException;
+import cn.seecoder.paas.service.facade.k8s.model.ConfigMap;
+import cn.seecoder.paas.service.facade.k8s.model.K8sObjectRequest;
+import cn.seecoder.paas.service.facade.k8s.model.LabelSelector;
+import cn.seecoder.paas.util.LoggerUtil;
+import com.google.gson.JsonSyntaxException;
+import io.kubernetes.client.openapi.ApiException;
+import io.kubernetes.client.openapi.apis.CoreV1Api;
+import io.kubernetes.client.openapi.models.*;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static cn.seecoder.paas.service.facade.k8s.K8sConstants.FOREGROUND_PROPAGATION_POLICY;
+import static cn.seecoder.paas.service.facade.k8s.K8sConstants.PRETTY_FORMAT;
+import static cn.seecoder.paas.service.facade.k8s.exception.K8sApiException.*;
+
+@Service
+public class ConfigMapApiImpl implements ConfigMapApi {
+
+    private static final Logger logger = LoggerUtil.getLogger(ConfigMapApiImpl.class);
+
+    private CoreV1Api coreV1Api;
+
+    @Autowired
+    public ConfigMapApiImpl(CoreV1Api coreV1Api) {
+        this.coreV1Api = coreV1Api;
+    }
+
+    @Override
+    public ConfigMap create(ConfigMap configMap) {
+        try {
+            V1ConfigMap v1ConfigMap = configMap.toK8sObject();
+            V1ConfigMap result = coreV1Api.createNamespacedConfigMap(configMap.getNamespace(), v1ConfigMap, PRETTY_FORMAT, null, null);
+            return result == null ? null : new ConfigMap(result);
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "configMap创建失败, configMap={}, response={}", configMap, e.getResponseBody());
+
+            if (e.getCode() == NOT_FOUND) {
+                throw new K8sApiException(NOT_FOUND, "namespace不存在");
+            } else if (e.getCode() == ALREADY_EXIST) {
+                throw new K8sApiException(ALREADY_EXIST, "configMap已经存在");
+            } else {
+                throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        } catch (Exception e) {
+            LoggerUtil.error(logger, e, "configMap创建异常, configMap={}", configMap);
+            throw e;
+        }
+    }
+
+    @Override
+    public ConfigMap update(ConfigMap configMap) {
+        try {
+            V1ConfigMap v1ConfigMap = getV1ConfigMap(configMap.getNamespace(), configMap.getName());
+            configMap.merge(v1ConfigMap);
+            V1ConfigMap result = coreV1Api.replaceNamespacedConfigMap(configMap.getName(), configMap.getNamespace(), v1ConfigMap, PRETTY_FORMAT, null, null);
+            return result == null ? null : new ConfigMap(result);
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "configMap更新失败, configMap={}, response={}", configMap, e.getResponseBody());
+
+            if (e.getCode() == NOT_FOUND) {
+                throw new K8sApiException(NOT_FOUND, "namespace不存在");
+            } else if (e.getCode() == ALREADY_EXIST) {
+                throw new K8sApiException(ALREADY_EXIST, "configMap已经存在");
+            } else {
+                throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        } catch (Exception e) {
+            LoggerUtil.error(logger, e, "configMap更新异常, configMap={}", configMap);
+            throw e;
+        }
+    }
+
+    @Override
+    public void delete(ConfigMap configMap) {
+        try {
+            V1DeleteOptions v1DeleteOptions = new V1DeleteOptionsBuilder()
+                    .withApiVersion(ConfigMap.API_VERSION)
+                    .withPropagationPolicy(FOREGROUND_PROPAGATION_POLICY)
+                    .build();
+            coreV1Api.deleteNamespacedConfigMap(
+                    configMap.getName(),
+                    configMap.getNamespace(),
+                    PRETTY_FORMAT,
+                    null,
+                    null,
+                    null,
+                    FOREGROUND_PROPAGATION_POLICY,
+                    v1DeleteOptions);
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "删除ConfigMap失败,namespace={}, name={}, response={}", configMap.getNamespace(), configMap.getName(), e.getResponseBody());
+
+            if (e.getCode() == K8sApiException.NOT_FOUND) {
+                throw new K8sApiException(NOT_FOUND, "configMap不存在");
+            }else {
+                throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        } catch (JsonSyntaxException e) {
+            //do nothing
+        } catch (Exception e) {
+            LoggerUtil.error(logger, e, "删除ConfigMap异常,namespace={}, name={}", configMap.getNamespace(), configMap.getName());
+            throw K8s_SYSTEM_ERROR_EXCEPTION;
+        }
+    }
+
+    @Override
+    public List<ConfigMap> getByCondition(K8sObjectRequest request) {
+        try {
+            if (request.getName() != null) {
+                V1ConfigMap obj = coreV1Api.readNamespacedConfigMap(
+                        request.getName(),
+                        request.getNamespace(),
+                        PRETTY_FORMAT,
+                        null,
+                        null);
+                return obj == null ? Collections.emptyList() : Collections.singletonList(new ConfigMap(obj));
+            }
+            LabelSelector selector = null;
+            if (request.getSelector() != null) {
+                selector = request.getSelector();
+            }
+            if (request.getLabels() != null) {
+                if (selector == null) {
+                    selector = new LabelSelector();
+                }
+                selector.addAll(request.getLabels());
+            }
+            V1ConfigMapList objList = coreV1Api.listNamespacedConfigMap(
+                    request.getNamespace(),
+                    PRETTY_FORMAT,
+                    null,
+                    null,
+                    null,
+                    selector.toJsonString(),
+                    null,
+                    null,
+                    null,
+                    null);
+            return objList.getItems().stream().map(ConfigMap::new).collect(Collectors.toList());
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "查找configMap失败,namespace = {}, response={}",
+                    request.getNamespace(), e.getResponseBody());
+            if (e.getCode() == NOT_FOUND) {
+                throw new K8sApiException(NOT_FOUND);
+            }else {
+                throw K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        } catch (Exception e) {
+            LoggerUtil.error(logger, e, "查找configMap失败,namespace = {}, name={}",
+                    request.getNamespace());
+            throw K8s_SYSTEM_ERROR_EXCEPTION;
+        }
+    }
+
+    private V1ConfigMap getV1ConfigMap(String namespace, String name) throws ApiException {
+        return coreV1Api.readNamespacedConfigMap(
+                name,
+                namespace,
+                PRETTY_FORMAT,
+                false,
+                false);
+    }
+}

+ 203 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/impl/DeploymentApiImpl.java

@@ -0,0 +1,203 @@
+package cn.seecoder.paas.service.facade.k8s.impl;
+
+import cn.seecoder.paas.service.facade.k8s.model.K8sObjectRequest;
+import cn.seecoder.paas.service.facade.k8s.model.LabelSelector;
+import cn.seecoder.paas.util.LoggerUtil;
+import com.google.gson.JsonSyntaxException;
+import cn.seecoder.paas.service.facade.k8s.DeploymentApi;
+import cn.seecoder.paas.service.facade.k8s.exception.K8sApiException;
+import cn.seecoder.paas.service.facade.k8s.model.Deployment;
+import cn.seecoder.paas.service.facade.k8s.model.DeploymentStatus;
+import io.kubernetes.client.openapi.ApiCallback;
+import io.kubernetes.client.openapi.ApiException;
+import io.kubernetes.client.openapi.apis.AppsV1Api;
+import io.kubernetes.client.openapi.models.*;
+
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static cn.seecoder.paas.service.facade.k8s.exception.K8sApiException.ALREADY_EXIST;
+import static cn.seecoder.paas.service.facade.k8s.exception.K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+import static cn.seecoder.paas.service.facade.k8s.exception.K8sApiException.NOT_FOUND;
+import static cn.seecoder.paas.service.facade.k8s.K8sConstants.FOREGROUND_PROPAGATION_POLICY;
+import static cn.seecoder.paas.service.facade.k8s.K8sConstants.PRETTY_FORMAT;
+
+
+/**
+ * author: rale
+ * createdAt: 12/23/18
+ */
+@Service
+public class DeploymentApiImpl implements DeploymentApi {
+
+    private static final Logger logger = LoggerUtil.getLogger(DeploymentApi.class);
+
+    private AppsV1Api appsV1Api;
+
+    @Autowired
+    public DeploymentApiImpl(AppsV1Api appsV1Api) {
+        this.appsV1Api = appsV1Api;
+    }
+
+    /**
+     * 异步创建Deployment
+     * @param deployment
+     * @throws K8sApiException NOT_FOUND namespace不存在
+     * @throws K8sApiException ALREADY_EXIST deployment已经存在
+     * @throws K8sApiException SYSTEM_ERROR 系统异常
+     */
+    @Override
+    public Deployment create(Deployment deployment) {
+        try {
+            V1Deployment v1Deployment = deployment.toK8sObject();
+            V1Deployment result = appsV1Api.createNamespacedDeployment(deployment.getNamespace(),v1Deployment, PRETTY_FORMAT, null, null);
+            return v1Deployment == null ? null : new Deployment(result);
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "deployment创建失败, deployment={}, response={}", deployment, e.getResponseBody());
+
+            if (e.getCode() == NOT_FOUND) {
+                throw new K8sApiException(NOT_FOUND, "namespace不存在");
+            } else if (e.getCode() == ALREADY_EXIST) {
+                throw new K8sApiException(ALREADY_EXIST, "deployment已经存在");
+            } else {
+                throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        } catch (Exception e) {
+            LoggerUtil.error(logger, e, "deployment创建异常, deployment={}", deployment);
+            throw e;
+        }
+    }
+
+    /**
+     * 替换现有的Deployment
+     * @param deployment
+     * @throws K8sApiException NOT_FOUND namespace不存在
+     * @throws K8sApiException SYSTEM_ERROR 系统异常
+     * @return
+     */
+    @Override
+    public Deployment update(Deployment deployment) {
+        try {
+            //replace vs patch
+            //replace方法需要明确所有变量的更新值,包括hostIP等,因此需要执行merge方法来合并更新
+            //patch本身会保有原来的配置,但是会用annotation记录更新的部分进行比较 需要生成patch语句
+            //需继续评估二者的选择
+            V1Deployment v1Deployment = getV1Deployment(deployment.getNamespace(), deployment.getName());
+            deployment.merge(v1Deployment);
+            v1Deployment = appsV1Api.replaceNamespacedDeployment(deployment.getName(), deployment.getNamespace(), v1Deployment, PRETTY_FORMAT, null, null);
+            return new Deployment(v1Deployment);
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "deployment更新异常, deployment={}, response={}", deployment,  e.getResponseBody());
+            if (e.getCode() == NOT_FOUND) {
+                throw new K8sApiException(NOT_FOUND);
+            } else {
+                throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        } catch (Exception e) {
+            LoggerUtil.error(logger, e, "deployment更新异常, deployment={}", deployment);
+            throw K8s_SYSTEM_ERROR_EXCEPTION;
+        }
+    }
+
+    @Override
+    public void delete(Deployment deployment) {
+        try {
+            V1DeleteOptions v1DeleteOptions = new V1DeleteOptionsBuilder()
+                    .withApiVersion(Deployment.API_VERSION)
+                    .withPropagationPolicy(FOREGROUND_PROPAGATION_POLICY)
+                    .build();
+            appsV1Api.deleteNamespacedDeployment(
+                    deployment.getName(),
+                    deployment.getNamespace(),
+                    PRETTY_FORMAT,
+                    null,
+                    null,
+                    null,
+                    FOREGROUND_PROPAGATION_POLICY,
+                    v1DeleteOptions);
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "删除Deployment失败,namespace={}, name={}, response={}", deployment.getNamespace(), deployment.getName(), e.getResponseBody());
+
+            if (e.getCode() == K8sApiException.NOT_FOUND) {
+                throw new K8sApiException(NOT_FOUND, "deployment不存在");
+            }else {
+                throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        } catch (JsonSyntaxException e) {
+            //do nothing
+        } catch (Exception e) {
+            LoggerUtil.error(logger, e, "删除Deployment异常,namespace={}, name={}", deployment.getNamespace(), deployment.getName());
+            throw K8s_SYSTEM_ERROR_EXCEPTION;
+        }
+    }
+
+    @Override
+    public List<Deployment> getByCondition(K8sObjectRequest request) {
+        try {
+            if (request.getName() != null) {
+                V1Deployment obj = appsV1Api.readNamespacedDeployment(
+                        request.getName(),
+                        request.getNamespace(),
+                        PRETTY_FORMAT,
+                        null,
+                        null);
+                return obj == null ? Collections.emptyList() : Collections.singletonList(new Deployment(obj));
+            }
+            LabelSelector selector = null;
+            if (request.getSelector() != null) {
+                selector = request.getSelector();
+            }
+            if (request.getLabels() != null) {
+                if (selector == null) {
+                    selector = new LabelSelector();
+                }
+                selector.addAll(request.getLabels());
+            }
+            V1DeploymentList objList = appsV1Api.listNamespacedDeployment(
+                    request.getNamespace(),
+                    PRETTY_FORMAT,
+                    null,
+                    null,
+                    null,
+                    selector.toJsonString(),
+                    null,
+                    null,
+                    null,
+                    null);
+            return objList.getItems().stream().map(Deployment::new).collect(Collectors.toList());
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "查找deployment失败,namespace = {}, response={}",
+                    request.getNamespace(), e.getResponseBody());
+            if (e.getCode() == NOT_FOUND) {
+                throw new K8sApiException(NOT_FOUND);
+            }else {
+                throw K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        } catch (Exception e) {
+            LoggerUtil.error(logger, e, "查找deployment失败,namespace = {}, name={}",
+                    request.getNamespace());
+            throw K8s_SYSTEM_ERROR_EXCEPTION;
+        }
+    }
+
+
+    @Override
+    public DeploymentStatus getStatus(String namespace, String name) {
+        return null;
+    }
+
+    private V1Deployment getV1Deployment(String namespace, String name) throws ApiException {
+        return appsV1Api.readNamespacedDeployment(
+                name,
+                namespace,
+                PRETTY_FORMAT,
+                false,
+                false);
+    }
+}

+ 70 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/impl/ExecApiImpl.java

@@ -0,0 +1,70 @@
+package cn.seecoder.paas.service.facade.k8s.impl;
+
+import cn.seecoder.paas.service.facade.k8s.ExecApi;
+import cn.seecoder.paas.service.facade.k8s.exception.K8sApiException;
+import cn.seecoder.paas.util.LoggerUtil;
+import io.kubernetes.client.openapi.ApiException;
+import io.kubernetes.client.Exec;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import static cn.seecoder.paas.service.facade.k8s.exception.K8sApiException.NOT_FOUND;
+
+
+@Service
+public class ExecApiImpl implements ExecApi {
+
+    private static final Logger logger = LoggerUtil.getLogger(ExecApiImpl.class);
+
+    private static final int DEFAULT_TIME_OUT_IN_SECOND = 30;
+    private final Exec exec;
+
+    @Autowired
+    public ExecApiImpl(Exec exec) {
+        this.exec = exec;
+    }
+
+    @Override
+    public boolean exec(String namespace, String podName, String containerName, List<String> commands) {
+        return false;
+    }
+
+    @Override
+    public boolean exec(String namespace, String podName, List<String> commands){
+        try {
+            final Process proc =
+                    exec.exec(
+                            namespace,
+                            podName,
+                            commands.isEmpty()
+                                    ? new String[]{"sh"}
+                                    : commands.toArray(new String[commands.size()]),
+                            true,
+                            false);
+            proc.waitFor(DEFAULT_TIME_OUT_IN_SECOND, TimeUnit.SECONDS);
+            proc.destroy();
+            return proc.exitValue() == 0;
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "指令执行异常, namespace={}, podName={}, commands={}, response={}",
+                namespace, podName, commands, e.getResponseBody());
+            if (e.getCode() == NOT_FOUND) {
+                throw new K8sApiException(NOT_FOUND, "容器不存在");
+            }else{
+                throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        } catch (InterruptedException | IOException e) {
+            LoggerUtil.error(logger, e, "指令执行异常, namespace={}, podName={}, commands={}",
+                    namespace, podName, commands);
+            throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+        } catch (Exception e) {
+            LoggerUtil.error(logger, e, "指令执行异常, namespace={}, podName={}, commands={}",
+                    namespace, podName, commands);
+        }
+        return false;
+    }
+}

+ 208 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/impl/IngressApiImpl.java

@@ -0,0 +1,208 @@
+package cn.seecoder.paas.service.facade.k8s.impl;
+
+import cn.seecoder.paas.service.facade.k8s.model.K8sObjectRequest;
+import com.google.gson.JsonSyntaxException;
+import cn.seecoder.paas.service.facade.k8s.IngressApi;
+import cn.seecoder.paas.service.facade.k8s.exception.K8sApiException;
+import cn.seecoder.paas.service.facade.k8s.model.Ingress;
+import cn.seecoder.paas.util.LoggerUtil;
+import io.kubernetes.client.openapi.ApiException;
+import io.kubernetes.client.openapi.apis.ExtensionsV1beta1Api;
+import io.kubernetes.client.openapi.models.*;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static cn.seecoder.paas.service.facade.k8s.exception.K8sApiException.ALREADY_EXIST;
+import static cn.seecoder.paas.service.facade.k8s.exception.K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+import static cn.seecoder.paas.service.facade.k8s.exception.K8sApiException.NOT_FOUND;
+import static cn.seecoder.paas.service.facade.k8s.K8sConstants.FOREGROUND_PROPAGATION_POLICY;
+import static cn.seecoder.paas.service.facade.k8s.K8sConstants.PRETTY_FORMAT;
+
+/**
+ * author: rale
+ * createdAt: 12/24/18
+ */
+@Service
+public class IngressApiImpl implements IngressApi {
+    private static final Logger logger = LoggerUtil.getLogger(IngressApi.class);
+
+    private final ExtensionsV1beta1Api extensionsV1beta1Api;
+
+    @Autowired
+    public IngressApiImpl(ExtensionsV1beta1Api extensionsV1beta1Api){
+        this.extensionsV1beta1Api = extensionsV1beta1Api;
+    }
+
+    /**
+     * 创建Ingress资源
+     * @param ingress
+     * @throws K8sApiException NOT_FOUND 命名空间不存在
+     * @throws K8sApiException ALREADY_EXIST ingress已经存在
+     * @throws K8sApiException SYSTEM_ERROR 系统异常
+     */
+    @Override
+    public Ingress create(Ingress ingress) {
+        ExtensionsV1beta1Ingress ExtensionsV1beta1Ingress = ingress.toK8sObject();
+
+        try {
+            ExtensionsV1beta1Ingress result = extensionsV1beta1Api.createNamespacedIngress(
+                    ingress.getNamespace(),
+                    ExtensionsV1beta1Ingress,
+                    PRETTY_FORMAT,
+                    null,
+                    null);
+            return result == null ? null : new Ingress(result);
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "ingress 创建失败,ingress={}, response={}", ingress, e.getResponseBody());
+            if (e.getCode() == NOT_FOUND) {
+                throw new K8sApiException(NOT_FOUND);
+            } else if (e.getCode() == ALREADY_EXIST) {
+                throw new K8sApiException(ALREADY_EXIST);
+            } else {
+                throw K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        } catch (Exception e) {
+            LoggerUtil.error(logger, e, "ingress 创建异常,ingress={}", ingress);
+            throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+        }
+    }
+
+
+    /**
+     * 更新Ingress资源
+     * @param ingress
+     * @throws K8sApiException NOT_FOUND 资源不存在
+     * @throws K8sApiException SYSTEM_ERROR 系统异常
+     * @return
+     * @todo bugfix replace 需要merge字段
+     */
+    @Override
+    public Ingress update(Ingress ingress) {
+        ExtensionsV1beta1Ingress ExtensionsV1beta1Ingress = ingress.toK8sObject();
+        try {
+            ExtensionsV1beta1Ingress = extensionsV1beta1Api.replaceNamespacedIngress(
+                    ingress.getName(),
+                    ingress.getNamespace(),
+                    ExtensionsV1beta1Ingress,
+                    PRETTY_FORMAT,
+                    null,
+                    null);
+            return new Ingress(ExtensionsV1beta1Ingress);
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "ingress更新失败,ingress={}, response={}", ingress, e.getResponseBody());
+            if (e.getCode() == NOT_FOUND) {
+                throw new K8sApiException(NOT_FOUND, "资源不存在");
+            }else {
+                throw K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        } catch (Exception e) {
+            LoggerUtil.error(logger, e, "ingress更新失败,ingress={}", ingress);
+            throw K8s_SYSTEM_ERROR_EXCEPTION;
+        }
+    }
+
+    @Override
+    public void delete(Ingress ingress) {
+
+    }
+
+    @Override
+    public List<Ingress> getByCondition(K8sObjectRequest request) {
+        return null;
+    }
+
+
+    /**
+     * 删除namespace命名空间下的ingress
+     * @param namespace
+     * @param name
+     * @throws K8sApiException NOT_FOUND 命名空间/ingress不存在
+     * @throws K8sApiException SYSTEM_ERROR 系统异常
+     */
+    public void delete(String namespace, String name) {
+        try {
+            deleteIngress(namespace, name);
+        }catch (ApiException e) {
+            LoggerUtil.error(logger, e, "ingress 删除失败,name={}, namespace={}, response={}", name, namespace, e.getResponseBody());
+            if (e.getCode() == NOT_FOUND) {
+                throw new K8sApiException(NOT_FOUND, "ingress不存在");
+            }else {
+                throw K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        }catch (JsonSyntaxException e) {
+            //do nothing
+        }catch (Exception e) {
+            LoggerUtil.error(logger, e, "ingress 删除异常,name={}, namespace={}", name, namespace);
+            throw e;
+        }
+    }
+
+    public List<Ingress> getAll(String namespace) {
+        try {
+            ExtensionsV1beta1IngressList ingresses = extensionsV1beta1Api.listNamespacedIngress(
+                    namespace,
+                    PRETTY_FORMAT,
+                    null,
+                    null,
+                    null,
+                    null,
+                    null,
+                    null,
+                    null,
+                    null);
+            return ingresses.getItems().stream().map(Ingress::new).collect(Collectors.toList());
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "查询namespace下所有ingress异常, namespace={}, response={}",
+                    namespace, e.getResponseBody());
+            throw K8s_SYSTEM_ERROR_EXCEPTION;
+        } catch (Exception e) {
+            LoggerUtil.error(logger, e, "查询namespace下所有ingress异常, namespace={}",
+                    namespace);
+            throw K8s_SYSTEM_ERROR_EXCEPTION;
+        }
+    }
+
+    /**
+     * 查找位于namespace下名称为name的Ingress
+     * @param namespace
+     * @param name
+     * @throws K8sApiException SYSTEM_ERROR 系统异常
+     * @return 不存在返回null
+     */
+    public Ingress get(String namespace, String name) {
+        try {
+            ExtensionsV1beta1Ingress ExtensionsV1beta1Ingress = extensionsV1beta1Api.readNamespacedIngress(name, namespace, PRETTY_FORMAT, false, false);
+            return ExtensionsV1beta1Ingress == null ? null : new Ingress(ExtensionsV1beta1Ingress);
+        } catch (ApiException e) {
+            if (e.getCode() != NOT_FOUND) {
+                LoggerUtil.error(logger, e, "ingress 查找失败,name={}, namespace={}, response={}", name, namespace, e.getResponseBody());
+                throw K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        } catch (Exception e) {
+            LoggerUtil.error(logger, e, "ingress 查找失败,name={}, namespace={}", name, namespace);
+            throw K8s_SYSTEM_ERROR_EXCEPTION;
+        }
+        return null;
+    }
+
+
+    private void deleteIngress(String namespace, String name) throws ApiException{
+        V1DeleteOptions v1DeleteOptions = new V1DeleteOptionsBuilder()
+                .withApiVersion(Ingress.API_VERSION)
+                .withPropagationPolicy(FOREGROUND_PROPAGATION_POLICY)
+                .build();
+        extensionsV1beta1Api.deleteNamespacedIngress(
+                name,
+                namespace,
+                PRETTY_FORMAT,
+                null,
+                null,
+                null,
+                FOREGROUND_PROPAGATION_POLICY,
+                v1DeleteOptions);
+    }
+}

+ 105 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/impl/LogApiImpl.java

@@ -0,0 +1,105 @@
+package cn.seecoder.paas.service.facade.k8s.impl;
+
+import cn.seecoder.paas.service.facade.k8s.LogApi;
+import cn.seecoder.paas.service.facade.k8s.exception.K8sApiException;
+import cn.seecoder.paas.util.LoggerUtil;
+import io.kubernetes.client.openapi.ApiException;
+import io.kubernetes.client.openapi.apis.CoreV1Api;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+
+import static cn.seecoder.paas.service.facade.k8s.K8sConstants.PRETTY_FORMAT;
+
+/**
+ * author: rale
+ * createdAt: 1/6/19
+ */
+@Service
+public class LogApiImpl implements LogApi {
+
+    private static final Logger logger = LoggerUtil.getLogger(LogApiImpl.class);
+
+    private final CoreV1Api coreV1Api;
+
+    @Autowired
+    public LogApiImpl(CoreV1Api coreV1Api) {
+        this.coreV1Api = coreV1Api;
+    }
+
+    @Override
+    public String getAll(String namespace, String pod, String containerName) {
+        return get(namespace, pod, containerName, null, null);
+    }
+
+    @Override
+    public String getAll(String namespace, String pod) {
+        return getAll(namespace, pod, null);
+    }
+
+    @Override
+    public String getLastNLines(String namespace, String pod, String containerName, int N) {
+        return get(namespace, pod, containerName, N, null);
+    }
+
+    @Override
+    public String getLastNLines(String namespace, String pod, int N) {
+        return getLastNLines(namespace, pod, null, N);
+    }
+
+    @Override
+    public String getBeforeNSeconds(String namespace, String pod, String containerName, int N) {
+        return get(namespace, pod, containerName, null, N);
+    }
+
+    @Override
+    public String getBeforeNSeconds(String namespace, String pod, int N) {
+        return getBeforeNSeconds(namespace, pod, null, N);
+    }
+
+    @Override
+    public String getAfterTime(String namespace, String pod, String container, LocalDateTime time) {
+
+        return null;
+    }
+
+    @Override
+    public String getAfterTime(String namespace, String pod, LocalDateTime time) {
+        return null;
+    }
+
+    private String get(String namespace, String pod, String containerName, Integer NLastLines, Integer sinceNSecondsBefore) {
+        try {
+            return coreV1Api.readNamespacedPodLog(
+                    pod,
+                    namespace,
+                    containerName,
+                    null,
+                    null,
+                    null,
+                    PRETTY_FORMAT,
+                    false,
+                    sinceNSecondsBefore,
+                    NLastLines,
+                    null);
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "获取日志异常,namespace={}, pod={}, containerName={}, NLastLines={}, sinceNSecondsBefore={}",
+                    namespace, pod, containerName, NLastLines, sinceNSecondsBefore);
+            if (e.getCode() == K8sApiException.NOT_FOUND) {
+                throw new K8sApiException(K8sApiException.NOT_FOUND);
+            } if (e.getCode() == K8sApiException.BAD_REQUEST) {
+                throw new K8sApiException(K8sApiException.BAD_REQUEST);
+            }else {
+                throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        } catch (Exception e) {
+            LoggerUtil.error(logger, e, "获取日志异常,namespace={}, pod={}, containerName={}",
+                    namespace, pod, containerName);
+            throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+        }
+
+    }
+
+}

+ 144 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/impl/NamespaceApiImpl.java

@@ -0,0 +1,144 @@
+package cn.seecoder.paas.service.facade.k8s.impl;
+
+import cn.seecoder.paas.service.facade.k8s.model.K8sObjectRequest;
+import com.google.gson.JsonSyntaxException;
+import cn.seecoder.paas.util.LoggerUtil;
+import io.kubernetes.client.openapi.ApiException;
+import io.kubernetes.client.openapi.apis.CoreV1Api;
+import io.kubernetes.client.openapi.models.V1DeleteOptions;
+import io.kubernetes.client.openapi.models.V1DeleteOptionsBuilder;
+import io.kubernetes.client.openapi.models.V1Namespace;
+import io.kubernetes.client.openapi.models.V1NamespaceList;
+import cn.seecoder.paas.service.facade.k8s.NamespaceApi;
+import cn.seecoder.paas.service.facade.k8s.exception.K8sApiException;
+import cn.seecoder.paas.service.facade.k8s.model.LabelSelector;
+import cn.seecoder.paas.service.facade.k8s.model.Namespace;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static cn.seecoder.paas.service.facade.k8s.exception.K8sApiException.*;
+import static cn.seecoder.paas.service.facade.k8s.K8sConstants.FOREGROUND_PROPAGATION_POLICY;
+import static cn.seecoder.paas.service.facade.k8s.K8sConstants.PRETTY_FORMAT;
+
+@Service
+public class NamespaceApiImpl implements NamespaceApi {
+
+    private static final Logger logger = LoggerUtil.getLogger(NamespaceApi.class);
+
+    private final CoreV1Api coreV1Api;
+
+    @Autowired
+    public NamespaceApiImpl(CoreV1Api coreV1Api) {
+        this.coreV1Api = coreV1Api;
+    }
+
+    /**
+     * 新建命名空间
+     * @param namespace 命名空间名称需要唯一
+     * @return
+     * @throws K8sApiException 命名空间已经存在
+     * @throws K8sApiException 系统异常
+     */
+    @Override
+    public Namespace create(Namespace namespace) {
+        V1Namespace v1Namespace = namespace.toK8sObject();
+        try {
+            V1Namespace result = coreV1Api.createNamespace(v1Namespace, PRETTY_FORMAT, null, null);
+            return result == null ? null : new Namespace(result);
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "Namespace创建失败,namespace={}, v1Namespace={}, response={}", namespace, v1Namespace, e.getResponseBody());
+            if (e.getCode() == ALREADY_EXIST) {
+                throw new K8sApiException(K8sApiException.ALREADY_EXIST);
+            } else {
+                throw K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        } catch (Exception e) {
+            LoggerUtil.error(logger, e, "Namespace创建异常, namespace={}, v1Namespace={}", namespace, v1Namespace);
+            throw K8s_SYSTEM_ERROR_EXCEPTION;
+        }
+    }
+
+    @Override
+    public Namespace update(Namespace namespace) {
+        throw K8s_NOT_SUPPORT_METHOD;
+    }
+
+    @Override
+    public void delete(Namespace namespace) {
+        try {
+            V1DeleteOptions v1DeleteOptions = new V1DeleteOptionsBuilder()
+                    .withApiVersion(Namespace.API_VERSION)
+                    .withPropagationPolicy(FOREGROUND_PROPAGATION_POLICY)
+                    .build();
+            coreV1Api.deleteNamespace(
+                    namespace.getName(),
+                    PRETTY_FORMAT,
+                    null,
+                    null,
+                    null,
+                    FOREGROUND_PROPAGATION_POLICY,
+                    v1DeleteOptions);
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "Namespace删除失败,name={}, response={}", namespace.getName() , e.getResponseBody());
+            if (e.getCode() == NOT_FOUND) {
+                throw K8s_NAMESPACE_NOT_EXIST;
+            }else {
+                throw K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        } catch (JsonSyntaxException e){
+            //do nothing
+        } catch (Exception e) {
+            LoggerUtil.error(logger, e, "Namespace删除异常, name={}", namespace.getName());
+            throw K8s_SYSTEM_ERROR_EXCEPTION;
+        }
+    }
+
+    @Override
+    public List<Namespace> getByCondition(K8sObjectRequest request) {
+        try {
+            if (request.getName() != null) {
+                V1Namespace v1Namespace = coreV1Api.readNamespace(
+                        request.getName(),
+                        PRETTY_FORMAT,
+                        null,
+                        null);
+                return v1Namespace == null ? Collections.emptyList() : Collections.singletonList(new Namespace(v1Namespace));
+            }
+            LabelSelector selector = null;
+            if (request.getSelector() != null) {
+                selector = request.getSelector();
+            }
+            if (request.getLabels() != null) {
+                if (selector == null) {
+                    selector = new LabelSelector();
+                }
+                selector.addAll(request.getLabels());
+            }
+            V1NamespaceList v1NamespaceList = coreV1Api.listNamespace(
+                    PRETTY_FORMAT,
+                    null,
+                    null,
+                    null,
+                    selector.toJsonString(),
+                    null,
+                    null,
+                    null,
+                    null);
+            List<Namespace> namespaces =  v1NamespaceList.getItems().stream().map(Namespace::new).collect(Collectors.toList());
+            LoggerUtil.info(logger,"result={}", namespaces);
+            return namespaces;
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "获取全部Namespace异常, response={}", e.getResponseBody());
+            throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+        } catch (Exception e) {
+            LoggerUtil.error(logger, e, "查询所有Namespace失败");
+            throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+        }
+    }
+
+}

+ 104 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/impl/PodApiImpl.java

@@ -0,0 +1,104 @@
+package cn.seecoder.paas.service.facade.k8s.impl;
+
+import cn.seecoder.paas.service.facade.k8s.PodApi;
+import cn.seecoder.paas.service.facade.k8s.exception.K8sApiException;
+import cn.seecoder.paas.service.facade.k8s.model.K8sObjectRequest;
+import cn.seecoder.paas.service.facade.k8s.model.LabelSelector;
+import cn.seecoder.paas.service.facade.k8s.model.Pod;
+import cn.seecoder.paas.util.LoggerUtil;
+import io.kubernetes.client.openapi.ApiException;
+import io.kubernetes.client.openapi.apis.CoreV1Api;
+import io.kubernetes.client.openapi.models.V1Pod;
+import io.kubernetes.client.openapi.models.V1PodList;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static cn.seecoder.paas.service.facade.k8s.K8sConstants.PRETTY_FORMAT;
+import static cn.seecoder.paas.service.facade.k8s.exception.K8sApiException.*;
+
+/**
+ * author: rale
+ * createdAt: 3/4/19
+ */
+@Service
+public class PodApiImpl implements PodApi {
+
+    private static final Logger logger = LoggerUtil.getLogger(PodApiImpl.class);
+
+    private final CoreV1Api coreV1Api;
+
+    @Autowired
+    public PodApiImpl(CoreV1Api coreV1Api) {
+        this.coreV1Api = coreV1Api;
+    }
+
+    @Override
+    public Pod create(Pod pod) {
+        throw  K8s_NOT_SUPPORT_METHOD;
+    }
+
+    @Override
+    public Pod update(Pod pod) {
+        throw  K8s_NOT_SUPPORT_METHOD;
+    }
+
+    @Override
+    public void delete(Pod pod) {
+        throw  K8s_NOT_SUPPORT_METHOD;
+    }
+
+    @Override
+    public List<Pod> getByCondition(K8sObjectRequest request) {
+        try {
+            if (request.getName() != null) {
+                V1Pod obj = coreV1Api.readNamespacedPod(
+                        request.getName(),
+                        request.getNamespace(),
+                        PRETTY_FORMAT,
+                        null,
+                        null);
+                return obj == null ? Collections.emptyList() : Collections.singletonList(new Pod(obj));
+            }
+            LabelSelector selector = null;
+            if (request.getSelector() != null) {
+                selector = request.getSelector();
+            }
+            if (request.getLabels() != null) {
+                if (selector == null) {
+                    selector = new LabelSelector();
+                }
+                selector.addAll(request.getLabels());
+            }
+            V1PodList v1Pods = coreV1Api.listNamespacedPod(
+                    request.getNamespace(),
+                    PRETTY_FORMAT,
+                    null,
+                    null,
+                    null,
+                    selector.toJsonString(),
+                    null,
+                    null,
+                    null,
+                    null);
+            return v1Pods.getItems().stream().map(Pod::new).collect(Collectors.toList());
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "查找pod失败,namespace = {}, response={}",
+                    request.getNamespace(), e.getResponseBody());
+            if (e.getCode() == NOT_FOUND) {
+                throw new K8sApiException(NOT_FOUND);
+            }else {
+                throw K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        } catch (Exception e) {
+            LoggerUtil.error(logger, e, "查找pod失败,namespace = {}, name={}",
+                    request.getNamespace());
+            throw K8s_SYSTEM_ERROR_EXCEPTION;
+        }
+    }
+
+}

+ 149 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/impl/SecretApiImpl.java

@@ -0,0 +1,149 @@
+package cn.seecoder.paas.service.facade.k8s.impl;
+
+
+import cn.seecoder.paas.service.facade.k8s.SecretApi;
+import cn.seecoder.paas.service.facade.k8s.exception.K8sApiException;
+import cn.seecoder.paas.service.facade.k8s.util.SecretTypeEnum;
+import cn.seecoder.paas.service.facade.k8s.vo.SecretVO;
+import cn.seecoder.paas.util.LoggerUtil;
+import io.kubernetes.client.openapi.ApiException;
+import io.kubernetes.client.openapi.apis.CoreV1Api;
+import io.kubernetes.client.openapi.models.*;
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static cn.seecoder.paas.service.facade.k8s.K8sConstants.PRETTY_FORMAT;
+
+
+@Service
+public class SecretApiImpl implements SecretApi {
+
+    private static final Logger logger = LoggerUtil.getLogger(SecretApi.class);
+
+    @Autowired
+    private CoreV1Api coreV1Api;
+
+    @Override
+    public SecretVO createGenericSecret(String namespace, String name, Map<String, byte[]> data) throws K8sApiException {
+        return createGenericSecret(namespace, name, data, SecretTypeEnum.Generic);
+    }
+
+    @Override
+    public SecretVO createGenericSecret(String namespace, String name, Map<String, byte[]> data, SecretTypeEnum type) throws K8sApiException {
+
+        //Secret默认创建的Type为Opaque,base64编码格式的Secret,用来储存密码、密钥等。
+
+        V1Secret v1Secret = new V1Secret();
+        V1ObjectMeta meta = new V1ObjectMeta();
+        meta.setName(name);
+        v1Secret.setApiVersion("v1");
+        meta.setNamespace(namespace);
+        v1Secret.setData(data);
+        v1Secret.setKind("Secret");
+        v1Secret.setMetadata(meta);
+        v1Secret.setType(type.getValue());
+
+        try {
+            return new SecretVO(coreV1Api.createNamespacedSecret(namespace, v1Secret, PRETTY_FORMAT, null, null));
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "Secret创建失败, Secret={}, response={}", v1Secret, e.getResponseBody());
+            switch (e.getCode()) {
+                case K8sApiException.NAMESPACE_NOT_EXIST:
+                    throw new K8sApiException(K8sApiException.NAMESPACE_NOT_EXIST);
+                case K8sApiException.ALREADY_EXIST:
+                    throw new K8sApiException(K8sApiException.ALREADY_EXIST);
+                default:
+                    throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        }
+    }
+
+    @Override
+    public SecretVO createPrivateRegistrySecret(String namespace, String server, String user, String password) throws K8sApiException {
+        //dockerSecret 统一的名称
+        String name = "docker-registry-secret";
+        return createPrivateRegistrySecret(namespace, name, server, user, password);
+
+    }
+
+    @Override
+    public SecretVO createPrivateRegistrySecret(String namespace, String name, String server, String user, String password) throws K8sApiException {
+        Map<String, byte[]> data = new HashMap<>();
+
+        String authInfo = user + ":" + password;
+        String auth = Base64.getEncoder().encodeToString(authInfo.getBytes());
+        String dockerConfigJson =
+                "{\"auths\":{\"" + server + "\":{\"username\":\"" + user +
+                        "\",\"password\":\"" + password +
+                        "\",\"auth\":\"" + auth + "\"}}}";
+        data.put(".dockerconfigjson", dockerConfigJson.getBytes());
+
+        return createGenericSecret(namespace, name, data, SecretTypeEnum.REGISTRY);
+    }
+
+    @Override
+    public List<SecretVO> getSecretList(String namespace) throws K8sApiException {
+        try {
+            V1SecretList v1SecretList = coreV1Api.listNamespacedSecret(
+                    namespace,
+                    PRETTY_FORMAT,
+                    null,
+                    "",
+                    null,
+                    "",
+                    200,
+                    "",
+                    10,
+                    false);
+            return v1SecretList.getItems().stream().map(SecretVO::new).collect(Collectors.toList());
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "Secret列表获取失败, response={}", e.getResponseBody());
+            if (e.getCode() == K8sApiException.NAMESPACE_NOT_EXIST) {
+                throw new K8sApiException(K8sApiException.NAMESPACE_NOT_EXIST);
+            } else {
+                throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        }
+    }
+
+    @Override
+    public List<SecretVO> getSecretListByType(String namespace, SecretTypeEnum type) throws K8sApiException {
+        return getSecretList(namespace)
+                .stream()
+                .filter(secretVO -> type == secretVO.getSecretType())
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public SecretVO getSecretByName(String namespace, String name) throws K8sApiException {
+        return getSecretList(namespace)
+                .stream()
+                .filter(secretVO -> StringUtils.equals(secretVO.getName(), name))
+                .findAny()
+                .get();
+    }
+
+    @Override
+    public void deleteSecretByName(String namespace, String name) throws K8sApiException {
+        V1DeleteOptions body = new V1DeleteOptions();
+        try {
+            //暂时没有用到deleteNamespacedSecret()中的其他参数。
+            V1Status v1Status = coreV1Api.deleteNamespacedSecret(name, namespace, PRETTY_FORMAT, null, 5, false, "", body);
+        } catch (ApiException e) {
+            if (e.getCode() == K8sApiException.NOT_FOUND) {
+                throw new K8sApiException(K8sApiException.NOT_FOUND);
+            } else {
+                throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        }
+    }
+}
+

+ 172 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/impl/ServiceApiImpl.java

@@ -0,0 +1,172 @@
+package cn.seecoder.paas.service.facade.k8s.impl;
+
+import cn.seecoder.paas.service.facade.k8s.model.K8sObjectRequest;
+import cn.seecoder.paas.service.facade.k8s.model.LabelSelector;
+import cn.seecoder.paas.util.LoggerUtil;
+import io.kubernetes.client.openapi.ApiException;
+import io.kubernetes.client.openapi.apis.CoreV1Api;
+import io.kubernetes.client.openapi.models.*;
+import cn.seecoder.paas.service.facade.k8s.ServiceApi;
+import cn.seecoder.paas.service.facade.k8s.exception.K8sApiException;
+import cn.seecoder.paas.service.facade.k8s.model.Service;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static cn.seecoder.paas.service.facade.k8s.exception.K8sApiException.ALREADY_EXIST;
+import static cn.seecoder.paas.service.facade.k8s.exception.K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+import static cn.seecoder.paas.service.facade.k8s.exception.K8sApiException.NOT_FOUND;
+import static cn.seecoder.paas.service.facade.k8s.K8sConstants.FOREGROUND_PROPAGATION_POLICY;
+import static cn.seecoder.paas.service.facade.k8s.K8sConstants.PRETTY_FORMAT;
+
+@org.springframework.stereotype.Service
+public class ServiceApiImpl implements ServiceApi {
+    private static final Logger logger = LoggerUtil.getLogger(ServiceApi.class);
+
+    @Autowired
+    private CoreV1Api coreV1Api;
+
+    /**
+     * 创建Service
+     * @param service
+     * @throws K8sApiException NOT_FOUND namespace不存在
+     * @throws K8sApiException ALREADY_EXISTS service已经存在
+     * @throws K8sApiException SYSTEM_ERROR 系统异常
+     */
+    @Override
+    public Service create(Service service) {
+        try {
+            V1Service v1Service = service.toK8sObject();
+            v1Service = coreV1Api.createNamespacedService(service.getNamespace(), v1Service, PRETTY_FORMAT, null, null);
+            return v1Service == null ? null : new Service(v1Service);
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "service创建失败, service={}, response={}", service, e.getResponseBody());
+            if (e.getCode() == ALREADY_EXIST) {
+                throw new K8sApiException(ALREADY_EXIST, "Service已经存在");
+            }else if (e.getCode() == NOT_FOUND){
+                throw new K8sApiException(NOT_FOUND, "Namespace不存在");
+            }else {
+                throw K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        }
+    }
+
+
+    /**
+     * 更新Service
+     * @param service
+     * @throws K8sApiException NOT_FOUND namespace不存在
+     * @throws K8sApiException SYSTEM_ERROR 系统异常
+     * @return
+     */
+    @Override
+    public Service update(Service service) {
+
+        try {
+            V1Service v1Service = getV1Service(service.getNamespace(), service.getName());
+            service.merge(v1Service);
+            v1Service = coreV1Api.replaceNamespacedService(service.getName(), service.getNamespace(), v1Service, PRETTY_FORMAT, null, null);
+            return v1Service == null ? null : new Service(v1Service);
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "service替换失败, service={}, response={}", service, e.getResponseBody());
+            if (e.getCode() == NOT_FOUND){
+                throw new K8sApiException(NOT_FOUND, "Service不存在");
+            }else {
+                throw K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        } catch (Exception e) {
+            LoggerUtil.error(logger, e, "service替换异常, service={}", service);
+            throw K8s_SYSTEM_ERROR_EXCEPTION;
+        }
+    }
+
+    @Override
+    public void delete(Service service) {
+        try {
+            V1DeleteOptions v1DeleteOptions = new V1DeleteOptionsBuilder()
+                    .withApiVersion(Service.API_VERSION)
+                    .withPropagationPolicy(FOREGROUND_PROPAGATION_POLICY)
+                    .build();
+            coreV1Api.deleteNamespacedService(
+                    service.getName(),
+                    service.getNamespace(),
+                    PRETTY_FORMAT,
+                    null,
+                    null,
+                    null,
+                    FOREGROUND_PROPAGATION_POLICY,
+                    v1DeleteOptions);
+        }catch (ApiException e) {
+            LoggerUtil.error(logger, e, "Service删除失败,namespace={}, name={}, response={}",
+                    service.getNamespace(), service.getName(), e.getResponseBody());
+            if (e.getCode() == NOT_FOUND) {
+                throw new K8sApiException(K8sApiException.NOT_FOUND, "Service不存在");
+            }else {
+                throw K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        }catch (Exception e) {
+            LoggerUtil.error(logger, e, "Service删除异常,namespace={}, name={}, response={}", service.getNamespace(), service.getName());
+        }
+    }
+
+    @Override
+    public List<Service> getByCondition(K8sObjectRequest request) {
+        try {
+            if (request.getName() != null) {
+                V1Service obj = coreV1Api.readNamespacedService(
+                        request.getName(),
+                        request.getNamespace(),
+                        PRETTY_FORMAT,
+                        null,
+                        null);
+                return obj == null ? Collections.emptyList() : Collections.singletonList(new Service(obj));
+            }
+            LabelSelector selector = null;
+            if (request.getSelector() != null) {
+                selector = request.getSelector();
+            }
+            if (request.getLabels() != null) {
+                if (selector == null) {
+                    selector = new LabelSelector();
+                }
+                selector.addAll(request.getLabels());
+            }
+            V1ServiceList objList = coreV1Api.listNamespacedService(
+                    request.getNamespace(),
+                    PRETTY_FORMAT,
+                    null,
+                    null,
+                    null,
+                    selector.toJsonString(),
+                    null,
+                    null,
+                    null,
+                    null);
+            return objList.getItems().stream().map(Service::new).collect(Collectors.toList());
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "查找pod失败,namespace = {}, response={}",
+                    request.getNamespace(), e.getResponseBody());
+            if (e.getCode() == NOT_FOUND) {
+                throw new K8sApiException(NOT_FOUND);
+            }else {
+                throw K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        } catch (Exception e) {
+            LoggerUtil.error(logger, e, "查找pod失败,namespace = {}, name={}",
+                    request.getNamespace());
+            throw K8s_SYSTEM_ERROR_EXCEPTION;
+        }
+    }
+
+    private V1Service getV1Service(String namespace, String name) throws ApiException {
+        return coreV1Api.readNamespacedService(
+                name,
+                namespace,
+                PRETTY_FORMAT,
+                null,
+                null);
+    }
+}

+ 71 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/impl/WatchApiImpl.java

@@ -0,0 +1,71 @@
+package cn.seecoder.paas.service.facade.k8s.impl;
+
+import com.google.common.reflect.TypeToken;
+import io.kubernetes.client.openapi.ApiClient;
+import io.kubernetes.client.openapi.ApiException;
+import io.kubernetes.client.openapi.apis.AppsV1Api;
+import io.kubernetes.client.openapi.models.V1Deployment;
+import cn.seecoder.paas.service.facade.k8s.WatchApi;
+import io.kubernetes.client.util.Watch;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.io.IOException;
+
+import static cn.seecoder.paas.service.facade.k8s.K8sConstants.PRETTY_FORMAT;
+
+/**
+ * author: rale
+ * createdAt: 1/13/19
+ */
+@Service
+public class WatchApiImpl implements WatchApi{
+
+    private ApiClient watchApiClient;
+
+    private AppsV1Api watchAppsV1Api;
+
+    @Autowired
+    public WatchApiImpl(ApiClient watchApiClient, AppsV1Api watchAppsV1Api) {
+        this.watchApiClient = watchApiClient;
+        this.watchAppsV1Api = watchAppsV1Api;
+    }
+    @Override
+    public void watchDeployment() {
+        try {
+            Watch<V1Deployment> watch = Watch.createWatch(
+                    watchApiClient,
+                    watchAppsV1Api.listNamespacedDeploymentCall(
+                            "demo2",
+                            PRETTY_FORMAT,
+                            null,
+                            null,
+                            null,
+                            "demo.seec.nju.cn/app=test",
+                            null,
+                            null,
+                            null,
+                            true,
+                            null
+                             ),
+                    new TypeToken<Watch.Response<V1Deployment>>(){}.getType()
+
+            );
+            watch.forEach(response -> {
+                System.out.printf("%s pod : %s %s%n", response.type, response.object.getMetadata().getName(), response.object.getStatus());
+                try {
+                    watch.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            });
+        } catch (ApiException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void watchPod() {
+
+    }
+}

+ 48 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/model/ConfigMap.java

@@ -0,0 +1,48 @@
+package cn.seecoder.paas.service.facade.k8s.model;
+
+import io.kubernetes.client.openapi.models.V1ConfigMap;
+import io.kubernetes.client.openapi.models.V1ConfigMapBuilder;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class ConfigMap extends K8sAbstractObject<V1ConfigMap> {
+
+    public static final String API_VERSION = "v1";
+
+    public static final String KIND = "ConfigMap";
+
+    private Map<String, String> data = new HashMap<>();
+
+
+    public ConfigMap(V1ConfigMap v1ConfigMap) {
+        super(v1ConfigMap);
+        this.data = v1ConfigMap.getData();
+    }
+
+    public void merge(V1ConfigMap v1ConfigMap) {
+        v1ConfigMap.getMetadata().setName(getName());
+        v1ConfigMap.getMetadata().setNamespace(this.getNamespace());
+        v1ConfigMap.getMetadata().setLabels(this.getLabels());
+        v1ConfigMap.getMetadata().setAnnotations(this.getAnnotations());
+        v1ConfigMap.setData(this.getData());
+    }
+
+    @Override
+    public V1ConfigMap toK8sObject() {
+        return new V1ConfigMapBuilder()
+                .withApiVersion(getApiVersion() == null ? API_VERSION : getApiVersion())
+                .withKind(getKind() == null ? KIND : getKind())
+                .withMetadata(toV1ObjectMeta())
+                .withData(data)
+                .build();
+    }
+}

+ 104 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/model/Container.java

@@ -0,0 +1,104 @@
+package cn.seecoder.paas.service.facade.k8s.model;
+
+import io.kubernetes.client.openapi.models.*;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+
+/**
+ * author: rale
+ * createdAt: 1/3/19
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class Container implements K8sObjectTransformer<V1Container> {
+
+    private static final String IMAGE_PULL_POLICY = "IfNotPresent";
+
+    private String name;
+
+    private String image;
+
+    /* 启动指令的参数 */
+    private List<String> args;
+
+    /* 启动指令 */
+    private List<String> command;
+
+    /* 环境变量 */
+    private Map<String, String> env = new HashMap<>();
+
+    private Set<ContainerPort> ports = new HashSet<>();
+
+    private List<VolumeMount> volumeMounts = new ArrayList<>();
+
+    @Data
+    @AllArgsConstructor
+    @NoArgsConstructor
+    @Builder
+    public static class VolumeMount implements K8sObjectTransformer<V1VolumeMount> {
+
+        String name;
+
+        String mountPath;
+
+        Boolean readOnly = true;
+
+        public VolumeMount(V1VolumeMount v1VolumeMount) {
+            this.name = v1VolumeMount.getName();
+            this.mountPath = v1VolumeMount.getMountPath();
+            this.readOnly = v1VolumeMount.getReadOnly();
+        }
+
+        @Override
+        public V1VolumeMount toK8sObject() {
+            return new V1VolumeMountBuilder()
+                    .withName(name)
+                    .withMountPath(mountPath)
+                    .withReadOnly(readOnly)
+                    .build();
+        }
+    }
+
+    public Container(V1Container v1Container) {
+        this.name = v1Container.getName();
+        this.image = v1Container.getImage();
+        this.ports = v1Container.getPorts().stream().map(ContainerPort::new).collect(Collectors.toSet());
+        this.args = v1Container.getArgs();
+        this.command = v1Container.getCommand();
+        this.volumeMounts = v1Container.getVolumeMounts().stream().map(VolumeMount::new).collect(Collectors.toList());
+        if (v1Container.getEnv() != null && !v1Container.getEnv().isEmpty()) {
+            this.env = v1Container.getEnv().stream().collect(Collectors.toMap(V1EnvVar::getName, V1EnvVar::getValue));
+        }
+    }
+
+    @Override
+    public V1Container toK8sObject() {
+        return new V1ContainerBuilder()
+                .withName(name)
+                .withImage(image)
+                .withImagePullPolicy(IMAGE_PULL_POLICY)
+                .withArgs(args)
+                .withCommand(command)
+                .withPorts(ports.stream().map(ContainerPort::toK8sObject).collect(Collectors.toList()))
+                .withVolumeMounts(this.volumeMounts.stream().map(VolumeMount::toK8sObject).collect(Collectors.toList()))
+                .withEnv(env
+                        .entrySet()
+                        .stream()
+                        .map(entry ->
+                                new V1EnvVarBuilder()
+                                        .withName(entry.getKey())
+                                        .withValue(entry.getValue())
+                                        .build())
+                        .collect(Collectors.toList())
+                )
+                .build();
+    }
+}

+ 46 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/model/ContainerPort.java

@@ -0,0 +1,46 @@
+package cn.seecoder.paas.service.facade.k8s.model;
+
+import io.kubernetes.client.openapi.models.V1ContainerPort;
+import io.kubernetes.client.openapi.models.V1ContainerPortBuilder;
+import lombok.*;
+import org.hibernate.validator.constraints.Range;
+
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+
+import static cn.seecoder.paas.service.facade.k8s.K8sConstants.NAME_PATTERN;
+
+
+/**
+ * author: rale
+ * createdAt: 12/23/18
+ */
+@Data
+@EqualsAndHashCode
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class ContainerPort implements K8sObjectTransformer<V1ContainerPort> {
+
+    private String name;
+
+    private int port;
+
+    private String protocol;
+
+    public ContainerPort(V1ContainerPort v1ContainerPort) {
+        this.setName(v1ContainerPort.getName());
+        this.setPort(v1ContainerPort.getContainerPort());
+        this.setProtocol(v1ContainerPort.getProtocol());
+    }
+
+    @Override
+    public V1ContainerPort toK8sObject() {
+        V1ContainerPort v1ContainerPort = new V1ContainerPortBuilder()
+                .withContainerPort(port)
+                .withName(name)
+                .withProtocol(protocol)
+                .build();
+        return v1ContainerPort;
+    }
+}

+ 72 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/model/CpuQuantity.java

@@ -0,0 +1,72 @@
+package cn.seecoder.paas.service.facade.k8s.model;
+
+import io.kubernetes.client.custom.Quantity;
+import lombok.Getter;
+
+/**
+ * CPU配额数量,默认单位为m,即CPU时间
+ */
+@Getter
+public class CpuQuantity {
+
+    private final Quantity quantity;
+
+    private final Unit unit;
+
+    private static final Unit DEFAULT_UNIT = Unit.Mi;
+    //Cpu=0
+    public static final CpuQuantity EMPTY = new CpuQuantity(0);
+    //Cpu的默认分配额度
+    public static final CpuQuantity DEFAULT = new CpuQuantity(50);
+
+    //Cpu单位
+    public enum Unit{
+        Mi("m");
+
+        private String code;
+
+        Unit(String code) {
+            this.code = code;
+        }
+
+        public String getCode(){
+            return this.code;
+        }
+    }
+
+
+
+    public CpuQuantity(int quantity){
+        this.quantity = Quantity.fromString(quantity + DEFAULT_UNIT.getCode());
+        this.unit = DEFAULT_UNIT;
+    }
+
+    public CpuQuantity(long quantity) {
+        this.quantity = Quantity.fromString(quantity + DEFAULT_UNIT.getCode());
+        this.unit = DEFAULT_UNIT;
+    }
+
+
+    public CpuQuantity(Quantity quantity) {
+        this.quantity = quantity;
+        this.unit = DEFAULT_UNIT;
+    }
+
+    public Quantity toQuantity(){
+        return quantity;
+    }
+
+    public String getQuantity() {
+        return quantity.toSuffixedString();
+    }
+
+    public static CpuQuantity getCpuQuantity(Quantity quantity) {
+        if (quantity == null) {
+            return null;
+        } else {
+            return new CpuQuantity(quantity);
+        }
+    }
+
+
+}

+ 141 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/model/Deployment.java

@@ -0,0 +1,141 @@
+package cn.seecoder.paas.service.facade.k8s.model;
+
+import io.kubernetes.client.openapi.models.*;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.hibernate.validator.constraints.Range;
+import org.springframework.data.annotation.ReadOnlyProperty;
+
+import javax.validation.constraints.NotEmpty;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * author: rale
+ * createdAt: 1/3/19
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class Deployment extends K8sAbstractObject<V1Deployment> {
+
+    public static final String API_VERSION = "apps/v1";
+
+    private static final String KIND = "Deployment";
+
+    private static final int DEFAULT_PROGRESS_DEADLINE_SECONDS = 600;
+
+    private static final int DEFAULT_REVISION_HISTORY_LIMIT = 0;
+
+    private static final String DEFAULT_UPDATE_STRATEGY = "Recreate";
+
+    private List<Container> containers = new ArrayList<>();
+
+    private List<String> imagePullSecrets = new ArrayList<>();
+
+    private List<V1Volume> volumes = new ArrayList<>();
+
+    @Range(min = 1, max = 3)
+    private int replicas;
+
+    /**
+     * 超过timeout时间未完成启动则提示启动超时
+     */
+    private int timeout = DEFAULT_PROGRESS_DEADLINE_SECONDS;
+
+    /**
+     * 保留历史replica set的个数
+     */
+    private int revisionHistoryLimit = DEFAULT_REVISION_HISTORY_LIMIT;
+
+    @ReadOnlyProperty
+    private V1DeploymentStatus status;
+
+    public Deployment(V1Deployment v1Deployment) {
+        super(v1Deployment);
+        this.containers = v1Deployment.getSpec().getTemplate().getSpec().getContainers()
+                .stream().map(Container::new).collect(Collectors.toList());
+        this.imagePullSecrets = v1Deployment.getSpec().getTemplate().getSpec().getImagePullSecrets()
+                .stream().map(V1LocalObjectReference::getName).collect(Collectors.toList());
+        this.replicas = v1Deployment.getSpec().getReplicas();
+        this.timeout = v1Deployment.getSpec().getProgressDeadlineSeconds();
+        this.revisionHistoryLimit = v1Deployment.getSpec().getRevisionHistoryLimit();
+        this.volumes =  v1Deployment.getSpec().getTemplate().getSpec().getVolumes();
+        this.status = v1Deployment.getStatus();
+    }
+
+    private V1LabelSelector toDeploymentV1LabelSelector() {
+        return new V1LabelSelectorBuilder()
+                .withMatchLabels(getLabels())
+                .build();
+    }
+
+    private V1PodTemplateSpec toPodSpecTemplate(){
+        V1ObjectMeta v1ObjectMeta = new V1ObjectMetaBuilder()
+                .withLabels(getLabels())
+                .withAnnotations(getAnnotations())
+                .build();
+        List<V1LocalObjectReference> references = imagePullSecrets
+                .stream()
+                .map(secret -> new V1LocalObjectReference().name(secret))
+                .collect(Collectors.toList());
+        V1PodSpec v1PodSpec = new V1PodSpecBuilder()
+                .addAllToContainers(containers.stream().map(Container::toK8sObject).collect(Collectors.toList()))
+                .withImagePullSecrets(references)
+                .withVolumes(volumes)
+                .build();
+        V1PodTemplateSpec v1PodTemplateSpec = new V1PodTemplateSpecBuilder()
+                .withMetadata(v1ObjectMeta)
+                .withSpec(v1PodSpec)
+                .build();
+        return v1PodTemplateSpec;
+    }
+
+    private V1DeploymentSpec toV1DeploymentSpec(){
+//        V1RollingUpdateDeployment rollingUpdateDeployment = new V1RollingUpdateDeploymentBuilder()
+//                .withMaxSurge(new IntOrString(0))
+//                .withMaxUnavailable(new IntOrString("100%"))
+//                .build();
+
+
+        return new V1DeploymentSpecBuilder()
+                .withReplicas(replicas)
+                .withProgressDeadlineSeconds(timeout)
+                .withStrategy(new V1DeploymentStrategy().type(DEFAULT_UPDATE_STRATEGY))
+//                .withStrategy(new V1DeploymentStrategy().rollingUpdate(rollingUpdateDeployment))
+                .withRevisionHistoryLimit(revisionHistoryLimit)
+                .withSelector(this.toDeploymentV1LabelSelector())
+                .withTemplate(this.toPodSpecTemplate())
+                .build();
+    }
+
+    public void merge(V1Deployment v1Deployment) {
+        v1Deployment.getMetadata().setName(getName());
+        v1Deployment.getMetadata().setNamespace(this.getNamespace());
+        v1Deployment.getMetadata().setLabels(this.getLabels());
+        v1Deployment.getMetadata().setAnnotations(this.getAnnotations());
+        v1Deployment.getSpec().setReplicas(this.replicas);
+        v1Deployment.getSpec().setProgressDeadlineSeconds(timeout);
+        v1Deployment.getSpec().setRevisionHistoryLimit(revisionHistoryLimit);
+        v1Deployment.getSpec().getTemplate().getSpec().setContainers(this.containers.stream().map(Container::toK8sObject).collect(Collectors.toList()));
+        v1Deployment.getSpec().getTemplate().getSpec().setVolumes(this.volumes);
+        v1Deployment.getSpec().getTemplate().getSpec().setImagePullSecrets(
+                this.imagePullSecrets
+                .stream()
+                .map(secret -> new V1LocalObjectReference().name(secret))
+                .collect(Collectors.toList()));
+    }
+
+    @Override
+    public V1Deployment toK8sObject() {
+        return new V1DeploymentBuilder()
+                .withApiVersion(getApiVersion() == null ? API_VERSION : getApiVersion())
+                .withKind(getKind() == null ? KIND : getKind())
+                .withMetadata(this.toV1ObjectMeta())
+                .withSpec(this.toV1DeploymentSpec())
+                .build();
+    }
+}

+ 65 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/model/DeploymentStatus.java

@@ -0,0 +1,65 @@
+package cn.seecoder.paas.service.facade.k8s.model;
+
+import io.kubernetes.client.openapi.models.V1DeploymentCondition;
+import io.kubernetes.client.openapi.models.V1DeploymentStatus;
+import lombok.Data;
+
+/**
+ * author: rale
+ * createdAt: 1/16/19
+ * 如果要凭借该状态
+ */
+@Data
+public class DeploymentStatus {
+
+    /**
+     * 当前为就绪状态的容器
+     */
+    private int readyReplicas;
+
+    /**
+     * 当前不可用的容器
+     */
+    private int unavailableReplicas;
+
+    /**
+     * 当前为最新版本的容器
+     */
+    private int updatedReplicas;
+
+    /**
+     * 是否满足了最低可用服务数量
+     */
+    private boolean available;
+
+    /**
+     * 当前deployment是否能够正常执行
+     */
+    private boolean progressing;
+
+    /**
+     * progressing为false时的几种情况
+     * 1. ProgressDeadlineExceeded 启动超时
+     * 2.
+     */
+    private String progressingMessage;
+
+    public DeploymentStatus(V1DeploymentStatus deploymentStatus) {
+        this.readyReplicas = deploymentStatus.getReadyReplicas() == null ? 0 : deploymentStatus.getReadyReplicas();
+        this.unavailableReplicas = deploymentStatus.getUnavailableReplicas() == null ? 0 : deploymentStatus.getUnavailableReplicas();
+        this.updatedReplicas = deploymentStatus.getUpdatedReplicas() == null ? 0 : deploymentStatus.getUpdatedReplicas();
+        for (V1DeploymentCondition condition : deploymentStatus.getConditions()) {
+            if (condition.getType().equals("Available")) {
+                available = condition.getStatus().equals("True");
+            }else if (condition.getType().equals("Progressing")) {
+                progressing = condition.getStatus().equals("True");
+                progressingMessage = condition.getMessage();
+            }
+        }
+    }
+
+    public boolean isAvailable() {
+        return this.available;
+    }
+
+}

+ 53 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/model/HttpGetAction.java

@@ -0,0 +1,53 @@
+package cn.seecoder.paas.service.facade.k8s.model;
+
+import io.kubernetes.client.custom.IntOrString;
+import io.kubernetes.client.openapi.models.V1HTTPGetAction;
+import io.kubernetes.client.openapi.models.V1HTTPGetActionBuilder;
+import io.kubernetes.client.openapi.models.V1HTTPHeader;
+import io.kubernetes.client.openapi.models.V1HTTPHeaderBuilder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * author: rale
+ * createdAt: 1/13/19
+ *  封装HTTP get请求
+ */
+@Data
+@NoArgsConstructor
+public class HttpGetAction {
+
+    /*默认值为容器的host名称,即容器名称*/
+    private String host;
+
+    private Map<String, String> httpHeaders = Collections.EMPTY_MAP;
+
+    private String path;
+
+    private int port;
+
+    public HttpGetAction(V1HTTPGetAction v1HTTPGetAction) {
+        this.host = v1HTTPGetAction.getHost();
+        if (v1HTTPGetAction.getHttpHeaders() != null) {
+            httpHeaders = v1HTTPGetAction.getHttpHeaders().stream().collect(Collectors.toMap(V1HTTPHeader::getName, V1HTTPHeader::getValue));
+        }
+        this.path = v1HTTPGetAction.getPath();
+        this.port = v1HTTPGetAction.getPort().getIntValue();
+    }
+
+    public V1HTTPGetAction toV1HTTPGetAction() {
+        return new V1HTTPGetActionBuilder()
+                .withHost(host)
+                .withHttpHeaders(
+                        httpHeaders.entrySet().stream()
+                                .map(entry -> new V1HTTPHeaderBuilder().withName(entry.getKey()).withValue(entry.getValue()).build())
+                                .collect(Collectors.toList()))
+                .withPath(path)
+                .withPort(new IntOrString(port))
+                .build();
+    }
+}

+ 150 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/model/Ingress.java

@@ -0,0 +1,150 @@
+package cn.seecoder.paas.service.facade.k8s.model;
+
+import io.kubernetes.client.custom.IntOrString;
+import io.kubernetes.client.openapi.models.*;
+import lombok.*;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * author: rale
+ * createdAt: 1/5/19
+ * @// TODO: 3/25/19 添加标签字段 以后应当是一个分支上的代码可以创建一个deployment 
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class Ingress extends K8sAbstractObject<ExtensionsV1beta1Ingress> {
+
+    public static final String API_VERSION = "extensions/v1beta1";
+
+    private static final String KIND = "Ingress";
+
+    private static final String REWRITE_ANNOTATION = "nginx.ingress.kubernetes.io/rewrite-target";
+
+    private static final String DEFAULT_REWRITE_PATH = "/";
+
+    //重定向的路径,默认为根路径
+    private static final String redirectPath = DEFAULT_REWRITE_PATH;
+
+    /*Ingress能暴露HTTP和HTTPS协议口,目前暂不支持HTTPS协议*/
+    private List<IngressRule> httpRules = new ArrayList<>();
+
+    public Ingress(ExtensionsV1beta1Ingress extensionsV1beta1Ingress) {
+        super(extensionsV1beta1Ingress);
+        List<ExtensionsV1beta1IngressRule> rules = extensionsV1beta1Ingress.getSpec().getRules();
+        if (rules != null && !rules.isEmpty()) {
+            httpRules = rules.stream()
+                    .filter(Objects::nonNull)
+                    .map(IngressRule::new)
+                    .collect(Collectors.toList());
+        }
+    }
+
+    @Override
+    public ExtensionsV1beta1Ingress toK8sObject() {
+        ExtensionsV1beta1IngressSpec ExtensionsV1beta1IngressSpec = new ExtensionsV1beta1IngressSpecBuilder()
+                .withRules(toExtensionsV1beta1IngressRules())
+                .build();
+        ExtensionsV1beta1Ingress ExtensionsV1beta1Ingress = new ExtensionsV1beta1IngressBuilder()
+                .withApiVersion(getApiVersion() == null ? API_VERSION : getApiVersion())
+                .withKind(getKind() == null ? KIND : getKind())
+                .withMetadata(this.toV1ObjectMeta())
+                .withSpec(ExtensionsV1beta1IngressSpec)
+                .build();
+        return ExtensionsV1beta1Ingress;
+    }
+
+    @Data
+    @NoArgsConstructor
+    @AllArgsConstructor
+    @EqualsAndHashCode
+    @Builder
+    public static class IngressPath implements K8sObjectTransformer<ExtensionsV1beta1HTTPIngressPath> {
+
+        private String path;
+
+        private String serviceName;
+
+        private int port;
+
+        public IngressPath(ExtensionsV1beta1HTTPIngressPath path) {
+            this.path = path.getPath();
+            this.serviceName = path.getBackend().getServiceName();
+            this.port = path.getBackend().getServicePort().getIntValue();
+        }
+
+        @Override
+        public ExtensionsV1beta1HTTPIngressPath toK8sObject() {
+            ExtensionsV1beta1HTTPIngressPath v1beta1HTTPIngressPath = new ExtensionsV1beta1HTTPIngressPath();
+            v1beta1HTTPIngressPath.setPath(getPath());
+
+            ExtensionsV1beta1IngressBackend ExtensionsV1beta1IngressBackend = new ExtensionsV1beta1IngressBackend();
+            ExtensionsV1beta1IngressBackend.setServiceName(serviceName);
+            ExtensionsV1beta1IngressBackend.setServicePort(new IntOrString(port));
+
+            v1beta1HTTPIngressPath.setBackend(ExtensionsV1beta1IngressBackend);
+
+            return v1beta1HTTPIngressPath;
+        }
+    }
+
+    @Data
+    @NoArgsConstructor
+    @AllArgsConstructor
+    @EqualsAndHashCode
+    @Builder
+    public static class IngressRule implements K8sObjectTransformer<ExtensionsV1beta1IngressRule> {
+
+        private String host;
+
+        private Set<IngressPath> ruleValues = new HashSet<>();
+
+        public IngressRule(String host) {
+            this.host = host;
+        }
+
+        public void addRuleValue(IngressPath ingressPath) {
+            this.ruleValues.add(ingressPath);
+        }
+
+        public IngressRule(ExtensionsV1beta1IngressRule ExtensionsV1beta1IngressRule) {
+            this.host = ExtensionsV1beta1IngressRule.getHost();
+            if (ExtensionsV1beta1IngressRule.getHttp() != null) {
+                this.ruleValues = ExtensionsV1beta1IngressRule.getHttp()
+                        .getPaths()
+                        .stream()
+                        .map(IngressPath::new)
+                        .collect(Collectors.toSet());
+            }
+
+        }
+
+        private ExtensionsV1beta1HTTPIngressRuleValue toV1beta1HTTPIngressRuleValue() {
+            List<ExtensionsV1beta1HTTPIngressPath> paths = ruleValues.stream()
+                    .map(IngressPath::toK8sObject)
+                    .collect(Collectors.toList());
+
+            return new ExtensionsV1beta1HTTPIngressRuleValueBuilder()
+                    .withPaths(paths)
+                    .build();
+        }
+
+        @Override
+        public ExtensionsV1beta1IngressRule toK8sObject() {
+            return new ExtensionsV1beta1IngressRuleBuilder()
+                    .withHost(host)
+                    .withHttp(toV1beta1HTTPIngressRuleValue())
+                    .build();
+        }
+    }
+
+    private List<ExtensionsV1beta1IngressRule> toExtensionsV1beta1IngressRules() {
+        return httpRules.stream()
+                .map(IngressRule::toK8sObject)
+                .collect(Collectors.toList());
+
+    }
+}

+ 84 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/model/K8sAbstractObject.java

@@ -0,0 +1,84 @@
+package cn.seecoder.paas.service.facade.k8s.model;
+
+import io.kubernetes.client.common.KubernetesObject;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@NoArgsConstructor
+@Data
+public abstract class K8sAbstractObject<T extends KubernetesObject> implements K8sObject, K8sObjectTransformer<T> {
+
+    private String apiVersion;
+
+    private String kind;
+
+    private String name;
+
+    private String namespace;
+
+    private Map<String, String> labels = new HashMap<>();
+
+    private Map<String, String> annotations = new HashMap<>();
+
+    public K8sAbstractObject(T t) {
+        this.apiVersion = t.getApiVersion();
+        this.kind = t.getKind();
+        this.name = t.getMetadata().getName();
+        this.namespace = t.getMetadata().getNamespace();
+        this.annotations = t.getMetadata().getAnnotations();
+        this.labels = t.getMetadata().getLabels();
+    }
+
+    @Override
+    public Map<String, String> getAnnotations() {
+        return annotations;
+    }
+
+    @Override
+    public String getAnnotation(String key) {
+        return annotations.get(key);
+    }
+
+    @Override
+    public void setAnnotation(String key, String value) {
+        annotations.put(key, value);
+    }
+
+    @Override
+    public Map<String, String> getLabels() {
+        return labels;
+    }
+
+    @Override
+    public String getLabel(String key) {
+        return labels.get(key);
+    }
+
+    @Override
+    public void setLabel(String key, String value) {
+        labels.put(key, value);
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public String getNamespace() {
+        return namespace;
+    }
+
+    @Override
+    public String getApiVersion() {
+        return apiVersion;
+    }
+
+    @Override
+    public String getKind() {
+        return kind;
+    }
+}

+ 42 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/model/K8sObject.java

@@ -0,0 +1,42 @@
+package cn.seecoder.paas.service.facade.k8s.model;
+
+import io.kubernetes.client.openapi.models.V1ObjectMeta;
+import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder;
+
+import java.util.Map;
+
+public interface K8sObject {
+
+    String DEFAULT_NAMESPACE = "default";
+
+    String getApiVersion();
+
+    String getKind();
+
+    String getName();
+
+    Map<String, String> getLabels();
+
+    String getLabel(String key);
+
+    void setLabel(String key, String value);
+
+    Map<String, String> getAnnotations();
+
+    String getAnnotation(String key);
+
+    void setAnnotation(String key, String value);
+
+    default String getNamespace() {
+      return DEFAULT_NAMESPACE;
+    }
+
+    default V1ObjectMeta toV1ObjectMeta() {
+        return new V1ObjectMetaBuilder()
+                .withName(getName())
+                .withNamespace(getNamespace())
+                .withAnnotations(getAnnotations())
+                .withLabels(getLabels())
+                .build();
+    }
+}

+ 24 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/model/K8sObjectRequest.java

@@ -0,0 +1,24 @@
+package cn.seecoder.paas.service.facade.k8s.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Data
+@AllArgsConstructor
+@Builder
+public class K8sObjectRequest {
+
+    private String name;
+
+    private String namespace;
+
+    private Map<String, String> labels = new HashMap<>();
+
+    private Map<String, String> annotations = new HashMap<>();
+
+    private LabelSelector selector;
+}

+ 6 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/model/K8sObjectTransformer.java

@@ -0,0 +1,6 @@
+package cn.seecoder.paas.service.facade.k8s.model;
+
+public interface K8sObjectTransformer<T> {
+
+    T toK8sObject();
+}

+ 170 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/model/LabelSelector.java

@@ -0,0 +1,170 @@
+package cn.seecoder.paas.service.facade.k8s.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.util.*;
+
+/**
+ * author: rale
+ * createdAt: 1/7/19
+ *
+ * 标签选择器
+ * 支持以下几种语法
+ * key IN value1,value2 ... 筛选出键为key且值在value中的资源 key equals value 等价于key in value
+ * key NOT_IN value1, value2 ... 筛选出键为key且值不在value中,或者不存在键为key的键值对的资源 key not equals value 等价于 key not in value
+ * Key EXISTS 筛选出包含键key的资源
+ * Key NOT_EXISTS 筛选出不包含键key的资源
+ *
+ * 本选择器不会删除同名的key
+ */
+@NoArgsConstructor
+public class LabelSelector {
+
+    public enum Operator{
+
+        IN {
+            @Override
+            public String toJsonString(MatchExpressionItem matchExpressionItem) {
+                Set<String> values = matchExpressionItem.getValues();
+                if (values == null || values.size() == 0) return "";
+                if (values.size() == 1) return matchExpressionItem.getKey() + "=" + values.iterator().next();
+                StringBuilder resultValue = new StringBuilder("(");
+                for (String value : matchExpressionItem.getValues()) {
+                    if (resultValue.length() > 1) {
+                        resultValue.append(",");
+                    }
+                    resultValue.append(value);
+                }
+                resultValue.append(")");
+                return matchExpressionItem.getKey() + " in " + resultValue;
+            }
+        },
+
+        NOT_IN {
+            @Override
+            public String toJsonString(MatchExpressionItem matchExpressionItem) {
+                Set<String> values = matchExpressionItem.getValues();
+                if (values == null || values.size() == 0) return "";
+                if (values.size() == 1) return matchExpressionItem.getKey() + "!=" + values.iterator().next();
+                StringBuilder resultValue = new StringBuilder("(");
+                for (String value : matchExpressionItem.getValues()) {
+                    if (resultValue.length() > 1) {
+                        resultValue.append(",");
+                    }
+                    resultValue.append(value);
+                }
+                resultValue.append(")");
+                return matchExpressionItem.getKey() + " notin " + resultValue;
+            }
+        },
+
+        EXISTS {
+            @Override
+            public String toJsonString(MatchExpressionItem matchExpressionItem) {
+                return matchExpressionItem.getKey();
+            }
+        },
+
+        NOT_EXISTS {
+            @Override
+            public String toJsonString(MatchExpressionItem matchExpressionItem) {
+                return "!" + matchExpressionItem.getKey();
+            }
+        },
+
+        EQUALS {
+            @Override
+            public String toJsonString(MatchExpressionItem matchExpressionItem) {
+                Set<String> values = matchExpressionItem.getValues();
+                if (values == null || values.size() == 0) return "";
+                return matchExpressionItem.getKey() + "=" + values.iterator().next();
+            }
+        }
+        ;
+
+        public abstract String toJsonString(MatchExpressionItem matchExpressionItem);
+    }
+
+
+    private List<MatchExpressionItem> matchExpressionItems;
+
+
+    public void addMatchExpression(String key, Operator operator, Set<String> values) {
+        addMatchExpressionItem(new MatchExpressionItem(key, operator, values));
+    }
+
+
+    public void addMatchExpression(String key, Operator operator, String... values) {
+        Set<String> valueSet = new HashSet<>(Arrays.asList(values));
+        addMatchExpressionItem(new MatchExpressionItem(key, operator, valueSet));
+    }
+
+    public void addAll(Map<String, String> labels) {
+        if (labels == null) return;
+        labels.entrySet().forEach(entry -> {
+            addMatchExpressionItem(new MatchExpressionItem(entry.getKey(), Operator.EQUALS, Collections.singleton(entry.getValue())));
+        });
+    }
+
+
+    private void addMatchExpressionItem(MatchExpressionItem matchExpressionItem) {
+        if (matchExpressionItem == null) {
+            return;
+        }
+        if (matchExpressionItems == null) {
+            matchExpressionItems = new LinkedList<>();
+        }
+        matchExpressionItems.add(matchExpressionItem);
+    }
+
+    @Data
+    @EqualsAndHashCode
+    @NoArgsConstructor
+    @AllArgsConstructor
+    private static class MatchExpressionItem{
+
+        private String key;
+
+        private Operator operator;
+
+        private Set<String> values;
+
+        private String toJsonString() {
+            return operator == null ? "" : operator.toJsonString(this);
+        }
+
+    }
+
+    public String toJsonString() {
+        StringBuilder sb = new StringBuilder();
+
+        if (matchExpressionItems != null && !matchExpressionItems.isEmpty()) {
+            for (MatchExpressionItem matchExpressionItem : matchExpressionItems) {
+                if (sb.length() > 0) {
+                    sb.append(",");
+                }
+                sb.append(matchExpressionItem.toJsonString());
+            }
+        }
+        return sb.toString();
+    }
+
+    public static String toJsonString(Map<String, String> labels) {
+        StringBuilder result = new StringBuilder();
+        for (Map.Entry<String, String> label : labels.entrySet()) {
+            if (result.length() > 0) {
+                result.append(",");
+            }
+            result.append(toJsonString(label.getKey(), label.getValue()));
+        }
+        return result.toString();
+    }
+
+    public static String toJsonString(String key, String value) {
+        return key + "=" + value;
+    }
+
+}

+ 34 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/model/Namespace.java

@@ -0,0 +1,34 @@
+package cn.seecoder.paas.service.facade.k8s.model;
+
+import cn.seecoder.paas.util.DateUtil;
+import io.kubernetes.client.openapi.models.*;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.time.LocalDateTime;
+
+@Data
+@NoArgsConstructor
+public class Namespace extends K8sAbstractObject<V1Namespace> {
+
+    private static final String KIND = "Namespace";
+
+    public static final String API_VERSION = "v1";
+
+    private LocalDateTime createdAt;
+
+    public Namespace(V1Namespace v1Namespace) {
+        super(v1Namespace);
+        this.createdAt = DateUtil.fromJodaDateTime(v1Namespace.getMetadata().getCreationTimestamp());
+    }
+
+    public V1Namespace toK8sObject() {
+        V1Namespace v1Namespace = new V1NamespaceBuilder()
+                .withApiVersion(getApiVersion() == null ? API_VERSION : getApiVersion())
+                .withKind(getKind() == null ? KIND : getKind())
+                .withMetadata(this.toV1ObjectMeta())
+                .build();
+        return v1Namespace;
+    }
+
+}

+ 48 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/model/Pod.java

@@ -0,0 +1,48 @@
+package cn.seecoder.paas.service.facade.k8s.model;
+
+import io.kubernetes.client.openapi.models.V1Pod;
+import io.kubernetes.client.openapi.models.V1PodBuilder;
+import io.kubernetes.client.openapi.models.V1PodStatus;
+import lombok.*;
+import org.springframework.data.annotation.ReadOnlyProperty;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * author: rale
+ * createdAt: 3/4/19
+ */
+@Data
+@EqualsAndHashCode
+@NoArgsConstructor
+@AllArgsConstructor
+public class Pod extends K8sAbstractObject<V1Pod> {
+
+    public static final String API_VERSION = "v1";
+    public static final String KIND = "Pod";
+
+    @ReadOnlyProperty
+    private List<Container> containers;
+
+    @ReadOnlyProperty
+    private V1PodStatus podStatus;
+
+
+    public Pod(V1Pod v1Pod) {
+        super(v1Pod);
+        this.containers = v1Pod.getSpec().getContainers().stream().map(Container::new).collect(Collectors.toList());
+        this.podStatus = v1Pod.getStatus();
+    }
+
+    @Override
+    public V1Pod toK8sObject() {
+        return new V1PodBuilder()
+                .withApiVersion(getApiVersion() == null ? API_VERSION : getApiVersion())
+                .withKind(getKind() == null ? KIND : getKind())
+                .withMetadata(this.toV1ObjectMeta())
+                .withSpec(null)
+                .build();
+    }
+}

+ 10 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/model/PodStatus.java

@@ -0,0 +1,10 @@
+package cn.seecoder.paas.service.facade.k8s.model;
+
+/**
+ * author: rale
+ * createdAt: 3/4/19
+ */
+public class PodStatus {
+
+
+}

+ 67 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/model/Service.java

@@ -0,0 +1,67 @@
+package cn.seecoder.paas.service.facade.k8s.model;
+
+import io.kubernetes.client.openapi.models.*;
+import io.kubernetes.client.proto.Meta;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class Service extends K8sAbstractObject<V1Service> {
+
+    public static final String API_VERSION = "v1";
+
+    private static final String KIND = "Service";
+
+    private String name;
+
+    private String namespace;
+
+    private Map<String, String> labels = new HashMap<>();
+
+    private Map<String, String> annotations = new HashMap<>();
+
+    private Set<ServicePort> servicePorts = new HashSet<>();
+
+    private Map<String, String> selectors = new HashMap<>();
+
+    public Service(V1Service v1Service) {
+        super(v1Service);
+        this.servicePorts = v1Service.getSpec().getPorts().stream().map(ServicePort::new).collect(Collectors.toSet());
+        this.selectors = v1Service.getSpec().getSelector();
+    }
+
+    public void merge(V1Service v1Service) {
+        v1Service.getMetadata().setNamespace(this.namespace);
+        v1Service.getMetadata().setName(this.name);
+        v1Service.getSpec().setPorts(servicePorts.stream().map(ServicePort::toV1ServicePort).collect(Collectors.toList()));
+        v1Service.getSpec().setSelector(this.selectors);
+    }
+
+    private V1ServiceSpec toV1ServiceSpec() {
+        V1ServiceSpec serviceSpec = new V1ServiceSpecBuilder()
+                .withSelector(selectors)
+                .withPorts(servicePorts.stream().map(ServicePort::toV1ServicePort).collect(Collectors.toList()))
+                .build();
+        return serviceSpec;
+    }
+
+    @Override
+    public V1Service toK8sObject() {
+        V1Service v1Service = new V1ServiceBuilder()
+                .withApiVersion(getApiVersion() == null ? API_VERSION : getApiVersion())
+                .withKind(getKind() == null ? KIND : getKind())
+                .withMetadata(this.toV1ObjectMeta())
+                .withSpec(this.toV1ServiceSpec())
+                .build();
+        return v1Service;
+    }
+
+}

+ 51 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/model/ServicePort.java

@@ -0,0 +1,51 @@
+package cn.seecoder.paas.service.facade.k8s.model;
+
+import io.kubernetes.client.custom.IntOrString;
+import io.kubernetes.client.openapi.models.V1ServicePort;
+import io.kubernetes.client.openapi.models.V1ServicePortBuilder;
+import lombok.*;
+import org.hibernate.validator.constraints.Range;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * author: rale
+ * createdAt: 12/23/18
+ */
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode
+@AllArgsConstructor
+@Builder
+public class ServicePort {
+
+    @NotNull
+    private String name;
+
+    /**
+     * 服务端口号
+     */
+    @Range(min = 1, max = 633)
+    private int port;
+
+    /**
+     * 容器端口号
+     */
+    @NotNull
+    private int targetPort;
+
+    public ServicePort(V1ServicePort v1ServicePort) {
+        this.name = v1ServicePort.getName();
+        this.port = v1ServicePort.getPort();
+        this.targetPort = v1ServicePort.getTargetPort().getIntValue();
+    }
+
+    public V1ServicePort toV1ServicePort(){
+        return new V1ServicePortBuilder()
+                .withName(name)
+                .withPort(port)
+                .withTargetPort(new IntOrString(targetPort))
+                .build();
+    }
+
+}

+ 41 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/util/SecretTypeEnum.java

@@ -0,0 +1,41 @@
+package cn.seecoder.paas.service.facade.k8s.util;
+
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * author: rale
+ * createdAt: 1/3/19
+ */
+public enum SecretTypeEnum {
+    /*基本类型如数据库用户名密码,KEY-VALUE形式*/
+    Generic("Opaque"),
+
+    /*私有仓库的验证信息*/
+    REGISTRY("kubernetes.io/dockerconfigjson"),
+
+    /*账户验证信息,暂未用到*/
+    SERVICE_ACCOUNT("Service Account");
+
+    private String value;
+
+    SecretTypeEnum(String value) {
+        this.value = value;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public boolean equals(String value) {
+        return StringUtils.equals(this.value, value);
+    }
+
+    public static SecretTypeEnum getByValue(String value) {
+        for (SecretTypeEnum secretTypeEnum : SecretTypeEnum.values()){
+            if (StringUtils.equals(secretTypeEnum.getValue(), value)) {
+                return secretTypeEnum;
+            }
+        }
+        return null;
+    }
+}

+ 46 - 0
src/main/java/cn/seecoder/paas/service/facade/k8s/vo/SecretVO.java

@@ -0,0 +1,46 @@
+package cn.seecoder.paas.service.facade.k8s.vo;
+
+import cn.seecoder.paas.service.facade.k8s.util.SecretTypeEnum;
+import io.kubernetes.client.openapi.models.V1Secret;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.util.CollectionUtils;
+
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * author: rale
+ * createdAt: 1/3/19
+ */
+@Data
+@NoArgsConstructor
+public class SecretVO {
+
+    /**
+     * Secret的名字 格式:DeployEnv-projectName-DB_SECRET_NAME
+     */
+    private String name;
+
+    private String namespace;
+
+    private Map<String, String> data;
+
+    private SecretTypeEnum secretType;
+
+    public SecretVO(V1Secret v1Secret) {
+        this.name = v1Secret.getMetadata().getName();
+        this.namespace = v1Secret.getMetadata().getNamespace();
+        this.data = new HashMap<>();
+        if (!CollectionUtils.isEmpty(v1Secret.getStringData())) {
+            data.putAll(v1Secret.getStringData());
+        }
+        if (!CollectionUtils.isEmpty(v1Secret.getData())) {
+            for (Map.Entry<String, byte[]> entry : v1Secret.getData().entrySet()){
+                data.put(entry.getKey(), new String(entry.getValue(), StandardCharsets.UTF_8));
+            }
+        }
+        this.secretType = SecretTypeEnum.getByValue(v1Secret.getType());
+    }
+}

+ 73 - 0
src/main/java/cn/seecoder/paas/service/impl/ApplicationServiceImpl.java

@@ -0,0 +1,73 @@
+package cn.seecoder.paas.service.impl;
+
+import cn.seecoder.paas.data.dao.ApplicationDAO;
+import cn.seecoder.paas.data.dao.ConfigDAO;
+import cn.seecoder.paas.data.entity.Application;
+import cn.seecoder.paas.data.entity.Config;
+import cn.seecoder.paas.service.ApplicationService;
+import cn.seecoder.paas.service.model.converter.ApplicationConverter;
+import cn.seecoder.paas.service.model.converter.ConfigConverter;
+import cn.seecoder.paas.service.model.vo.ApplicationVO;
+import cn.seecoder.paas.service.model.vo.ConfigVO;
+import cn.seecoder.paas.util.ServiceException;
+import cn.seecoder.paas.util.enums.ConfigBelongsToType;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.transaction.Transactional;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Service
+public class ApplicationServiceImpl implements ApplicationService {
+
+    private final ApplicationDAO applicationDAO;
+
+    private final ConfigDAO configDAO;
+
+    @Autowired
+    public ApplicationServiceImpl(ApplicationDAO applicationDAO, ConfigDAO configDAO) {
+        this.applicationDAO = applicationDAO;
+        this.configDAO = configDAO;
+    }
+
+    @Override
+    @Transactional
+    public ApplicationVO createOrUpdate(ApplicationVO vo) throws ServiceException {
+        ApplicationVO result = ApplicationConverter.convertToVO(applicationDAO.save(ApplicationConverter.convertToEntity(vo)));
+        Config config = configDAO.findByConfigBelongsAndAndEntityId(ConfigBelongsToType.APPLICATION, result.getId());
+        if (config == null) {
+            ConfigVO configVO = ConfigVO.getEmptyConfigVO(ConfigBelongsToType.APPLICATION, result.getId());
+            ConfigConverter.convertToVO(configDAO.save(ConfigConverter.convertToEntity(configVO)));
+        }
+        return result;
+    }
+
+    @Override
+    @Transactional
+    public void delete(Integer id) {
+        applicationDAO.deleteById(id);
+    }
+
+    @Override
+    public List<ApplicationVO> getAll() {
+        return applicationDAO.findAll().stream().map(ApplicationConverter::convertToVO).collect(Collectors.toList());
+    }
+
+    @Override
+    public ApplicationVO getDetailById(Integer id) throws ServiceException {
+        Application application = applicationDAO.findById(id).orElse(null);
+        if (application == null) {
+            return null;
+        }
+        ApplicationVO vo = ApplicationConverter.convertToVO(application);
+        Config config = configDAO.findByConfigBelongsAndAndEntityId(ConfigBelongsToType.APPLICATION, id);
+        if (config != null) {
+            vo.setConfig(ConfigConverter.convertToVO(config));
+        } else {
+            vo.setConfig(ConfigVO.getEmptyConfigVO(ConfigBelongsToType.APPLICATION, id));
+        }
+        return vo;
+    }
+
+}

+ 40 - 0
src/main/java/cn/seecoder/paas/service/impl/ConfigServiceImpl.java

@@ -0,0 +1,40 @@
+package cn.seecoder.paas.service.impl;
+
+import cn.seecoder.paas.data.dao.ConfigDAO;
+import cn.seecoder.paas.data.entity.Config;
+import cn.seecoder.paas.service.ConfigService;
+import cn.seecoder.paas.service.model.converter.ConfigConverter;
+import cn.seecoder.paas.service.model.vo.ConfigVO;
+import cn.seecoder.paas.util.ServiceException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.transaction.Transactional;
+
+@Service
+public class ConfigServiceImpl implements ConfigService {
+
+    private final ConfigDAO configDAO;
+
+    @Autowired
+    public ConfigServiceImpl(ConfigDAO configDAO) {
+        this.configDAO = configDAO;
+    }
+
+    @Override
+    @Transactional
+    public ConfigVO createOrUpdate(ConfigVO configVO) throws ServiceException {
+        Config saved = ConfigConverter.convertToEntity(configVO);
+        if (configVO.getId() != null) {
+            Config config = configDAO.findById(configVO.getId()).orElse(null);
+            if (config != null) {
+                saved.setId(config.getId());
+                saved.setEntityId(config.getEntityId());
+                saved.setConfigBelongs(config.getConfigBelongs());
+            } else {
+                throw ServiceException.BAD_REQUEST;
+            }
+        }
+        return ConfigConverter.convertToVO(configDAO.save(saved));
+    }
+}

+ 373 - 0
src/main/java/cn/seecoder/paas/service/impl/EnvironmentServiceImpl.java

@@ -0,0 +1,373 @@
+package cn.seecoder.paas.service.impl;
+
+import cn.seecoder.paas.data.dao.ApplicationDAO;
+import cn.seecoder.paas.data.dao.ConfigDAO;
+import cn.seecoder.paas.data.dao.EnvironmentDAO;
+import cn.seecoder.paas.data.entity.Application;
+import cn.seecoder.paas.data.entity.Config;
+import cn.seecoder.paas.data.entity.Environment;
+import cn.seecoder.paas.service.EnvironmentService;
+import cn.seecoder.paas.service.facade.docker.DockerApi;
+import cn.seecoder.paas.service.facade.git.GitApi;
+import cn.seecoder.paas.service.facade.k8s.*;
+import cn.seecoder.paas.service.facade.k8s.model.*;
+import cn.seecoder.paas.service.facade.k8s.vo.SecretVO;
+import cn.seecoder.paas.service.model.converter.ConfigConverter;
+import cn.seecoder.paas.service.model.converter.EnvironmentConverter;
+import cn.seecoder.paas.service.model.vo.ConfigVO;
+import cn.seecoder.paas.service.model.vo.EnvironmentVO;
+import cn.seecoder.paas.util.ApplicationProperties;
+import cn.seecoder.paas.util.AsyncWrapper;
+import cn.seecoder.paas.util.LoggerUtil;
+import cn.seecoder.paas.util.ServiceException;
+import cn.seecoder.paas.util.enums.BuildStatus;
+import cn.seecoder.paas.util.enums.BuildType;
+import cn.seecoder.paas.util.enums.ConfigBelongsToType;
+import cn.seecoder.paas.util.enums.ResourceLabel;
+import com.spotify.docker.client.ProgressHandler;
+import com.spotify.docker.client.exceptions.DockerException;
+import com.spotify.docker.client.messages.ProgressMessage;
+import io.kubernetes.client.openapi.models.*;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.ResetCommand;
+import org.slf4j.Logger;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.CollectionUtils;
+
+import javax.transaction.Transactional;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.*;
+import java.util.stream.Collectors;
+
+@org.springframework.stereotype.Service
+public class EnvironmentServiceImpl implements EnvironmentService {
+
+    private static final Logger logger = LoggerUtil.getLogger(EnvironmentServiceImpl.class);
+
+    private final EnvironmentDAO environmentDAO;
+
+    private final ApplicationDAO applicationDAO;
+
+    private final ConfigDAO configDAO;
+
+    private final PodApi podApi;
+
+    private final DeploymentApi deploymentApi;
+
+    private final ServiceApi serviceApi;
+
+    private final IngressApi ingressApi;
+
+    private final SecretApi secretApi;
+
+    private final ConfigMapApi configMapApi;
+
+    private final AsyncWrapper asyncWrapper;
+
+    private final GitApi gitApi;
+
+    private final DockerApi dockerApi;
+
+    private final LogApi logApi;
+
+    private final ApplicationProperties applicationProperties;
+
+    private static final String DEFAULT_IMAGE_PULL_SECRET_NAME = "seecoder-paas-image-pull-secret";
+
+    @Autowired
+    public EnvironmentServiceImpl(EnvironmentDAO environmentDAO,
+                                  ApplicationDAO applicationDAO,
+                                  ConfigDAO configDAO,
+                                  PodApi podApi,
+                                  DeploymentApi deploymentApi,
+                                  ServiceApi serviceApi,
+                                  IngressApi ingressApi,
+                                  SecretApi secretApi,
+                                  ConfigMapApi configMapApi,
+                                  GitApi gitApi,
+                                  DockerApi dockerApi,
+                                  LogApi logApi,
+                                  ApplicationProperties properties,
+                                  AsyncWrapper asyncWrapper) {
+        this.environmentDAO = environmentDAO;
+        this.applicationDAO = applicationDAO;
+        this.configDAO = configDAO;
+        this.podApi = podApi;
+        this.deploymentApi = deploymentApi;
+        this.serviceApi = serviceApi;
+        this.ingressApi = ingressApi;
+        this.secretApi = secretApi;
+        this.configMapApi = configMapApi;
+        this.gitApi = gitApi;
+        this.dockerApi = dockerApi;
+        this.logApi = logApi;
+        this.applicationProperties = properties;
+        this.asyncWrapper = asyncWrapper;
+    }
+
+    @Override
+    @Transactional
+    public EnvironmentVO createOrUpdate(EnvironmentVO environmentVO) throws ServiceException {
+        Application application = applicationDAO.findById(environmentVO.getAppId()).orElse(null);
+        if (application == null) {
+            throw ServiceException.BAD_REQUEST;
+        }
+        Environment environment = EnvironmentConverter.convertToEntity(environmentVO);
+        if (environmentVO.getId() != null) {
+            environment = environmentDAO.findById(environmentVO.getId()).orElse(null);
+            if (environment == null) {
+                throw ServiceException.BAD_REQUEST;
+            }
+            BeanUtils.copyProperties(environmentVO, environment, "id", "appId", "buildStatus", "buildOutput");
+        }
+        EnvironmentVO result = EnvironmentConverter.convertToVO(environmentDAO.save(environment));
+        ConfigVO resultConfigVO;
+        if (environmentVO.getId() == null) {
+            // 是create
+            // 获取
+            Config config = configDAO.findByConfigBelongsAndAndEntityId(ConfigBelongsToType.APPLICATION, application.getId());
+            if (config == null) {
+                config = ConfigConverter.convertToEntity(ConfigVO.getEmptyConfigVO(ConfigBelongsToType.ENVIRONMENT, result.getId()));
+            }
+            Config saved = new Config();
+            BeanUtils.copyProperties(config, saved);
+            saved.setId(null);
+            saved.setConfigBelongs(ConfigBelongsToType.ENVIRONMENT);
+            saved.setEntityId(result.getId());
+            resultConfigVO = ConfigConverter.convertToVO(configDAO.save(saved));
+        } else {
+            resultConfigVO = ConfigConverter.convertToVO(configDAO.findByConfigBelongsAndAndEntityId(ConfigBelongsToType.ENVIRONMENT, result.getId()));
+        }
+        result.setConfig(resultConfigVO);
+        String labelKey = ResourceLabel.RESOURCE.getCode();
+        String labelValue = ResourceLabel.RESOURCE.getGenerator().gen("environment", String.valueOf(result.getAppId()), String.valueOf(result.getId()));
+        K8sObjectRequest request = K8sObjectRequest.builder().namespace(applicationProperties.getDeploymentNamespace()).labels(Collections.singletonMap(labelKey, labelValue)).build();
+        List<Pod> pods = podApi.getByCondition(request);
+        result.setInstance(pods);
+        return result;
+    }
+
+    @Override
+    @Transactional
+    public void delete(Integer id) {
+        environmentDAO.deleteById(id);
+        configDAO.deleteByConfigBelongsAndAndEntityId(ConfigBelongsToType.ENVIRONMENT, id);
+    }
+
+    @Override
+    public EnvironmentVO get(Integer id) throws ServiceException {
+        Environment result = environmentDAO.findById(id).orElse(null);
+        if (result == null) {
+            throw ServiceException.BAD_REQUEST;
+        }
+        ConfigVO resultConfigVO = ConfigConverter.convertToVO(configDAO.findByConfigBelongsAndAndEntityId(ConfigBelongsToType.ENVIRONMENT, result.getId()));
+        EnvironmentVO vo = EnvironmentConverter.convertToVO(result);
+        vo.setConfig(resultConfigVO);
+        String labelKey = ResourceLabel.RESOURCE.getCode();
+        String labelValue = ResourceLabel.RESOURCE.getGenerator().gen("environment", String.valueOf(result.getAppId()), String.valueOf(result.getId()));
+        K8sObjectRequest request = K8sObjectRequest.builder().namespace(applicationProperties.getDeploymentNamespace()).labels(Collections.singletonMap(labelKey, labelValue)).build();
+        List<Pod> pods = podApi.getByCondition(request);
+        vo.setInstance(pods);
+        return vo;
+    }
+
+    @Override
+    public List<EnvironmentVO> getListByAppId(Integer appId) {
+        return environmentDAO.findAllByAppId(appId).stream().map(EnvironmentConverter::convertToVO).collect(Collectors.toList());
+    }
+
+    @Override
+    @Transactional
+    public EnvironmentVO deploy(Integer id) throws ServiceException {
+        Environment result = environmentDAO.findById(id).orElse(null);
+        if (result == null) {
+            throw ServiceException.BAD_REQUEST;
+        }
+        if (result.getBuildStatus() == BuildStatus.BUILDING) {
+            throw new ServiceException("101", "存在正在进行的构建!");
+        }
+        Config config = configDAO.findByConfigBelongsAndAndEntityId(ConfigBelongsToType.ENVIRONMENT, id);
+        if (config == null) {
+            throw ServiceException.INVALID_DATA;
+        }
+        ConfigVO.ConfigContent configContent = ConfigConverter.convertToVO(config).getConfig();
+        if (configContent.getHostPrefix() == null) {
+            throw ServiceException.INVALID_DATA;
+        }
+        result.setBuildOutput("");
+        result.setBuildStatus(BuildStatus.BUILDING);
+        asyncWrapper.asyncInvoke(() -> buildAsync(id));
+        return EnvironmentConverter.convertToVO(environmentDAO.save(result));
+    }
+
+    @Override
+    public Map<String,String> getLog(Integer id, String podName) throws ServiceException {
+        Environment result = environmentDAO.findById(id).orElse(null);
+        if (result == null) {
+            throw ServiceException.BAD_REQUEST;
+        }
+        String labelKey = ResourceLabel.RESOURCE.getCode();
+        String labelValue = ResourceLabel.RESOURCE.getGenerator().gen("environment", String.valueOf(result.getAppId()), String.valueOf(result.getId()));
+        K8sObjectRequest request = K8sObjectRequest.builder().name(podName).namespace(applicationProperties.getDeploymentNamespace()).labels(Collections.singletonMap(labelKey, labelValue)).build();
+        List<Pod> pods = podApi.getByCondition(request);
+        if (CollectionUtils.isEmpty(pods)) {
+            return Collections.emptyMap();
+        }
+        Map<String, String> res = new HashMap<>();
+        for (Container container : pods.get(0).getContainers()) {
+            res.put(container.getName(), logApi.getAll(applicationProperties.getDeploymentNamespace(), pods.get(0).getName(), container.getName()));
+        }
+        return res;
+    }
+
+    void buildAsync(Integer id) throws ServiceException {
+        Environment result = environmentDAO.findById(id).get();
+        Config config = configDAO.findByConfigBelongsAndAndEntityId(ConfigBelongsToType.ENVIRONMENT, id);
+        Application application = applicationDAO.findById(result.getAppId()).get();
+        ConfigVO.ConfigContent configContent = ConfigConverter.convertToVO(config).getConfig();
+        String labelKey = ResourceLabel.RESOURCE.getCode();
+        String labelValue = ResourceLabel.RESOURCE.getGenerator().gen("environment", String.valueOf(result.getAppId()), String.valueOf(result.getId()));
+        Map<String, String> labelsMap = Collections.singletonMap(labelKey, labelValue);
+        String deploymentNamespace = applicationProperties.getDeploymentNamespace();
+        String deploymentHost = applicationProperties.getDeploymentHost();
+        String imageName = result.getBuildTypeValue();
+        // 1. 有git从git拿到地址进行构建并push
+        if (result.getBuildType() == BuildType.FROM_BRANCH_OR_COMMIT) {
+            try {
+                Path directory = Files.createTempDirectory("seecoder-paas-");
+                Git git = gitApi.clone(application.getGitUrl(), directory.toString());
+                if (Files.exists(Paths.get(directory.toString(), "Dockerfile"))) {
+                    git.reset().setMode(ResetCommand.ResetType.HARD).setRef(result.getBuildTypeValue()).call();
+                    String imageTag = String.valueOf(new Date().getTime());
+                    dockerApi.buildAndPush(directory.toString(), "seecoder-paas-" + labelValue, imageTag, new ProgressHandler() {
+                        @Override
+                        public void progress(ProgressMessage message) throws DockerException {
+                            result.setBuildOutput(message.stream());
+                            environmentDAO.save(result);
+                        }
+                    });
+                    imageName = applicationProperties.getDocker().getRegistry() + "/seecoder-paas-" + labelValue + ":" + imageTag;
+                    result.setBuildStatus(BuildStatus.SUCCESS);
+                    environmentDAO.save(result);
+                } else {
+                    result.setBuildStatus(BuildStatus.FAIL);
+                    result.setBuildOutput("找不到构建文件Dockerfile!");
+                    environmentDAO.save(result);
+                    throw new ServiceException("102", "找不到构建文件Dockerfile!");
+                }
+            } catch (Exception e) {
+                result.setBuildStatus(BuildStatus.FAIL);
+                result.setBuildOutput(e.getLocalizedMessage());
+                environmentDAO.save(result);
+                LoggerUtil.error(logger, "构建失败!gitUrl={}, branchOrCommit={}", application.getGitUrl(), result.getBuildTypeValue());
+            }
+        }
+
+        String[] ports = configContent.getServicePort().split(",");
+        if (configContent.getHostPrefix() != null) {
+            for (String port : ports) {
+                // ingress
+                List<Ingress> ingresses = ingressApi.getByCondition(K8sObjectRequest.builder()
+                        .name(labelValue + "-" + port)
+                        .namespace(deploymentNamespace).labels(labelsMap).build());
+                Ingress ingress = new Ingress();
+                ingress.setNamespace(deploymentNamespace);
+                ingress.setName(labelValue + "-" + port);
+                ingress.setLabels(labelsMap);
+                ingress.setHttpRules(Collections.singletonList(
+                        Ingress.IngressRule.builder().host(configContent.getHostPrefix() + "-" + port + "." + deploymentHost).ruleValues(
+                                Collections.singleton(Ingress.IngressPath.builder()
+                                        .path("/")
+                                        .serviceName(labelValue)
+                                        .port(Integer.parseInt(port))
+                                        .build())
+                        ).build()
+                ));
+                if (CollectionUtils.isEmpty(ingresses) || ingresses.size() != 1) {
+                    ingressApi.create(ingress);
+                } else {
+                    ingressApi.update(ingress);
+                }
+            }
+        }
+        // service
+        List<Service> svcs = serviceApi.getByCondition(K8sObjectRequest.builder().namespace(deploymentNamespace).labels(labelsMap).build());
+        Service svc = Service.builder().name(labelValue).labels(labelsMap).namespace(deploymentNamespace).selectors(labelsMap).servicePorts(
+                Arrays.stream(ports).map(port ->
+                        ServicePort.builder()
+                                .name(labelValue + "-" + port)
+                                .port(Integer.parseInt(port))
+                                .targetPort(Integer.parseInt(port))
+                                .build()).collect(Collectors.toSet())).build();
+        if (CollectionUtils.isEmpty(svcs) || svcs.size() != 1) {
+            serviceApi.create(svc);
+        } else {
+            serviceApi.update(svc);
+        }
+        // 检查secret,没有则创建
+        SecretVO secretVO = secretApi.getSecretByName(deploymentNamespace, DEFAULT_IMAGE_PULL_SECRET_NAME);
+        if (secretVO == null) {
+            secretApi.createPrivateRegistrySecret(
+                        deploymentNamespace,
+                        DEFAULT_IMAGE_PULL_SECRET_NAME,
+                        applicationProperties.getK8s().getImageRegistry(),
+                        applicationProperties.getDocker().getRegistryUsername(),
+                        applicationProperties.getDocker().getRegistryPassword()
+                    );
+        }
+        // configMap
+        List<ConfigMap> configMaps = configMapApi.getByCondition(K8sObjectRequest.builder().namespace(deploymentNamespace).labels(labelsMap).build());
+        ConfigMap configMap = ConfigMap.builder().data(configContent.getMounts()).build();
+        configMap.setName(labelValue);
+        configMap.setNamespace(deploymentNamespace);
+        configMap.setLabel(labelKey, labelValue);
+        if (CollectionUtils.isEmpty(configMaps) || configMaps.size() != 1) {
+            configMapApi.create(configMap);
+        } else {
+            configMapApi.update(configMap);
+        }
+        // deployment
+        List<Deployment> deployments = deploymentApi.getByCondition(K8sObjectRequest.builder().namespace(deploymentNamespace).labels(labelsMap).build());
+        Deployment deployment = Deployment.builder()
+                .imagePullSecrets(Collections.singletonList(DEFAULT_IMAGE_PULL_SECRET_NAME))
+                .replicas(configContent.getReplicas() == null ? 1 : configContent.getReplicas())
+                .volumes(Collections.singletonList(new V1VolumeBuilder().withName(labelValue).withConfigMap(
+                        new V1ConfigMapVolumeSourceBuilder().withName(labelValue).withItems(
+                                configMap.getData() == null ? null :
+                                        configMap.getData().keySet().stream().map(path -> new V1KeyToPathBuilder().withKey(path).withPath(path).build()).collect(Collectors.toList())
+                        ).build()
+                ).build()))
+                .containers(Collections.singletonList(
+                        Container.builder()
+                                .env(configContent.getEnvs())
+                                .ports(
+                                        Arrays.stream(ports).map(port ->
+                                                ContainerPort.builder()
+                                                        .name(labelValue + "-" + port)
+                                                        .port(Integer.parseInt(port))
+                                                        .protocol("TCP")
+                                                        .build()).collect(Collectors.toSet())
+                                        )
+                                .args(configContent.getRunArgs() == null ? null : Arrays.asList(configContent.getRunArgs().split(" ")))
+                                .command(configContent.getRunCommands() == null ? null : Arrays.asList(configContent.getRunCommands().split(" ")))
+                                .name(labelValue)
+                                .image(imageName)
+                                .volumeMounts(Collections.singletonList(Container.VolumeMount.builder()
+                                        .name(labelValue)
+                                        .mountPath("/")
+                                        .build()))
+                                .build()))
+                .build();
+        deployment.setName(labelValue);
+        deployment.setNamespace(deploymentNamespace);
+        deployment.setLabel(labelKey, labelValue);
+        if (CollectionUtils.isEmpty(deployments) || deployments.size() != 1) {
+            deploymentApi.create(deployment);
+        } else {
+            deploymentApi.update(deployment);
+        }
+    }
+}

+ 20 - 0
src/main/java/cn/seecoder/paas/service/model/converter/ApplicationConverter.java

@@ -0,0 +1,20 @@
+package cn.seecoder.paas.service.model.converter;
+
+import cn.seecoder.paas.data.entity.Application;
+import cn.seecoder.paas.service.model.vo.ApplicationVO;
+import org.springframework.beans.BeanUtils;
+
+public class ApplicationConverter {
+
+    public static Application convertToEntity(ApplicationVO applicationVO) {
+        Application application = new Application();
+        BeanUtils.copyProperties(applicationVO, application);
+        return application;
+    }
+
+    public static ApplicationVO convertToVO(Application application) {
+        ApplicationVO applicationVO = new ApplicationVO();
+        BeanUtils.copyProperties(application, applicationVO);
+        return applicationVO;
+    }
+}

+ 34 - 0
src/main/java/cn/seecoder/paas/service/model/converter/ConfigConverter.java

@@ -0,0 +1,34 @@
+package cn.seecoder.paas.service.model.converter;
+
+import cn.seecoder.paas.data.entity.Config;
+import cn.seecoder.paas.service.model.vo.ConfigVO;
+import cn.seecoder.paas.util.ServiceException;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.beans.BeanUtils;
+
+public class ConfigConverter {
+
+    public static ConfigVO convertToVO(Config config) throws ServiceException {
+        try {
+            ConfigVO configVO = new ConfigVO();
+            BeanUtils.copyProperties(config, configVO);
+            ConfigVO.ConfigContent configContent = new ObjectMapper().readValue(config.getContent(), ConfigVO.ConfigContent.class);
+            configVO.setConfig(configContent);
+            return configVO;
+        } catch (JsonProcessingException e) {
+            throw ServiceException.INVALID_DATA;
+        }
+    }
+
+    public static Config convertToEntity(ConfigVO configVO) throws ServiceException {
+        try {
+            Config config = new Config();
+            BeanUtils.copyProperties(configVO, config);
+            config.setContent(new ObjectMapper().writeValueAsString(configVO.getConfig()));
+            return config;
+        } catch (JsonProcessingException e) {
+            throw ServiceException.INVALID_DATA;
+        }
+    }
+}

+ 21 - 0
src/main/java/cn/seecoder/paas/service/model/converter/EnvironmentConverter.java

@@ -0,0 +1,21 @@
+package cn.seecoder.paas.service.model.converter;
+
+import cn.seecoder.paas.data.entity.Environment;
+import cn.seecoder.paas.service.model.vo.EnvironmentVO;
+import org.springframework.beans.BeanUtils;
+
+public class EnvironmentConverter {
+
+
+    public static EnvironmentVO convertToVO(Environment environment) {
+        EnvironmentVO environmentVO = new EnvironmentVO();
+        BeanUtils.copyProperties(environment, environmentVO);
+        return environmentVO;
+    }
+
+    public static Environment convertToEntity(EnvironmentVO environmentVO) {
+        Environment environment = new Environment();
+        BeanUtils.copyProperties(environmentVO, environment);
+        return environment;
+    }
+}

+ 22 - 0
src/main/java/cn/seecoder/paas/service/model/vo/ApplicationVO.java

@@ -0,0 +1,22 @@
+package cn.seecoder.paas.service.model.vo;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+@Data
+public class ApplicationVO {
+
+    private Integer id;
+
+    @NotBlank
+    private String name;
+
+    private String description;
+
+    @NotNull
+    private String gitUrl;
+
+    private ConfigVO config;
+}

+ 86 - 0
src/main/java/cn/seecoder/paas/service/model/vo/ConfigVO.java

@@ -0,0 +1,86 @@
+package cn.seecoder.paas.service.model.vo;
+
+import cn.seecoder.paas.util.enums.ConfigBelongsToType;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Collections;
+import java.util.Map;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class ConfigVO {
+
+    private Integer id;
+
+    private ConfigBelongsToType configBelongs;
+
+    private Integer entityId;
+
+    private ConfigContent config;
+
+    private static final ConfigContent EMPTY_CONFIG_CONTENT;
+
+    static {
+        EMPTY_CONFIG_CONTENT = new ConfigContent(null, Collections.emptyMap(), null, null, Collections.emptyMap(), null, null);
+    }
+
+    public static ConfigVO getEmptyConfigVO(ConfigBelongsToType configBelongs, Integer entityId) {
+        ConfigVO configVO = new ConfigVO();
+        configVO.setEntityId(entityId);
+        configVO.setConfigBelongs(configBelongs);
+        configVO.setConfig(EMPTY_CONFIG_CONTENT);
+        return configVO;
+    }
+
+    @Data
+    @AllArgsConstructor
+    @NoArgsConstructor
+    public static class ConfigContent {
+        /**
+         * 服务端口
+         */
+        @JsonProperty("servicePort")
+        private String servicePort;
+
+        /**
+         * 环境变量配置
+         */
+        @JsonProperty("envs")
+        private Map<String, String> envs;
+
+        /**
+         * 启动参数配置 CMD space split
+         */
+        @JsonProperty("runArgs")
+        private String runArgs;
+
+        /**
+         * 运行命令配置 ENTRYPOINT space split
+         */
+        @JsonProperty("runCommands")
+        private String runCommands;
+
+        /**
+         * 挂载文件配置
+         */
+        @JsonProperty("mounts")
+        private Map<String, String> mounts;
+
+        @JsonProperty("hostPrefix")
+        private String hostPrefix;
+
+        @JsonProperty("replicas")
+        private Integer replicas;
+
+        @JsonIgnore()
+        public boolean isValid() {
+            return servicePort != null && hostPrefix != null;
+        }
+    }
+
+}

+ 36 - 0
src/main/java/cn/seecoder/paas/service/model/vo/EnvironmentVO.java

@@ -0,0 +1,36 @@
+package cn.seecoder.paas.service.model.vo;
+
+import cn.seecoder.paas.service.facade.k8s.model.Pod;
+import cn.seecoder.paas.util.enums.BuildStatus;
+import cn.seecoder.paas.util.enums.BuildType;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@Data
+public class EnvironmentVO {
+
+    private Integer id;
+
+    @NotBlank
+    private String name;
+
+    @NotNull
+    private Integer appId;
+
+    @NotNull
+    private BuildType buildType;
+
+    @NotNull
+    private String buildTypeValue;
+
+    private BuildStatus buildStatus;
+
+    private String buildOutput;
+
+    private ConfigVO config;
+
+    private List<Pod> instance;
+}

+ 55 - 0
src/main/java/cn/seecoder/paas/util/ApplicationProperties.java

@@ -0,0 +1,55 @@
+package cn.seecoder.paas.util;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Data
+@Configuration
+@ConfigurationProperties("paas")
+public class ApplicationProperties {
+
+    private String deploymentNamespace;
+
+    private String deploymentHost;
+
+    private K8s k8s = new K8s();
+
+    private Git git = new Git();
+
+    private Docker docker = new Docker();
+
+    @Data
+    public static class K8s {
+
+        private String apiServer;
+
+        private String token;
+
+        private boolean validateSSL = false;
+
+        private boolean debug = true;
+
+        private String imageRegistry;
+    }
+
+    @Data
+    public static class Git {
+
+        private String username;
+
+        private String password;
+    }
+
+    @Data
+    public static class Docker {
+
+        private String host;
+
+        private String registry;
+
+        private String registryUsername;
+
+        private String registryPassword;
+    }
+}

+ 26 - 0
src/main/java/cn/seecoder/paas/util/AsyncConfig.java

@@ -0,0 +1,26 @@
+package cn.seecoder.paas.util;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.ThreadPoolExecutor;
+
+@Configuration
+@EnableAsync
+public class AsyncConfig {
+
+    @Bean("asyncExecutor")
+    public Executor executor() {
+        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+        executor.setCorePoolSize(10);
+        executor.setMaxPoolSize(10);
+        executor.setQueueCapacity(Integer.MAX_VALUE);
+        executor.setThreadNamePrefix("seecoder-paas-async-");
+        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+        executor.initialize();
+        return executor;
+    }
+}

+ 34 - 0
src/main/java/cn/seecoder/paas/util/AsyncWrapper.java

@@ -0,0 +1,34 @@
+package cn.seecoder.paas.util;
+
+import lombok.extern.apachecommons.CommonsLog;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Component;
+
+@Component
+@CommonsLog
+public class AsyncWrapper {
+
+    @FunctionalInterface
+    public interface Method {
+        void invoke() throws Exception;
+    }
+
+    @Async("asyncExecutor")
+    public void asyncInvoke(Method method) {
+        try {
+            method.invoke();
+        } catch (Exception e) {
+            log.error("进行异步调用时发生异常[" + e.getClass() + "]:" + e.getMessage());
+        }
+    }
+
+    @Async("asyncExecutor")
+    public void asyncInvoke(Method method, long delay) {
+        try {
+            Thread.sleep(delay);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+        }
+        asyncInvoke(method);
+    }
+}

+ 18 - 0
src/main/java/cn/seecoder/paas/util/DateUtil.java

@@ -0,0 +1,18 @@
+package cn.seecoder.paas.util;
+
+import org.joda.time.DateTime;
+
+import java.time.LocalDateTime;
+
+public class DateUtil {
+
+    public static LocalDateTime fromJodaDateTime(DateTime dateTime) {
+        return LocalDateTime.of(
+                dateTime.getYear(),
+                dateTime.getMonthOfYear(),
+                dateTime.getDayOfMonth(),
+                dateTime.getHourOfDay(),
+                dateTime.getMinuteOfHour(),
+                dateTime.getSecondOfMinute());
+    }
+}

+ 114 - 0
src/main/java/cn/seecoder/paas/util/K8sApiConfiguration.java

@@ -0,0 +1,114 @@
+package cn.seecoder.paas.util;
+
+import io.kubernetes.client.Exec;
+import io.kubernetes.client.openapi.ApiClient;
+import io.kubernetes.client.openapi.apis.AppsV1Api;
+import io.kubernetes.client.openapi.apis.CoreV1Api;
+import io.kubernetes.client.openapi.apis.ExtensionsV1beta1Api;
+import io.kubernetes.client.openapi.models.V1Pod;
+import io.kubernetes.client.openapi.models.V1PodList;
+import io.kubernetes.client.util.Config;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+
+@Configuration
+public class K8sApiConfiguration {
+
+    private final ApiClient apiClient;
+
+    private final CoreV1Api coreV1Api;
+
+    private final AppsV1Api appsV1Api;
+
+    private final ExtensionsV1beta1Api extensionsV1beta1Api;
+
+    private final Exec exec;
+
+    /**
+     * http读取的超时时间设置为7分钟
+     */
+    private final ApiClient watchApiClient;
+
+    private final CoreV1Api watchCoreV1Api;
+
+    private final AppsV1Api watchAppsV1Api;
+
+    @Autowired
+    public K8sApiConfiguration(ApplicationProperties applicationProperties) {
+        ApplicationProperties.K8s k8s = applicationProperties.getK8s();
+        this.apiClient = Config.fromToken(k8s.getApiServer(), k8s.getToken(), k8s.isValidateSSL());
+        this.apiClient.setDebugging(k8s.isDebug());
+        io.kubernetes.client.openapi.Configuration.setDefaultApiClient(this.apiClient);
+        this.coreV1Api = new CoreV1Api();
+        this.appsV1Api = new AppsV1Api();
+        this.extensionsV1beta1Api = new ExtensionsV1beta1Api();
+        this.exec = new Exec();
+
+
+        this.watchApiClient = Config.fromToken(k8s.getApiServer(), k8s.getToken(), k8s.isValidateSSL());
+        this.watchCoreV1Api = new CoreV1Api(watchApiClient);
+        this.watchAppsV1Api = new AppsV1Api(watchApiClient);
+
+        try {
+            V1PodList v1PodList = coreV1Api.listPodForAllNamespaces(
+                    null,
+                    null,
+                    null,
+                    null,
+                    null,
+                    null,
+                    null,
+                    null,
+                    null
+                    );
+            for (V1Pod item : v1PodList.getItems()) {
+                System.out.println(item.getMetadata().getName());
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Bean
+    public ApiClient apiClient() {
+        return apiClient;
+    }
+
+    @Bean
+    public CoreV1Api coreV1Api() {
+        return coreV1Api;
+    }
+
+    @Bean
+    public AppsV1Api appsV1Api() {
+        return appsV1Api;
+    }
+
+    @Bean
+    public ExtensionsV1beta1Api extensionsV1beta1Api() {
+        return extensionsV1beta1Api;
+    }
+
+    @Bean
+    public Exec exec() {
+        return exec;
+    }
+
+
+    @Bean
+    public ApiClient watchApiClient() {
+        return watchApiClient;
+    }
+
+    @Bean
+    public CoreV1Api watchCoreV1Api() {
+        return watchCoreV1Api;
+    }
+
+    @Bean
+    public AppsV1Api watchAppsV1Api() {
+        return watchAppsV1Api;
+    }
+}

+ 42 - 0
src/main/java/cn/seecoder/paas/util/LoggerUtil.java

@@ -0,0 +1,42 @@
+package cn.seecoder.paas.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * author: rale
+ * createdAt: 12/22/18
+ * 日志工具
+ */
+public class LoggerUtil {
+
+    public static Logger getLogger(Class<?> className){
+        return LoggerFactory.getLogger(className);
+    }
+
+    public static void info(Logger logger, String info, Object... parameters){
+        logger.info(info, parameters);
+    }
+
+    public static void warn(Logger logger, String info, Object... parameters){
+        logger.warn(info, parameters);
+    }
+
+    public static void warn(Logger logger, Exception e, String info, Object... parameters) {
+        logger.warn("", e);
+        logger.warn(info, parameters);
+    }
+
+    public static void  error(Logger logger, String info, Object... parameters) {
+        logger.error(info, parameters);
+    }
+
+    public static void error(Logger logger, Exception e, String info, Object... parameters) {
+        logger.error("", e);
+        logger.error(info, parameters);
+    }
+
+    public static void debug(Logger logger, String info, Object... parameters) {
+        logger.debug(info, parameters);
+    }
+}

+ 27 - 0
src/main/java/cn/seecoder/paas/util/ServiceException.java

@@ -0,0 +1,27 @@
+package cn.seecoder.paas.util;
+
+import lombok.Getter;
+
+@Getter
+public class ServiceException extends Exception {
+
+    public static final ServiceException INVALID_DATA = new ServiceException("400", "不合法的数据");
+
+    public static final ServiceException BAD_REQUEST = new ServiceException("400", "不合法的请求");
+
+    private String code;
+
+    private String msg;
+
+    public ServiceException(String code, String msg) {
+        super(msg);
+        this.code = code;
+        this.msg = msg;
+    }
+
+    public ServiceException(String code, String msg, Throwable cause) {
+        super(msg, cause);
+        this.code = code;
+        this.msg = msg;
+    }
+}

+ 28 - 0
src/main/java/cn/seecoder/paas/util/enums/BuildStatus.java

@@ -0,0 +1,28 @@
+package cn.seecoder.paas.util.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public enum BuildStatus {
+
+    NOT_STARTED("NOT_STARTED", "构建未开始"),
+    SUCCESS("SUCCESS", "构建成功"),
+    BUILDING("BUILDING", "正在构建"),
+    FAIL("FAIL", "构建失败"),
+    ;
+
+    private String code;
+
+    private String desc;
+
+    public static BuildStatus getByCode(String code) {
+        for (BuildStatus objs : BuildStatus.values()) {
+            if (objs.getCode().equals(code)) {
+                return objs;
+            }
+        }
+        return null;
+    }
+}

+ 27 - 0
src/main/java/cn/seecoder/paas/util/enums/BuildType.java

@@ -0,0 +1,27 @@
+package cn.seecoder.paas.util.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+public enum BuildType {
+
+    FROM_BRANCH_OR_COMMIT("FROM_BRANCH_OR_COMMIT", "从分支或commit构建"),
+
+    FROM_IMAGE("FROM_IMAGE", "从现有镜像构建"),
+    ;
+
+    private String code;
+
+    private String desc;
+
+    public static BuildType getByCode(String code) {
+        for (BuildType objs : BuildType.values()) {
+            if (objs.getCode().equals(code)) {
+                return objs;
+            }
+        }
+        return null;
+    }
+}

+ 27 - 0
src/main/java/cn/seecoder/paas/util/enums/ConfigBelongsToType.java

@@ -0,0 +1,27 @@
+package cn.seecoder.paas.util.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+public enum ConfigBelongsToType {
+
+    APPLICATION("application", "应用"),
+
+    ENVIRONMENT("environment", "环境"),
+    ;
+
+    private String code;
+
+    private String desc;
+
+    public static ConfigBelongsToType getByCode(String code) {
+        for (ConfigBelongsToType objs : ConfigBelongsToType.values()) {
+            if (objs.getCode().equals(code)) {
+                return objs;
+            }
+        }
+        return null;
+    }
+}

+ 37 - 0
src/main/java/cn/seecoder/paas/util/enums/ResourceLabel.java

@@ -0,0 +1,37 @@
+package cn.seecoder.paas.util.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public enum ResourceLabel {
+
+    RESOURCE("paas.seecoder.cn/resource", "表明该k8sObject隶属于paas的哪个资源", (String ...args) -> {
+        // resource
+        return String.join("-", args);
+    }),
+
+    ;
+
+    private String code;
+
+    private String desc;
+
+    private ResourceLabelValueGenerator generator;
+
+    public static ConfigBelongsToType getByCode(String code) {
+        for (ConfigBelongsToType objs : ConfigBelongsToType.values()) {
+            if (objs.getCode().equals(code)) {
+                return objs;
+            }
+        }
+        return null;
+    }
+
+    @FunctionalInterface
+    public interface ResourceLabelValueGenerator {
+
+        String gen(String... args);
+    }
+}

+ 44 - 0
src/main/java/cn/seecoder/paas/web/CustomerExceptionHandler.java

@@ -0,0 +1,44 @@
+package cn.seecoder.paas.web;
+
+import cn.seecoder.paas.service.facade.k8s.exception.K8sApiException;
+import cn.seecoder.paas.util.ServiceException;
+import cn.seecoder.paas.web.model.Response;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import javax.servlet.http.HttpServletRequest;
+
+
+@ControllerAdvice
+public class CustomerExceptionHandler {
+
+    private static final Logger logger = LoggerFactory.getLogger(CustomerExceptionHandler.class);
+
+    @ExceptionHandler(value = ServiceException.class)
+    @ResponseBody
+    public Response serviceExceptionHandler(HttpServletRequest req, ServiceException e) {
+        e.printStackTrace();
+        logger.error("Error: ", e);
+        return Response.buildFailure(e);
+    }
+
+
+    @ExceptionHandler(value = K8sApiException.class)
+    @ResponseBody
+    public Response exceptionHandler(HttpServletRequest req, K8sApiException e){
+        e.printStackTrace();
+        logger.error("Error: ", e);
+        return Response.buildFailure(String.valueOf(e.getCode()), e.getDescription());
+    }
+
+    @ExceptionHandler(value = Exception.class)
+    @ResponseBody
+    public Response exceptionHandler(HttpServletRequest req, Exception e){
+        e.printStackTrace();
+        logger.error("Error: ", e);
+        return Response.buildFailure(e);
+    }
+}

+ 15 - 0
src/main/java/cn/seecoder/paas/web/WebSecurityConfig.java

@@ -0,0 +1,15 @@
+package cn.seecoder.paas.web;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+
+@Configuration
+@EnableWebSecurity
+public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
+    @Override
+    protected void configure(HttpSecurity http) throws Exception {
+        http.csrf().disable();
+    }
+}

+ 46 - 0
src/main/java/cn/seecoder/paas/web/controller/api/ApplicationController.java

@@ -0,0 +1,46 @@
+package cn.seecoder.paas.web.controller.api;
+
+import cn.seecoder.paas.service.ApplicationService;
+import cn.seecoder.paas.service.model.vo.ApplicationVO;
+import cn.seecoder.paas.util.ServiceException;
+import cn.seecoder.paas.web.model.Response;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/api/application")
+public class ApplicationController {
+
+    private final ApplicationService applicationService;
+
+    @Autowired
+    public ApplicationController(ApplicationService applicationService) {
+        this.applicationService = applicationService;
+    }
+
+    @PostMapping("")
+    public Response create(@RequestBody  ApplicationVO vo) throws ServiceException {
+        return Response.buildSuccess(applicationService.createOrUpdate(vo));
+    }
+
+    @PutMapping("")
+    public Response update(@RequestBody  ApplicationVO vo) throws ServiceException {
+        return Response.buildSuccess(applicationService.createOrUpdate(vo));
+    }
+
+    @GetMapping("")
+    public Response getAll() {
+        return Response.buildSuccess(applicationService.getAll());
+    }
+
+    @GetMapping("/{id}")
+    public Response get(@PathVariable("id") Integer id) throws ServiceException {
+        return Response.buildSuccess(applicationService.getDetailById(id));
+    }
+
+    @DeleteMapping("/{id}")
+    public Response delete(@PathVariable("id") Integer id) {
+        applicationService.delete(id);
+        return Response.buildSuccess(null);
+    }
+}

+ 28 - 0
src/main/java/cn/seecoder/paas/web/controller/api/ConfigController.java

@@ -0,0 +1,28 @@
+package cn.seecoder.paas.web.controller.api;
+
+import cn.seecoder.paas.service.ConfigService;
+import cn.seecoder.paas.service.model.vo.ConfigVO;
+import cn.seecoder.paas.util.ServiceException;
+import cn.seecoder.paas.web.model.Response;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/api/config")
+public class ConfigController {
+
+    private final ConfigService configService;
+
+    @Autowired
+    public ConfigController(ConfigService configService) {
+        this.configService = configService;
+    }
+
+    @PutMapping("")
+    public Response update(@RequestBody ConfigVO configVO) throws ServiceException {
+        return Response.buildSuccess(configService.createOrUpdate(configVO));
+    }
+}

+ 60 - 0
src/main/java/cn/seecoder/paas/web/controller/api/EnvironmentController.java

@@ -0,0 +1,60 @@
+package cn.seecoder.paas.web.controller.api;
+
+import cn.seecoder.paas.service.EnvironmentService;
+import cn.seecoder.paas.service.model.vo.EnvironmentVO;
+import cn.seecoder.paas.util.ServiceException;
+import cn.seecoder.paas.web.model.Response;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+
+@RestController
+@RequestMapping("/api/environment")
+public class EnvironmentController {
+
+    private final EnvironmentService environmentService;
+
+    @Autowired
+    public EnvironmentController(EnvironmentService environmentService) {
+        this.environmentService = environmentService;
+    }
+
+    @PostMapping("")
+    public Response create(@Valid @RequestBody EnvironmentVO environmentVO) throws ServiceException {
+        return Response.buildSuccess(environmentService.createOrUpdate(environmentVO));
+    }
+
+    @PutMapping("")
+    public Response update(@Valid @RequestBody EnvironmentVO environmentVO) throws ServiceException {
+        return Response.buildSuccess(environmentService.createOrUpdate(environmentVO));
+    }
+
+    @GetMapping("")
+    public Response getAll(@RequestParam Integer appId) {
+        return Response.buildSuccess(environmentService.getListByAppId(appId));
+    }
+
+    @GetMapping("/{id}")
+    public Response get(@PathVariable("id") Integer id) throws ServiceException {
+        return Response.buildSuccess(environmentService.get(id));
+    }
+
+    @DeleteMapping("/{id}")
+    public Response delete(@PathVariable("id") Integer id) {
+        environmentService.delete(id);
+        return Response.buildSuccess(null);
+    }
+
+    @GetMapping("/log")
+    public Response getLog(@RequestParam Integer id, @RequestParam String podName) throws ServiceException {
+        return Response.buildSuccess(
+                environmentService.getLog(id, podName));
+    }
+
+    @PostMapping("/deploy")
+    public Response deploy(@RequestParam Integer id) throws ServiceException {
+        return Response.buildSuccess(
+                environmentService.deploy(id));
+    }
+}

+ 38 - 0
src/main/java/cn/seecoder/paas/web/model/Response.java

@@ -0,0 +1,38 @@
+package cn.seecoder.paas.web.model;
+
+import cn.seecoder.paas.util.ServiceException;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class Response {
+
+    private String code;
+
+    private String msg;
+
+    private Object result;
+
+    public static Response buildSuccess(Object result) {
+        return new Response("0", "", result);
+    }
+
+    public static  Response buildFailure(ServiceException e) {
+        return new Response(e.getCode(), e.getMsg(), null);
+    }
+
+    public static  Response buildFailure(Exception e) {
+        return new Response("-1", e.getLocalizedMessage(), null);
+    }
+
+    public static Response buildFailure(String code, String message) {
+        return new Response(code, message, null);
+    }
+
+    public static Response buildFailure(String message) {
+        return new Response("-1", message, null);
+    }
+}

+ 36 - 0
src/main/resources/application.yml

@@ -0,0 +1,36 @@
+server:
+  port: 8080
+spring:
+  datasource:
+    url: jdbc:mysql://172.19.240.15:3306/seecoder_paas?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false
+    username: root
+    password: 1qaz2wsx
+  jpa:
+    hibernate:
+      ddl-auto: update
+      naming:
+        implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
+        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
+    database-platform: org.hibernate.dialect.MySQL57InnoDBDialect
+  security:
+    user:
+      name: root
+      password: NJU67software
+
+paas:
+  git:
+    username: WangHongKai
+    password: 1!2@3#4$
+  docker:
+    host: unix://var/run/docker.sock
+    registry: nexus-api.seecii.cn:80
+    registryUsername: admin
+    registryPassword: admin123
+  k8s:
+    api-server: http://k8s-apiserver.seecii.cn
+    token: eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi10b2tlbi1zdzlwOCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJhZG1pbiIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6ImY3MzQ2NzVlLWZmYmItMTFlOS1iYTdjLTIyNmUyZmY2ZTdlYyIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDprdWJlLXN5c3RlbTphZG1pbiJ9.GvBP7iNM_WNJN7dPm8ZPszDJeykifhHwBYcF58c4W73iAet1ZcZPrmiurqn4mSHcavUHNR8FqPaafnS3OD4eq5dR-i7frdIBt7yiME7AIGA4pDKWNJ3NykhyfG75omCvtQ2Gp-UvFIdf_4peAilxbwDR2NtWpky4sJQOTukZIYzN9fCeYV8My85XDBl7ze20QBsEKfPxTGZkqmytX-4VmjKkQH9ob7YB90NR2ug5VK8CA16_2ZPjf8Fx4QsdTbkyZKdFo7zIrIID0fa2lpOc2TpCKWeYCl335-ULllpxds6kiVwkRXYP-5zkIOfpI_0SshK7kbBjd238OLM3KFDf6A
+    validateSSL: false
+    debug: true
+    imageRegistry: 192.168.68.79:18082
+  deployment-namespace: seec
+  deployment-host: seecii.cn

+ 8 - 0
src/main/resources/docker-config.json

@@ -0,0 +1,8 @@
+{
+  "auths": {
+    "nexus-api.seecii.cn:80": {
+      "username": "admin",
+      "password": "admin123"
+    }
+  }
+}

+ 13 - 0
src/test/java/cn/seecoder/paas/SeecoderPaasApplicationTests.java

@@ -0,0 +1,13 @@
+package cn.seecoder.paas;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class SeecoderPaasApplicationTests {
+
+    @Test
+    void contextLoads() {
+    }
+
+}