[SEBA-230]

add chart_version_check.sh verification script
improve failure messages in helmlint.sh
improve helmrepo.sh script to update index properly

Change-Id: I69e31a8671e9962252f2ef9a6b900bfe5df282a2
diff --git a/chart_version_check.sh b/chart_version_check.sh
new file mode 100755
index 0000000..cca7874
--- /dev/null
+++ b/chart_version_check.sh
@@ -0,0 +1,134 @@
+#!/usr/bin/env bash
+
+# Copyright 2018-present Open Networking Foundation
+#
+# 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
+#
+# http://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.
+
+# chart_version_check.sh
+# checks that changes to a chart include a change to the chart version
+
+set -u
+
+echo "# chart_version_check.sh, using git: $(git --version) #"
+
+# Collect success/failure, and list/types of failures
+fail_version=0
+failed_charts=""
+
+# when not running under Jenkins, use current dir as workspace
+WORKSPACE=${WORKSPACE:-.}
+
+# branch to compare against, defaults to master
+GERRIT_BRANCH=${GERRIT_BRANCH:-opencord/master}
+
+# Create list of changed files compared to branch
+changed_files=$(git diff --name-only "${GERRIT_BRANCH}")
+
+# Create list of untracked by git files
+untracked_files=$(git ls-files -o --exclude-standard)
+
+# Print lists of files that are changed/untracked
+if [ -z "$changed_files" ] && [ -z "$untracked_files" ]
+then
+  echo "# chart_version_check.sh - No changes, Success! #"
+  exit 0
+else
+  if [ -n "$changed_files" ]
+  then
+    echo "Changed files compared with $GERRIT_BRANCH:"
+    # Search and replace per SC2001 doesn't recognize ^ (beginning of line)
+    # shellcheck disable=SC2001
+    echo "${changed_files}" | sed 's/^/  /'
+  fi
+  if [ -n "$untracked_files" ]
+  then
+    echo "Untracked files:"
+    # shellcheck disable=SC2001
+    echo "${untracked_files}" | sed 's/^/  /'
+  fi
+fi
+
+# combine lists
+if [ -n "$changed_files" ]
+then
+  if [ -n "$untracked_files" ]
+  then
+    changed_files+=$'\n'
+    changed_files+="${untracked_files}"
+  fi
+else
+  changed_files="${untracked_files}"
+fi
+
+# For all the charts, fail on changes within a chart without a version change
+# loop on result of 'find -name Chart.yaml'
+while IFS= read -r -d '' chart
+do
+  chartdir=$(dirname "${chart#./}")
+  chart_changed_files=""
+  version_updated=0
+
+  # create a list of files that were changed in the chart
+  for file in $changed_files; do
+    if [[ $file =~ $chartdir ]]
+    then
+      chart_changed_files+=$'\n'
+      chart_changed_files+="  ${file}"
+    fi
+  done
+
+  # See if chart version changed using 'git diff', and is SemVer
+  chart_yaml_diff=$(git diff -p "$GERRIT_BRANCH" "${chartdir}/Chart.yaml")
+
+  if [ -n "$chart_yaml_diff" ]
+  then
+    echo "Changes to Chart.yaml in '$chartdir'"
+    old_version_string=$(echo "$chart_yaml_diff" | grep -E '\-version:\s*\d+\.\d+\.\d+$')
+    new_version_string=$(echo "$chart_yaml_diff" | grep -E '\+version:\s*\d+\.\d+\.\d+$')
+
+    if [ -n "$new_version_string" ]
+    then
+      version_updated=1
+      echo " Old version string:${old_version_string//-version:/}"
+      echo " New version string:${new_version_string//+version:/}"
+    fi
+  fi
+
+  # if there are any changed files
+  if [ -n "$chart_changed_files" ]
+  then
+    # and version updated, print changed files
+    if [ $version_updated -eq 1 ]
+    then
+      echo " Files changed:${chart_changed_files}"
+    else
+      # otherwise fail this chart
+      echo "Changes to chart but no version update in '$chartdir':${chart_changed_files}"
+      fail_version=1
+      failed_charts+=$'\n'
+      failed_charts+="  $chartdir"
+    fi
+  fi
+
+done < <(find "${WORKSPACE}" -name Chart.yaml -print0)
+
+if [[ $fail_version != 0 ]]; then
+  echo "# chart_version_check.sh Failure! #"
+  echo "Charts that need to be fixed:$failed_charts"
+  exit 1
+fi
+
+echo "# chart_version_check.sh Success! - all charts have updated versions #"
+
+exit 0
+
diff --git a/helmlint.sh b/helmlint.sh
index aabdca0..c068c28 100755
--- a/helmlint.sh
+++ b/helmlint.sh
@@ -22,9 +22,12 @@
 # verify that we have helm installed
 command -v helm >/dev/null 2>&1 || { echo "helm not found, please install it" >&2; exit 1; }
 
