initial revision.
authorErik Brakkee <erik@brakkee.org>
Wed, 21 Sep 2022 19:05:44 +0000 (21:05 +0200)
committerErik Brakkee <erik@brakkee.org>
Wed, 21 Sep 2022 19:05:44 +0000 (21:05 +0200)
12 files changed:
resources/podtemplates/google.yaml [new file with mode: 0644]
resources/podtemplates/jnlp.yaml [new file with mode: 0644]
resources/podtemplates/kaniko.yaml [new file with mode: 0644]
resources/scripts/processtemplates.groovy [new file with mode: 0644]
src/org/wamblee/jenkins/pipelinelib/MyYaml.groovy [new file with mode: 0644]
vars/agentsetup.groovy [new file with mode: 0644]
vars/applicationYaml.groovy [new file with mode: 0644]
vars/buildcontainer.groovy [new file with mode: 0644]
vars/echotest.groovy [new file with mode: 0644]
vars/k8sagent.groovy [new file with mode: 0644]
vars/processresources.groovy [new file with mode: 0644]
vars/renderTemplate.groovy [new file with mode: 0644]

diff --git a/resources/podtemplates/google.yaml b/resources/podtemplates/google.yaml
new file mode 100644 (file)
index 0000000..b2e1c3e
--- /dev/null
@@ -0,0 +1,16 @@
+apiVersion: v1
+kind: Pod
+spec:
+  containers:
+    - name: google
+      image: google/cloud-sdk
+      #imagePullPolicy: Always
+      # this or any command that is
+      # bascially a noop is required, this is so that you don't overwrite the
+      # entrypoint of the base container
+      command: ["tail", "-f", "/dev/null"]
+      resources:
+        requests:
+          memory: "512M"
+          cpu: "500m"
+
diff --git a/resources/podtemplates/jnlp.yaml b/resources/podtemplates/jnlp.yaml
new file mode 100644 (file)
index 0000000..1e66b60
--- /dev/null
@@ -0,0 +1,9 @@
+apiVersion: v1
+kind: Pod
+spec:
+  containers:
+    - name: jnlp
+      resources:
+        requests:
+          memory: "600M"
+          cpu: "550m"
diff --git a/resources/podtemplates/kaniko.yaml b/resources/podtemplates/kaniko.yaml
new file mode 100644 (file)
index 0000000..96b8a99
--- /dev/null
@@ -0,0 +1,16 @@
+apiVersion: v1
+kind: Pod
+spec:
+  containers:
+    - name: kaniko
+      image: gcr.io/kaniko-project/executor:debug
+      #imagePullPolicy: Always
+      command:
+        - /busybox/cat
+      tty: true
+      resources:
+        requests:
+          memory: "2048M"
+          cpu: "1000m"
+          ephemeral-storage: "5Gi"
+
diff --git a/resources/scripts/processtemplates.groovy b/resources/scripts/processtemplates.groovy
new file mode 100644 (file)
index 0000000..0530481
--- /dev/null
@@ -0,0 +1,26 @@
+import groovy.io.FileType
+
+variables = System.getenv()
+
+File dir = new File(".")
+
+def files = [] 
+dir.eachFileRecurse FileType.FILES, { 
+  if (it.name =~ /\.template$/ ) { 
+    files << it 
+  } 
+}
+
+println(variables)
+for (file in files) { 
+  println(file)
+  content = file.text
+  
+  def engine = new groovy.text.SimpleTemplateEngine()
+  content = engine.createTemplate(content).make(variables).toString()
+  print("new content:" + content)
+  target = new File(file.absolutePath.replaceAll('\\.template$', ""))
+  target.text = content
+}
+
+
diff --git a/src/org/wamblee/jenkins/pipelinelib/MyYaml.groovy b/src/org/wamblee/jenkins/pipelinelib/MyYaml.groovy
new file mode 100644 (file)
index 0000000..733e9b7
--- /dev/null
@@ -0,0 +1,130 @@
+package org.wamblee.jenkins.pipelinelib
+
+@Grab('org.yaml:snakeyaml:1.25')
+
+import org.yaml.snakeyaml.Yaml
+import org.yaml.snakeyaml.constructor.SafeConstructor
+
+/*
+  Original reference:
+  https://github.com/OndraZizka/yaml-merge/blob/master/src/main/java/org/cobbzilla/util/yml/YmlMerger.java
+ */
+
+class MyYaml {
+  private final Yaml parser
+
+  MyYaml() {
+    parser = new Yaml(new SafeConstructor())
+  }
+
+  String merge(List<String> yamls) {
+    Map<String, Object> mergedResult = new LinkedHashMap<String, Object>();
+    for (yaml in yamls) {
+      final Map<String, Object> yamlToMerge = parser.load(yaml)
+      // Merge into results map.
+      mergeStructures(mergedResult, yamlToMerge)
+    }
+    return parser.dump(mergedResult)
+  }
+
+  private static Object addToMergedResult(Map<String, Object> mergedResult, String key, Object yamlValue) {
+    return mergedResult.put(key, yamlValue)
+  }
+
+  private static IllegalArgumentException unknownValueType(String key, Object yamlValue) {
+    final String msg = "Cannot mergeYamlFiles element of unknown type: " + key + ": " + yamlValue.getClass().getName()
+    return new IllegalArgumentException(msg)
+  }
+
+  private void mergeLists(Map<String, Object> mergedResult, String key, Object yamlValue) {
+    if (!(yamlValue instanceof List && mergedResult.get(key) instanceof List)) {
+      throw new IllegalArgumentException("Cannot mergeYamlFiles a list with a non-list: " + key)
+    }
+
+    List<Object> originalList = (List<Object>) mergedResult.get(key)
+
+    // original implementation
+    // originalList.addAll((List<Object>) yamlValue)
+
+    // my implementation
+    // below is non-standard approach as I assume a key:value mapping called (name->value) to identify a Map
+    List<Object> yamlList = (List<Object>) yamlValue
+    Map<String, Object> originalCache = new LinkedHashMap<>()
+    String name
+    for (ori in originalList) {
+      if (ori instanceof Map) {
+        name = ori.get('name')
+        if (name) {
+          originalCache.put(name, ori)
+        }
+      }
+    }
+
+    def merged
+    for (item in yamlList) {
+      merged = false
+      if (item instanceof Map) {
+        name = item.get('name')
+        if (name && originalCache.containsKey(name)) {
+          mergeStructures((Map<String, Object>) originalCache.get(name), (Map<String, Object>) item)
+          merged = true
+        }
+      }
+      if (!merged) {
+        originalList.add(item)
+      }
+    }
+  }
+
+  private void mergeStructures(Map<String, Object> targetTree, Map<String, Object> sourceTree) {
+    if (sourceTree == null) return
+
+    for (String key : sourceTree.keySet()) {
+
+      Object yamlValue = sourceTree.get(key)
+      if (yamlValue == null) {
+        addToMergedResult(targetTree, key, yamlValue)
+        continue
+      }
+
+      Object existingValue = targetTree.get(key);
+      if (existingValue != null) {
+        if (yamlValue instanceof Map) {
+          if (existingValue instanceof Map) {
+            mergeStructures((Map<String, Object>) existingValue, (Map<String, Object>) yamlValue);
+          } else if (existingValue instanceof String) {
+            throw new IllegalArgumentException("Cannot mergeYamlFiles complex element into a simple element: " + key)
+          } else {
+            throw unknownValueType(key, yamlValue)
+          }
+        } else if (yamlValue instanceof List) {
+          mergeLists(targetTree, key, yamlValue)
+
+        } else if (yamlValue instanceof String
+            || yamlValue instanceof Boolean
+            || yamlValue instanceof Double
+            || yamlValue instanceof Integer) {
+
+          addToMergedResult(targetTree, key, yamlValue)
+
+        } else {
+          throw unknownValueType(key, yamlValue)
+        }
+
+      } else {
+        if (yamlValue instanceof Map
+            || yamlValue instanceof List
+            || yamlValue instanceof String
+            || yamlValue instanceof Boolean
+            || yamlValue instanceof Integer
+            || yamlValue instanceof Double) {
+
+          addToMergedResult(targetTree, key, yamlValue)
+        } else {
+          throw unknownValueType(key, yamlValue)
+        }
+      }
+    }
+  }
+}
+
diff --git a/vars/agentsetup.groovy b/vars/agentsetup.groovy
new file mode 100644 (file)
index 0000000..ffa1d36
--- /dev/null
@@ -0,0 +1,69 @@
+// inspired/copied from https://github.com/liejuntao001/jenkins-k8sagent-lib
+
+import org.wamblee.jenkins.pipelinelib.MyYaml
+
+// containers: comma-separated list of containers to include. The order of the containers i 
+//    important. Each container in the list corresponds to a yaml file in the podtemplates resource
+//    resource directory. 
+// repo: docker repo, default is the repo configured in the CONTAINER_REGISTRY environment variable
+// version: version to use, defaults to BRANCH_NAME 
+// label: label to use for the agent. Defaults to the stage name if the agent is configured within a stage,
+//      otherwise the job name is used. 
+//
+// All of the arguments specified in the call to agentsetup are passed without change to the 
+// pod template files and can be accessed in the yaml file as ${name} where name is the 
+// argument name. 
+def call(Map args) { 
+  def defaults = [ 
+    version: env.BRANCH_NAME,
+    repo: env.CONTAINER_REGISTRY,
+    label: env.STAGE_NAME ? env.STAGE_NAME: env.JOB_NAME,
+    idleMinutes: 0,
+    defaultContainer: null // initialized to first container later on
+  ]
+  args.label = env.JOB_NAME
+  if (env.STAGE_NAME) { 
+    args.label = args.label + "-" + env.STAGE_NAME
+  } 
+  args = defaults << args
+    
+  // combine the configured application templates 
+  
+  def containers = args.containers.split(',').toList()
+  // always include the jnlp container to increase resource requirements.
+  // Otherwise, the checkout will be slow because of the limite amount of cpu that is 
+  // reserverd. 
+  if (!args.defaultContainer) { 
+    args.defaultContainer = containers[0]
+  }
+  containers = containers.plus(0, 'jnlp')
+  
+  args.label = args.label.toLowerCase().replaceAll("[^a-zA-Z0-9]", "-").replaceAll("-+", "-")
+  println("agentsetup: containers to include: " + containers)
+  println("agentsetup: label '${args.label}'")
+  
+  
+  // all args are available to the templates 
+  Map template_vars = args
+  
+  def templates = []
+  for (container in containers ) { 
+    template = libraryResource 'podtemplates/' + container + '.yaml'
+    template = renderTemplate(template, template_vars)
+    templates.add(template)
+  }
+
+  def myyaml = new MyYaml()
+  def final_template = myyaml.merge(templates)
+
+  ret = [:]
+  ret.idleMinutes = args.idleMinutes
+  ret.label = args.label  
+  ret.yaml = final_template
+  ret.defaultContainer = args.defaultContainer
+  
+  println('agentsetup: parameters returned' + ret); 
+  
+  ret 
+                      
+}
diff --git a/vars/applicationYaml.groovy b/vars/applicationYaml.groovy
new file mode 100644 (file)
index 0000000..ead38a5
--- /dev/null
@@ -0,0 +1,22 @@
+
+def call(Map args) { 
+  def defaults = [ 
+    version: env.BRANCH_NAME,
+    repo: 'europe-west3-docker.pkg.dev/prod-cobundu-datascience-eu/ds',
+  ]
+  args = defaults << args
+  """
+apiVersion: v1
+kind: Pod
+spec:
+  containers:
+    - name: ${args.application}
+      image: ${args.repo}/${args.application}:${args.version}
+      imagePullPolicy: Always
+      command: ["tail", "-f", "/dev/null"]
+      resources:
+        requests:
+          memory: "512M"
+          cpu: "2000m"
+                    """
+}
diff --git a/vars/buildcontainer.groovy b/vars/buildcontainer.groovy
new file mode 100644 (file)
index 0000000..69763ce
--- /dev/null
@@ -0,0 +1,28 @@
+// Builds a docker container. This requires the kaniko container to be included using agentsetup
+//
+// Mandatory arguments:
+// - context: the relative path in the source repository to the directory where the docker file is located
+// - container: the name of the container to build
+//
+// Optional arguments:
+// - cache: Whether to use the cache for building containers. Defaults to true
+// - dockerfile: Name of the docker file in the context directory. Defaults to Dockerfile
+// - repo: Repository to publish container to. Defaults to the CONTAINER_REGISTRY environment variable
+// - version: Container version to build. Default to the value of the BRANCH_NAME variable 
+//
+def call(Map args) { 
+  def defaults = [ 
+    cache: true, 
+    cachettl: "100000h",
+    dockerfile: 'Dockerfile', 
+    repo: env.CONTAINER_REGISTRY,
+    version: env.BRANCH_NAME
+  ]
+  args = defaults << args
+  container('kaniko') { 
+    sh """
+      echo "Building container with settings: ${args}"
+      /kaniko/executor --dockerfile ${args.dockerfile} --cache=${args.cache} --cache-ttl=${args.cachettl} --context \$(  pwd )/${args.context} --destination ${args.repo}/${args.container}:${args.version}
+    """
+  }
+}
diff --git a/vars/echotest.groovy b/vars/echotest.groovy
new file mode 100644 (file)
index 0000000..3b32f2e
--- /dev/null
@@ -0,0 +1,7 @@
+
+
+def call(String name) { 
+  echo "Hello, ${name}"
+}
+
+
diff --git a/vars/k8sagent.groovy b/vars/k8sagent.groovy
new file mode 100644 (file)
index 0000000..1082731
--- /dev/null
@@ -0,0 +1,32 @@
+
+def call(Map args) { 
+  def defaults = [ 
+    version: env.BRANCH_NAME,
+    repo: 'europe-west3-docker.pkg.dev/prod-cobundu-datascience-eu/ds',
+  ]
+  args = defaults << args
+  ret = [:]
+  ret["label"] = args.application + ":" + args.version
+  ret["yaml"] = """
+apiVersion: v1
+kind: Pod
+spec:
+  containers:
+    - name: ${args.application}
+      image: ${args.repo}/${args.application}:${args.version}
+      imagePullPolicy: Always
+      command: ["tail", "-f", "/dev/null"]
+      resources:
+        requests:
+          memory: "512M"
+          cpu: "2000m"
+    - name: jnlp
+      resources:
+        requests:
+          memory: "600M"
+          cpu: "550m"
+      
+"""
+  ret 
+                      
+}
diff --git a/vars/processresources.groovy b/vars/processresources.groovy
new file mode 100644 (file)
index 0000000..742b734
--- /dev/null
@@ -0,0 +1,58 @@
+// inspired/copied from https://github.com/liejuntao001/jenkins-k8sagent-lib
+
+import org.wamblee.jenkins.pipelinelib.MyYaml
+
+// containers: comma-separated list of containers to include. The order of the containers i 
+//    important. Each container in the list corresponds to a yaml file in the podtemplates resource
+//    resource directory. 
+// repo: docker repo, default is the repo configured in the CONTAINER_REGISTRY environment variable
+// version: version to use, defaults to BRANCH_NAME 
+// label: label to use for the agent. Defaults to the stage name if the agent is configured within a stage,
+//      otherwise the job name is used. 
+//
+// All of the arguments specified in the call to agentsetup are passed without change to the 
+// pod template files and can be accessed in the yaml file as ${name} where name is the 
+// argument name. 
+def call(Map args) { 
+  def defaults = [ 
+    version: env.BRANCH_NAME,
+    repo: env.CONTAINER_REGISTRY,
+    label: env.STAGE_NAME ? env.STAGE_NAME: env.JOB_NAME,
+  ]
+  if (!args) { 
+    args = [:]
+  }
+  args.label = env.JOB_NAME
+  if (env.STAGE_NAME) { 
+    args.label = args.label + "-" + env.STAGE_NAME
+  } 
+  args = defaults << args
+    
+  // combine the configured application templates 
+  args.label = args.label.toLowerCase().replaceAll("[^a-zA-Z0-9]", "-").replaceAll("-+", "-")
+    
+  command = ""
+
+  command += '''
+    files="$( find . -name '*.template' )"
+    for file in $files
+    do
+      base="$( dirname "$file")/$( basename "$file" .template )"
+      cat "$file" '''
+
+  for (key in args.keySet()) { 
+    command += """ | sed 's|\\\$${key}|${args[key]}|g'  """
+  }
+  
+  command += ''' > "$file.tmp"
+      mv "$file.tmp" "$base"
+    done
+  '''
+  
+  println "processresources: ${args}"
+  println "processresources: $command" 
+  
+  sh "$command"
+  
+}
diff --git a/vars/renderTemplate.groovy b/vars/renderTemplate.groovy
new file mode 100644 (file)
index 0000000..bd8dc8c
--- /dev/null
@@ -0,0 +1,7 @@
+import groovy.text.StreamingTemplateEngine
+
+def call(input, variables) {
+  def engine = new StreamingTemplateEngine()
+  return engine.createTemplate(input).make(variables).toString()
+}
+