/ scripts / verify-license.sh
verify-license.sh
  1  #!/bin/bash
  2  # Copyright 2025 Alibaba Group Holding Ltd.
  3  #
  4  # Licensed under the Apache License, Version 2.0 (the "License");
  5  # you may not use this file except in compliance with the License.
  6  # You may obtain a copy of the License at
  7  #
  8  #     http://www.apache.org/licenses/LICENSE-2.0
  9  #
 10  # Unless required by applicable law or agreed to in writing, software
 11  # distributed under the License is distributed on an "AS IS" BASIS,
 12  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  # See the License for the specific language governing permissions and
 14  # limitations under the License.
 15  
 16  # This script verifies that required files contain the Apache 2.0 license header.
 17  # It scans tracked source files and fails with a list of violations if any header
 18  # is missing.
 19  
 20  set -euo pipefail
 21  
 22  REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
 23  CURRENT_YEAR="$(date +%Y)"
 24  MIN_YEAR="2025"
 25  LICENSE_OWNER="Alibaba Group Holding Ltd."
 26  LICENSE_REGEX="Copyright [0-9]{4} ${LICENSE_OWNER// / }"
 27  
 28  # File extensions that are expected to carry a license header.
 29  LICENSE_EXTS=(
 30    go py sh kt kts java ts tsx js jsx toml html css sql tf
 31  )
 32  
 33  # Explicit file basenames that should also be checked (e.g., Dockerfile)
 34  LICENSE_BASENAMES=(
 35    Dockerfile
 36  )
 37  
 38  # Paths to ignore entirely.
 39  IGNORED_PATHS=(
 40    "LICENSE"
 41    "NOTICE"
 42    "docs/"
 43    "scripts/spec-doc/index.html" # Generated doc
 44  )
 45  
 46  is_k8s_mock_go() {
 47    local file="${1-}"
 48    [[ -z "$file" ]] && return 1
 49    # Skip any Go mocks under kubernetes/internal:
 50    # - filenames ending with _mock.go
 51    # - any file under a /mock/ directory
 52    if [[ "$file" != kubernetes/internal/* ]]; then
 53      return 1
 54    fi
 55    if [[ "$file" == *"_mock.go" ]]; then
 56      return 0
 57    fi
 58    if [[ "$file" == */mock/*.go ]]; then
 59      return 0
 60    fi
 61    return 1
 62  }
 63  
 64  is_generated_to_skip() {
 65    local file="$1"
 66    # Skip common generated files
 67    if [[ "$file" == *"deepcopy.go" ]]; then
 68      return 0
 69    fi
 70    return 1
 71  }
 72  
 73  cd "$REPO_ROOT"
 74  
 75  is_ignored() {
 76    local file="$1"
 77    for ignore in "${IGNORED_PATHS[@]}"; do
 78      if [[ "$ignore" == */ ]]; then
 79        if [[ "$file" == "$ignore"* ]]; then
 80          return 0
 81        fi
 82      elif [[ "$file" == "$ignore" ]]; then
 83        return 0
 84      fi
 85    done
 86    return 1
 87  }
 88  
 89  has_expected_extension() {
 90    local file="$1"
 91    local ext="${file##*.}"
 92    for candidate in "${LICENSE_EXTS[@]}"; do
 93      if [[ "$ext" == "$candidate" ]]; then
 94        return 0
 95      fi
 96    done
 97    return 1
 98  }
 99  
100  has_expected_basename() {
101    local file="$1"
102    local base
103    base="$(basename "$file")"
104    for candidate in "${LICENSE_BASENAMES[@]}"; do
105      if [[ "$base" == "$candidate" ]]; then
106        return 0
107      fi
108    done
109    return 1
110  }
111  
112  missing=()
113  
114  while IFS= read -r file; do
115    # Skip ignored paths
116    if is_ignored "$file"; then
117      continue
118    fi
119    # Skip kubernetes internal mock go files
120    if is_k8s_mock_go "$file"; then
121      continue
122    fi
123    # Skip generated files
124    if is_generated_to_skip "$file"; then
125      continue
126    fi
127  
128    # Only check files with expected extensions or basenames
129    if ! has_expected_extension "$file" && ! has_expected_basename "$file"; then
130      continue
131    fi
132  
133    # Limit scan to the first 25 lines to allow shebangs/DOCTYPE above the header.
134    header="$(head -n 25 "$file")"
135    if ! echo "$header" | grep -Eq "$LICENSE_REGEX"; then
136      missing+=("$file")
137      continue
138    fi
139    found_year="$(echo "$header" | grep -Eo "$LICENSE_REGEX" | head -n1 | grep -Eo '[0-9]{4}')"
140    if [[ -z "$found_year" || "$found_year" -gt "$CURRENT_YEAR" || "$found_year" -lt "$MIN_YEAR" ]]; then
141      missing+=("$file")
142    fi
143  done < <(git -C "$REPO_ROOT" ls-files)
144  
145  if ((${#missing[@]} > 0)); then
146    echo "Missing license header in the following files:"
147    printf ' - %s\n' "${missing[@]}"
148    exit 1
149  fi
150  
151  echo "License headers verified."