-echo "helmlint.sh, using helm version: $(helm version -c --short)"
+echo "# helmlint.sh, using helm version: $(helm version -c --short) #"
 
+# Collect success/failure, and list/types of failures
 fail_lint=0
+failed_lint=""
+failed_req=""
 
 # when not running under Jenkins, use current dir as workspace
 WORKSPACE=${WORKSPACE:-.}
@@ -32,21 +35,27 @@
 # cleanup repos if `clean` option passed as parameter
 if [ "$1" = "clean" ]
 then
-  echo "Removing dependent charts"
-  find "${WORKSPACE}" -name 'charts' -exec rm -rf {} \;
+  echo "Removing any downloaded charts"
+  find "${WORKSPACE}" -type d -name 'charts' -exec rm -rf {} \;
 fi
 
+# now that $1 is checked, error on undefined vars
+set -u
+
+# loop on result of 'find -name Chart.yaml'
 while IFS= read -r -d '' chart
 do
   chartdir=$(dirname "${chart}")
 
-  # only update dependencies for profiles
+  echo "Checking chart: $chartdir"
+
+  # update dependencies for profiles/workflows, as they include TOSCA from required charts
   if [[ $chartdir =~ xos-profiles || $chartdir =~ workflows ]] && [ -f "${chartdir}/requirements.yaml" ]
   then
     helm dependency update "${chartdir}"
   fi
 
-  # lint with values.yaml if it exists
+  # lint the chart (with values.yaml if it exists)
   if [ -f "${chartdir}/values.yaml" ]; then
     helm lint --strict --values "${chartdir}/values.yaml" "${chartdir}"
   else
@@ -56,11 +65,34 @@
   rc=$?
   if [[ $rc != 0 ]]; then
     fail_lint=1
+    failed_lint+="${chartdir} "
   fi
+
+  # check that requirements are available if they're specified
+  if [ -f "${chartdir}/requirements.yaml" ]
+  then
+    echo "Chart has requirements.yaml, checking availability"
+    helm dependency update "${chartdir}"
+    rc=$?
+    if [[ $rc != 0 ]]; then
+      fail_lint=1
+      failed_req+="${chartdir} "
+    fi
+
+    # remove charts dir after checking for availability, as this chart might be
+    # required by other charts in the next loop
+    rm -rf "${chartdir}/charts"
+  fi
+
 done < <(find "${WORKSPACE}" -name Chart.yaml -print0)
 
 if [[ $fail_lint != 0 ]]; then
+  echo "# helmlint.sh Failure! #"
+  echo "Charts that failed to lint: $failed_lint"
+  echo "Charts with failures in requirements.yaml: $failed_req"
   exit 1
 fi
 
+echo "# helmlint.sh Success! - all charts linted and have valid requirements.yaml #"
+
 exit 0
diff --git a/helmrepo.sh b/helmrepo.sh
index 3c94aa0..fd59ca6 100755
--- a/helmrepo.sh
+++ b/helmrepo.sh
@@ -15,37 +15,91 @@
 # limitations under the License.
 
 # helmrepo.sh
-# creates a helm repo for publishing on guide website
+# creates or updates a helm repo for publishing on the guide website
+# Reference: https://github.com/helm/charts/blob/master/test/repo-sync.sh
 
 set -eu -o pipefail
 
+echo "# helmrepo.sh, using helm: $(helm version -c) #"
+
 # when not running under Jenkins, use current dir as workspace
 WORKSPACE=${WORKSPACE:-.}
 
-REPO_DIR="${REPO_DIR:-chart_repo}"
+# branch to compare against, defaults to master
+GERRIT_BRANCH=${GERRIT_BRANCH:-opencord/master}
+
+# directory to compare against, doesn't need to be present
+OLD_REPO_DIR="${OLD_REPO_DIR:-cord-charts-repo}"
+NEW_REPO_DIR="${NEW_REPO_DIR:-chart_repo}"
 
 GERRIT_BRANCH="${GERRIT_BRANCH:-$(git symbolic-ref --short HEAD)}"
 PUBLISH_URL="${PUBLISH_URL:-charts.opencord.org}"
 
