1414# See the License for the specific language governing permissions and
1515# limitations under the License.
1616
17- from __future__ import print_function
18-
1917import argparse
2018import datetime
2119import difflib
2624
2725parser = argparse .ArgumentParser ()
2826parser .add_argument (
29- "filenames" ,
30- help = "list of files to check, all files if unspecified" ,
31- nargs = '*' )
27+ "filenames" , help = "list of files to check, all files if unspecified" , nargs = "*"
28+ )
3229
3330rootdir = os .path .dirname (__file__ ) + "/../../"
3431rootdir = os .path .abspath (rootdir )
35- parser .add_argument (
36- "--rootdir" , default = rootdir , help = "root directory to examine" )
32+ parser .add_argument ("--rootdir" , default = rootdir , help = "root directory to examine" )
3733
3834default_boilerplate_dir = os .path .join (rootdir , "hack/boilerplate" )
39- parser .add_argument (
40- "--boilerplate-dir" , default = default_boilerplate_dir )
35+ parser .add_argument ("--boilerplate-dir" , default = default_boilerplate_dir )
4136
4237parser .add_argument (
43- "-v" , "--verbose" ,
38+ "-v" ,
39+ "--verbose" ,
4440 help = "give verbose output regarding why a file does not pass" ,
45- action = "store_true" )
41+ action = "store_true" ,
42+ )
4643
4744args = parser .parse_args ()
4845
4946verbose_out = sys .stderr if args .verbose else open ("/dev/null" , "w" )
5047
48+
5149def get_refs ():
5250 refs = {}
5351
5452 for path in glob .glob (os .path .join (args .boilerplate_dir , "boilerplate.*.txt" )):
5553 extension = os .path .basename (path ).split ("." )[1 ]
5654
57- ref_file = open (path , 'r' )
58- ref = ref_file .read ().splitlines ()
59- ref_file .close ()
60- refs [extension ] = ref
55+ with open (path , "r" ) as ref_file :
56+ refs [extension ] = ref_file .read ().splitlines ()
6157
6258 return refs
6359
64- def is_generated_file (filename , data , regexs ):
65- for d in skipped_ungenerated_files :
66- if d in filename :
67- return False
6860
69- p = regexs ["generated" ]
70- return p .search (data )
61+ def is_generated_file (data , regexs ):
62+ return regexs ["generated" ].search (data )
63+
7164
7265def file_passes (filename , refs , regexs ):
7366 try :
74- f = open (filename , 'r' )
75- except Exception as exc :
76- print ("Unable to open %s: %s" % (filename , exc ), file = verbose_out )
67+ with open (filename ) as stream :
68+ data = stream .read ()
69+ except OSError as exc :
70+ print (f"Unable to open { filename } : { exc } " , file = verbose_out )
7771 return False
7872
79- data = f .read ()
80- f .close ()
81-
8273 # determine if the file is automatically generated
83- generated = is_generated_file (filename , data , regexs )
74+ generated = is_generated_file (data , regexs )
8475
8576 basename = os .path .basename (filename )
77+ extension = file_extension (filename )
8678 if generated :
87- extension = "generatego"
88- else :
89- extension = file_extension (filename )
79+ if extension == "go" :
80+ extension = "generatego"
9081
9182 if extension != "" :
9283 ref = refs [extension ]
9384 else :
9485 ref = refs [basename ]
9586
9687 # remove extra content from the top of files
97- if extension == "go" or extension == "generatego" :
98- p = regexs ["go_build_constraints" ]
99- (data , found ) = p .subn ("" , data , 1 )
100- elif extension == "sh" :
101- p = regexs ["shebang" ]
102- (data , found ) = p .subn ("" , data , 1 )
88+ if extension in ("go" , "generatego" ):
89+ data , found = regexs ["go_build_constraints" ].subn ("" , data , 1 )
90+ elif extension in ["sh" , "py" ]:
91+ data , found = regexs ["shebang" ].subn ("" , data , 1 )
10392
10493 data = data .splitlines ()
10594
10695 # if our test file is smaller than the reference it surely fails!
10796 if len (ref ) > len (data ):
108- print ('File %s smaller than reference (%d < %d)' %
109- (filename , len (data ), len (ref )),
110- file = verbose_out )
97+ print (
98+ f"File { filename } smaller than reference ({ len (data )} < { len (ref )} )" ,
99+ file = verbose_out ,
100+ )
111101 return False
112102
113103 # trim our file to the same number of lines as the reference file
114- data = data [:len (ref )]
115-
116- p = regexs ["year" ]
117- for d in data :
118- if p .search (d ):
119- if generated :
120- print ('File %s has the YEAR field, but it should not be in generated file' % filename , file = verbose_out )
121- else :
122- print ('File %s has the YEAR field, but missing the year of date' % filename , file = verbose_out )
123- return False
104+ data = data [: len (ref )]
124105
125106 if not generated :
126- # Replace all occurrences of the regex "2014|2015|2016|2017|2018" with "YEAR"
127- p = regexs ["date" ]
128- for i , d in enumerate (data ):
129- ( data [i ], found ) = p .subn ('YEAR' , d )
107+ # Remove all occurrences of the year ( regex "Copyright ( 2014|2015|2016|2017|2018) ")
108+ pattern = regexs ["date" ]
109+ for i , line in enumerate (data ):
110+ data [i ], found = pattern .subn ("Copyright " , line )
130111 if found != 0 :
131112 break
132113
133114 # if we don't match the reference at this point, fail
134115 if ref != data :
135- print ("Header in %s does not match reference, diff:" % filename , file = verbose_out )
116+ print (f "Header in { filename } does not match reference, diff:" , file = verbose_out )
136117 if args .verbose :
137118 print (file = verbose_out )
138- for line in difflib .unified_diff (ref , data , 'reference' , filename , lineterm = '' ):
119+ for line in difflib .unified_diff (
120+ ref , data , "reference" , filename , lineterm = ""
121+ ):
139122 print (line , file = verbose_out )
140123 print (file = verbose_out )
141124 return False
142125
143126 return True
144127
128+
145129def file_extension (filename ):
146130 return os .path .splitext (filename )[1 ].split ("." )[- 1 ].lower ()
147131
148- skipped_dirs = ['Godeps' , 'third_party' , '_gopath' , '_output' , '.git' , 'cluster/env.sh' ,
149- "vendor" , "test/e2e/generated/bindata.go" , "hack/boilerplate/test" ,
150- "pkg/generated/bindata.go" ,
151- "cluster-autoscaler/expander/grpcplugin/protos" ,
152- "cluster-autoscaler/cloudprovider/aws/aws-sdk-go" ,
153- "cluster-autoscaler/cloudprovider/huaweicloud/huaweicloud-sdk-go-v3" ,
154- "cluster-autoscaler/cloudprovider/bizflycloud/gobizfly" ,
155- "cluster-autoscaler/cloudprovider/brightbox/gobrightbox" ,
156- "cluster-autoscaler/cloudprovider/brightbox/k8ssdk" ,
157- "cluster-autoscaler/cloudprovider/brightbox/linkheader" ,
158- "cluster-autoscaler/cloudprovider/brightbox/go-cache" ,
159- "cluster-autoscaler/cloudprovider/digitalocean/godo" ,
160- "cluster-autoscaler/cloudprovider/externalgrpc/protos" ,
161- "cluster-autoscaler/cloudprovider/magnum/gophercloud" ,
162- "cluster-autoscaler/cloudprovider/ionoscloud/ionos-cloud-sdk-go" ,
163- "cluster-autoscaler/cloudprovider/hetzner/hcloud-go" ,
164- "cluster-autoscaler/cloudprovider/oci" ,
165- "cluster-autoscaler/cloudprovider/volcengine/volcengine-go-sdk" ]
166-
167- # list all the files contain 'DO NOT EDIT', but are not generated
168- skipped_ungenerated_files = ['hack/build-ui.sh' , 'hack/lib/swagger.sh' ,
169- 'hack/boilerplate/boilerplate.py' ,
170- 'cluster-autoscaler/cloudprovider/aws/ec2_instance_types/gen.go' ,
171- 'cluster-autoscaler/cloudprovider/azure/azure_instance_types/gen.go' ]
132+
133+ skipped_names = [
134+ "third_party" ,
135+ "_output" ,
136+ ".git" ,
137+ "cluster/env.sh" ,
138+ "vendor" ,
139+ "testdata" ,
140+ "test/e2e/generated/bindata.go" ,
141+ "hack/boilerplate/test" ,
142+ "hack/boilerplate/boilerplate.py" ,
143+ "hack/scripts/break_mig.py" ,
144+ "hack/scripts/ca_metrics_parser.py" ,
145+ "cluster-autoscaler/update_toc.py" ,
146+ "cluster-autoscaler/hack/list-owners.py" ,
147+ "cluster-autoscaler/expander/grpcplugin/protos" ,
148+ "cluster-autoscaler/cloudprovider/aws/aws-sdk-go" ,
149+ "cluster-autoscaler/cloudprovider/huaweicloud/huaweicloud-sdk-go-v3" ,
150+ "cluster-autoscaler/cloudprovider/bizflycloud/gobizfly" ,
151+ "cluster-autoscaler/cloudprovider/brightbox/gobrightbox" ,
152+ "cluster-autoscaler/cloudprovider/brightbox/k8ssdk" ,
153+ "cluster-autoscaler/cloudprovider/brightbox/linkheader" ,
154+ "cluster-autoscaler/cloudprovider/brightbox/go-cache" ,
155+ "cluster-autoscaler/cloudprovider/digitalocean/godo" ,
156+ "cluster-autoscaler/cloudprovider/externalgrpc/protos" ,
157+ "cluster-autoscaler/cloudprovider/magnum/gophercloud" ,
158+ "cluster-autoscaler/cloudprovider/ionoscloud/ionos-cloud-sdk-go" ,
159+ "cluster-autoscaler/cloudprovider/hetzner/hcloud-go" ,
160+ "cluster-autoscaler/cloudprovider/oci" ,
161+ "cluster-autoscaler/cloudprovider/volcengine/volcengine-go-sdk" ,
162+ "cluster-autoscaler/cloudprovider/aws/ec2_instance_types/gen.go" ,
163+ "cluster-autoscaler/cloudprovider/azure/azure_instance_types/gen.go" ,
164+ "vertical-pod-autoscaler/hack/emit-metrics.py" ,
165+ ]
166+
172167
173168def normalize_files (files ):
174169 newfiles = []
175170 for pathname in files :
176- if any (x in pathname for x in skipped_dirs ):
171+ if any (x in pathname for x in skipped_names ):
177172 continue
178173 newfiles .append (pathname )
179174 for i , pathname in enumerate (newfiles ):
180175 if not os .path .isabs (pathname ):
181176 newfiles [i ] = os .path .join (args .rootdir , pathname )
182177 return newfiles
183178
179+
184180def get_files (extensions ):
185181 files = []
186182 if len (args .filenames ) > 0 :
@@ -191,9 +187,13 @@ def get_files(extensions):
191187 # as we would prune these later in normalize_files(). But doing it
192188 # cuts down the amount of filesystem walking we do and cuts down
193189 # the size of the file list
194- for d in skipped_dirs :
195- if d in dirs :
196- dirs .remove (d )
190+ for dname in skipped_names :
191+ if dname in dirs :
192+ dirs .remove (dname )
193+ for dname in dirs :
194+ # dirs that start with __ are ignored
195+ if dname .startswith ("__" ):
196+ dirs .remove (dname )
197197
198198 for name in walkfiles :
199199 pathname = os .path .join (root , name )
@@ -208,39 +208,44 @@ def get_files(extensions):
208208 outfiles .append (pathname )
209209 return outfiles
210210
211+
211212def get_dates ():
212- years = datetime .datetime .now ().year
213- return '(%s)' % '|' .join ((str (year ) for year in range (2014 , years + 1 )))
213+ # After 2025, we no longer allow new files to include the year in the copyright header.
214+ final_year = 2025
215+ return " (%s) " % "|" .join (str (year ) for year in range (2014 , final_year + 1 ))
216+
214217
215218def get_regexs ():
216219 regexs = {}
217220 # Search for "YEAR" which exists in the boilerplate, but shouldn't in the real thing
218- regexs ["year" ] = re .compile ( 'YEAR' )
219- # get_dates return 2014, 2015, 2016, 2017, or 2018 until the current year as a regex like: "(2014|2015|2016|2017|2018)";
220- # company holder names can be anything
221- regexs ["date" ] = re .compile (get_dates ())
222- # strip // +build \n\n build constraints
221+ regexs ["year" ] = re .compile ("YEAR" )
222+ # get_dates return 2014, 2015, 2016, 2017, ..., 2025
223+ # as a regex like: "(2014|2015|2016|2017|2018|...|2025)";
224+ regexs ["date" ] = re .compile ("Copyright" + get_dates ())
225+ # strip the following build constraints/tags:
226+ # //go:build
227+ # // +build \n\n
223228 regexs ["go_build_constraints" ] = re .compile (
224- r"^(//(go:build| \+build).*\n)+\n" , re .MULTILINE )
225- # strip #!.* from shell scripts
229+ r"^(//(go:build| \+build).*\n)+\n" , re .MULTILINE
230+ )
231+ # strip #!.* from scripts
226232 regexs ["shebang" ] = re .compile (r"^(#!.*\n)\n*" , re .MULTILINE )
227233 # Search for generated files
228- regexs ["generated" ] = re .compile ( ' DO NOT EDIT' )
234+ regexs ["generated" ] = re .compile (r"^[/*#]+ +.* DO NOT EDIT\.$" , re . MULTILINE )
229235 return regexs
230236
237+
231238def main ():
232239 regexs = get_regexs ()
233240 refs = get_refs ()
234- filenames = get_files (refs . keys () )
241+ filenames = get_files (refs )
235242
236243 for filename in filenames :
237- if "/cluster-autoscaler/_override/" in filename :
238- continue
239-
240- if not file_passes (filename , refs , regexs ):
241- print (filename , file = sys .stdout )
244+ if not file_passes (filename , refs , regexs ):
245+ print (filename )
242246
243247 return 0
244248
249+
245250if __name__ == "__main__" :
246- sys .exit (main ())
251+ sys .exit (main ())
0 commit comments