-ORIGINAL_INDEX_YAML="${ORIGINAL_INDEX_YAML:-/var/www/charts/index.yaml}"
+# create and clean NEW_REPO_DIR
+mkdir -p "${NEW_REPO_DIR}"
+rm -f "${NEW_REPO_DIR}"/*
 
-mkdir -p "${REPO_DIR}"
+# if OLD_REPO_DIR doesn't exist, generate packages and index in NEW_REPO_DIR
+if [ ! -d "${OLD_REPO_DIR}" ]
+then
+  echo "Creating new helm repo: ${NEW_REPO_DIR}"
 
-while IFS= read -r -d '' chart
-do
-  chartdir=$(dirname "${chart}")
+  while IFS= read -r -d '' chart
+  do
+    chartdir=$(dirname "${chart#./}")
+    helm package --dependency-update --destination "${NEW_REPO_DIR}" "${chartdir}"
 
-  echo "Adding ${chartdir}"
+  done < <(find "${WORKSPACE}" -name Chart.yaml -print0)
 
-  helm package --dependency-update --destination "${REPO_DIR}" "${chartdir}"
+  helm repo index "${NEW_REPO_DIR}" --url https://"${PUBLISH_URL}"
+  echo "# helmrepo.sh Success! Generated new repo index in ${NEW_REPO_DIR}"
 
-done < <(find "${WORKSPACE}" -name Chart.yaml -print0)
+else
+  # OLD_REPO_DIR exists, check for new charts and update only with changes
+  echo "Found existing helm repo: ${OLD_REPO_DIR}, attempting update"
 
-echo "Generating repo index"
+  # Loop and create chart packages, only if changed
+  while IFS= read -r -d '' chart
+  do
+    chartdir=$(dirname "${chart#./}")
 
-scp -o StrictHostKeyChecking=no "${PUBLISH_URL}":"${ORIGINAL_INDEX_YAML}" .
+    # See if chart version changed from previous HEAD commit
+    chart_yaml_diff=$(git diff -p HEAD^ "${chartdir}/Chart.yaml")
 
-helm repo index "${REPO_DIR}" --url https://"${PUBLISH_URL}" --merge index.yaml
+    if [ -n "$chart_yaml_diff" ]
+    then
+      # assumes that helmlint.sh and chart_version_check.sh have been run
+      # pre-merge, which ensures that all charts are valid and have their
+      # version updated in Chart.yaml
+      new_version_string=$(echo "$chart_yaml_diff" | grep -E '\+version:\s*\d+\.\d+\.\d+$')
+      echo "New version of chart ${chartdir}, creating package: ${new_version_string//+version:/}"
+      helm package --dependency-update --destination "${NEW_REPO_DIR}" "${chartdir}"
+    else
+      echo "Chart unchanged, not packaging: '${chartdir}'"
+    fi
 
-echo "Finished, chart repo generated: ${REPO_DIR}"
+  done < <(find "${WORKSPACE}" -name Chart.yaml -print0)
 
+  # Check for collisions between old/new packages
+  while IFS= read -r -d '' package_path
+  do
+    package=$(basename "${package_path}")
+
+    if [ -f "${OLD_REPO_DIR}/${package}" ]
+    then
+      echo "# helmrepo.sh Failure! Package: ${package} with same version already exists in ${OLD_REPO_DIR}"
+      exit 1
+    fi
+  done < <(find "${NEW_REPO_DIR}" -name '*.tgz' -print0)
+
+  # Create updated index.yaml (new version created in NEW_REPO_DIR)
+  helm repo index --url "https://${PUBLISH_URL}" --merge "${OLD_REPO_DIR}/index.yaml" "${NEW_REPO_DIR}"
+
+  # move over packages and index.yaml
+  mv "${NEW_REPO_DIR}"/*.tgz "${OLD_REPO_DIR}/"
+  mv "${NEW_REPO_DIR}/index.yaml" "${OLD_REPO_DIR}/index.yaml"
+
+  echo "# helmrepo.sh Success! Updated existing repo index in ${OLD_REPO_DIR}"
+fi
+
+exit 0