rda-python-miscs 3.0.6__tar.gz → 3.0.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. {rda_python_miscs-3.0.6/src/rda_python_miscs.egg-info → rda_python_miscs-3.0.8}/PKG-INFO +12 -1
  2. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/README.md +11 -0
  3. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/pyproject.toml +1 -1
  4. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/gdexdrop.py +76 -2
  5. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/gdexdrop.usg +13 -2
  6. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8/src/rda_python_miscs.egg-info}/PKG-INFO +12 -1
  7. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/LICENSE +0 -0
  8. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/MANIFEST.in +0 -0
  9. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/setup.cfg +0 -0
  10. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/__init__.py +0 -0
  11. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/bash_qsub.py +0 -0
  12. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/bashqsub.py +0 -0
  13. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/bashqsub.usg +0 -0
  14. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/decsdata_restore.py +0 -0
  15. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/decsdata_restore.usg +0 -0
  16. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/decsdata_storage.py +0 -0
  17. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/decsdata_storage.usg +0 -0
  18. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/gdex_ls.py +0 -0
  19. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/gdexcp.py +0 -0
  20. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/gdexcp.usg +0 -0
  21. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/gdexdrop_standalone.py +0 -0
  22. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/gdexkill.py +0 -0
  23. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/gdexkill.usg +0 -0
  24. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/gdexls.py +0 -0
  25. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/gdexls.usg +0 -0
  26. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/gdexls_standalone.py +0 -0
  27. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/gdexmod.py +0 -0
  28. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/gdexmod.usg +0 -0
  29. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/gdexown.py +0 -0
  30. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/gdexown.usg +0 -0
  31. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/gdexps.py +0 -0
  32. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/gdexps.usg +0 -0
  33. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/gdexsub.py +0 -0
  34. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/gdexsub.usg +0 -0
  35. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/gdexzip.py +0 -0
  36. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/gdexzip.usg +0 -0
  37. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/pg_docs.py +0 -0
  38. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/pg_rst.py +0 -0
  39. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/pg_rst.usg +0 -0
  40. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/pg_wget.py +0 -0
  41. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/pgwget.py +0 -0
  42. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/pgwget.usg +0 -0
  43. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/rda_cp.py +0 -0
  44. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/rda_kill.py +0 -0
  45. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/rda_mod.py +0 -0
  46. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/rda_own.py +0 -0
  47. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/rda_ps.py +0 -0
  48. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/rda_sub.py +0 -0
  49. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/rda_zip.py +0 -0
  50. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/rdals.py +0 -0
  51. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/rdals.usg +0 -0
  52. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/rst_templates/appendix.rst.temp +0 -0
  53. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/rst_templates/index.rst.temp +0 -0
  54. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/rst_templates/section.rst.temp +0 -0
  55. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/tcsh_qsub.py +0 -0
  56. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/tcshqsub.py +0 -0
  57. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs/tcshqsub.usg +0 -0
  58. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs.egg-info/SOURCES.txt +0 -0
  59. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs.egg-info/dependency_links.txt +0 -0
  60. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs.egg-info/entry_points.txt +0 -0
  61. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs.egg-info/requires.txt +0 -0
  62. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/src/rda_python_miscs.egg-info/top_level.txt +0 -0
  63. {rda_python_miscs-3.0.6 → rda_python_miscs-3.0.8}/tests/test_miscs.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rda_python_miscs
3
- Version: 3.0.6
3
+ Version: 3.0.8
4
4
  Summary: RDA Python package to hold RDA miscellaneous utility programs
5
5
  Author-email: Zaihua Ji <zji@ucar.edu>
6
6
  Project-URL: Homepage, https://github.com/NCAR/rda-python-miscs
@@ -172,6 +172,17 @@ deliberately narrow alternative to `gdexcp`: the destination is always
172
172
  directory is rejected, and every source must be readable by the calling user
173
173
  rather than only by `gdexdata`.
174
174
 
175
+ An absolute `-t` under `/tmp/` names any sub-path of `/tmp` instead of a sub-path
176
+ of the dataset directory, so that a drop can be tried out without touching the
177
+ dataset tree. The dataset of `-ds` is still checked against the access list, and
178
+ because `/tmp` is world writable — unlike a dataset directory — the part of the
179
+ path that already exists must belong to the calling user or to `gdexdata`.
180
+
181
+ Everything dropped, including sub-directories created for `-t`, is also set to the
182
+ GDEX group `PGLOG['GDEXGRP']`. The setuid wrapper switches only the user to
183
+ `gdexdata`, not the group, so without that step a dropped file would stay in the
184
+ calling user's group unless the directory it landed in carried the setgid bit.
185
+
175
186
  Who may drop into which dataset is read from the access list
176
187
  `/glade/u/home/gdexdata/config/gdexdrop.conf`. It must be owned by `gdexdata`
177
188
  and must not be writable by group or others, otherwise `gdexdrop` refuses to
@@ -155,6 +155,17 @@ deliberately narrow alternative to `gdexcp`: the destination is always
155
155
  directory is rejected, and every source must be readable by the calling user
156
156
  rather than only by `gdexdata`.
157
157
 
158
+ An absolute `-t` under `/tmp/` names any sub-path of `/tmp` instead of a sub-path
159
+ of the dataset directory, so that a drop can be tried out without touching the
160
+ dataset tree. The dataset of `-ds` is still checked against the access list, and
161
+ because `/tmp` is world writable — unlike a dataset directory — the part of the
162
+ path that already exists must belong to the calling user or to `gdexdata`.
163
+
164
+ Everything dropped, including sub-directories created for `-t`, is also set to the
165
+ GDEX group `PGLOG['GDEXGRP']`. The setuid wrapper switches only the user to
166
+ `gdexdata`, not the group, so without that step a dropped file would stay in the
167
+ calling user's group unless the directory it landed in carried the setgid bit.
168
+
158
169
  Who may drop into which dataset is read from the access list
159
170
  `/glade/u/home/gdexdata/config/gdexdrop.conf`. It must be owned by `gdexdata`
160
171
  and must not be writable by group or others, otherwise `gdexdrop` refuses to
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
 
7
7
  [project]
8
8
  name = "rda_python_miscs"
9
- version = "3.0.6"
9
+ version = "3.0.8"
10
10
  authors = [
11
11
  { name="Zaihua Ji", email="zji@ucar.edu" },
12
12
  ]
@@ -24,6 +24,7 @@ DROPROOT = "/glade/campaign/collections/gdex/data" # dataset data root path
24
24
  DROPCONF = "/glade/u/home/gdexdata/config/gdexdrop.conf" # access list
25
25
  DROPLOG = "/glade/u/home/gdexdata/dssdb/log" # log path
26
26
  DROPUSER = "gdexdata" # owner of dropped files
27
+ DROPTMP = "/tmp" # alternate root, for testing
27
28
 
28
29
  class GdexDrop(PgFile):
29
30
  """Copy local files and directories into a GDEX dataset directory as 'gdexdata'.
@@ -32,7 +33,8 @@ class GdexDrop(PgFile):
32
33
  by -t, and is verified to stay inside that dataset directory. The caller must
33
34
  be granted the dataset in the gdexdrop access list, and must be able to read
34
35
  every source path as themselves; both checks exist because the copy itself
35
- runs with the privileges of 'gdexdata'.
36
+ runs with the privileges of 'gdexdata'. Everything dropped is then set to the
37
+ GDEX group PGLOG['GDEXGRP'], which the copy does not land in on its own.
36
38
  """
37
39
 
38
40
  def __init__(self):
@@ -164,8 +166,11 @@ class GdexDrop(PgFile):
164
166
  """
165
167
  self.DINFO['target'] = self.resolve_target()
166
168
  self.validate_caller()
169
+ if not self.PGLOG['GDEXGID']:
170
+ self.pglog(self.PGLOG['GDEXGRP'] + ": Unknown Group to own the dropped files", self.LGEREX)
167
171
  self.PGLOG['FILEMODE'] = self.DROP['F']
168
172
  self.PGLOG['EXECMODE'] = self.DROP['D']
173
+ self.make_target_directory()
169
174
  for file in self.DROP['f']:
170
175
  self.DINFO['tcnt'] += self.drop_one(file)
171
176
  if self.DINFO['tcnt'] > 0:
@@ -183,18 +188,47 @@ class GdexDrop(PgFile):
183
188
  real path of the dataset directory, so that neither a '..' component in -t
184
189
  nor an existing symbolic link can move the drop outside the dataset.
185
190
 
191
+ An absolute -t under DROPTMP names any sub-path of that directory instead,
192
+ for trying a drop out without touching the dataset tree. The dataset of
193
+ -ds is still validated against the access list.
194
+
186
195
  Returns:
187
196
  str: Resolved absolute target directory.
188
197
  """
189
198
  dsid = self.DROP['ds']
190
199
  if not re.match(r'^[a-z]\d{6}$', dsid):
191
200
  self.pglog(dsid + ": Invalid Dataset ID of -ds, expecting the form d123456", self.LGEREX)
201
+ if self.DROP['t'] and re.match(r'^{}/'.format(DROPTMP), self.DROP['t']):
202
+ return self.tmp_target()
192
203
  dsroot = op.realpath(op.join(DROPROOT, dsid))
193
204
  if not op.isdir(dsroot):
194
205
  self.pglog("{}: Dataset directory NOT exists under {}".format(dsid, DROPROOT), self.LGEREX)
195
206
  target = self.confine_path(op.join(dsroot, self.DROP['t']) if self.DROP['t'] else dsroot, dsroot)
196
207
  return target
197
208
 
209
+ # resolve a target of -t that names a sub-path of DROPTMP
210
+ def tmp_target(self):
211
+ """Return the resolved -t path under DROPTMP, exiting if it may not be used.
212
+
213
+ DROPTMP is world writable, unlike a dataset directory, so a path there can
214
+ be replaced by another user between this check and the copy, and may well
215
+ belong to somebody else already. The existing part of the path is therefore
216
+ required to belong to the caller or to DROPUSER, so that a drop cannot be
217
+ aimed at a directory a third user controls.
218
+
219
+ Returns:
220
+ str: Resolved absolute target directory under DROPTMP.
221
+ """
222
+ tmproot = op.realpath(DROPTMP)
223
+ target = self.confine_path(self.DROP['t'], tmproot)
224
+ dir = target
225
+ while not op.isdir(dir): dir = op.dirname(dir)
226
+ if dir == tmproot: return target # nothing of the path exists yet
227
+ info = self.check_local_file(dir, 2, self.LOGWRN)
228
+ if info and info['logname'] not in (self.PGLOG['CURUID'], DROPUSER):
229
+ self.pglog("{}: Target path under {} is owned by {}".format(dir, DROPTMP, info['logname']), self.LGEREX)
230
+ return target
231
+
198
232
  # make sure a path stays inside the dataset directory
199
233
  def confine_path(self, path, dsroot):
200
234
  """Return the resolved path, exiting if it falls outside the dataset directory.
@@ -245,6 +279,44 @@ class GdexDrop(PgFile):
245
279
  if not ('all' in dsids or self.DROP['ds'] in dsids):
246
280
  self.pglog("{}: NOT granted Dataset {} in {}".format(logname, self.DROP['ds'], DROPCONF), self.LGEREX)
247
281
 
282
+ # create the target directory, and set the group of each directory created
283
+ def make_target_directory(self):
284
+ """Create the sub-directories named by -t that do not exist yet.
285
+
286
+ They are created here rather than on the fly by local_copy_local() so that
287
+ set_drop_group() can be called on each one; a directory left in the calling
288
+ user's group could not be written into by the rest of the DECS group later.
289
+ """
290
+ newdirs = []
291
+ dir = self.DINFO['target']
292
+ while not op.isdir(dir):
293
+ newdirs.insert(0, dir)
294
+ dir = op.dirname(dir)
295
+ if not newdirs: return
296
+ self.make_local_directory(self.DINFO['target'], self.LGWNEX)
297
+ for dir in newdirs: self.set_drop_group(dir)
298
+
299
+ # set the group of a dropped path, and of everything under it for a directory
300
+ def set_drop_group(self, path):
301
+ """Set the group of path, and of all of its contents for a directory.
302
+
303
+ The copy runs with an effective user of 'gdexdata' but with the EFFECTIVE
304
+ GROUP of the caller, so a dropped file lands in the caller's group unless
305
+ the directory it lands in happens to carry the setgid bit. Symbolic links
306
+ are skipped, since os.chown() follows them out of the dataset directory.
307
+
308
+ Args:
309
+ path (str): Absolute path of a dropped file or directory.
310
+ """
311
+ if op.islink(path): return
312
+ self.change_local_group(path, None, None, None, self.LOGWRN)
313
+ if not op.isdir(path): return
314
+ for root, dirs, files in os.walk(path):
315
+ for name in dirs + files:
316
+ subpath = op.join(root, name)
317
+ if op.islink(subpath): continue
318
+ self.change_local_group(subpath, None, None, None, self.LOGWRN)
319
+
248
320
  # copy one source path into the target directory
249
321
  def drop_one(self, file):
250
322
  """Copy one source file or directory into the target directory.
@@ -269,7 +341,9 @@ class GdexDrop(PgFile):
269
341
  if tinfo and tinfo['data_size'] == finfo['data_size']:
270
342
  self.pglog(tofile + ": Target exists with same size, skip copying", self.LOGWRN)
271
343
  return 0
272
- return (1 if self.local_copy_local(tofile, path, self.LGWNEX) else 0)
344
+ if not self.local_copy_local(tofile, path, self.LGWNEX): return 0
345
+ self.set_drop_group(tofile)
346
+ return 1
273
347
 
274
348
  # main function to execute this script
275
349
  def main():
@@ -17,7 +17,12 @@
17
17
  files into. Missing sub-directories are created. The path must stay
18
18
  inside the dataset directory; '..' components and symbolic links
19
19
  leading out of it are rejected. Defaults to the dataset directory
20
- itself;
20
+ itself.
21
+ An absolute path under /tmp/ names any sub-path of /tmp instead, for
22
+ trying a drop out without touching the dataset tree. The dataset of
23
+ -ds is still checked against the access list, and because /tmp is
24
+ world writable, the existing part of the path must belong to the
25
+ calling user or to 'gdexdata';
21
26
 
22
27
  - Option -f File/Directory List is the default option, so the source paths
23
28
  may be given without it and in any position. Relative paths are
@@ -40,7 +45,9 @@
40
45
 
41
46
  Every source path must be readable by the calling user, not merely by
42
47
  'gdexdata'; a path the caller cannot read themselves is refused. Copied files
43
- and directories are owned by 'gdexdata'.
48
+ and directories are owned by 'gdexdata' and belong to the GDEX group, as do any
49
+ sub-directories created for -t; without that they would stay in the calling
50
+ user's group, since only the user is switched to 'gdexdata', not the group.
44
51
 
45
52
  Access List
46
53
  -----------
@@ -75,3 +82,7 @@
75
82
  4. Drop a long list of files named in a file, with group-writable modes:
76
83
 
77
84
  gdexdrop -ds d123456 -i filelist.txt -F 664 -D 775
85
+
86
+ 5. Try a drop out under /tmp, leaving the dataset directory alone:
87
+
88
+ gdexdrop -ds d123456 -t /tmp/mydrop hourly.2026*.nc
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rda_python_miscs
3
- Version: 3.0.6
3
+ Version: 3.0.8
4
4
  Summary: RDA Python package to hold RDA miscellaneous utility programs
5
5
  Author-email: Zaihua Ji <zji@ucar.edu>
6
6
  Project-URL: Homepage, https://github.com/NCAR/rda-python-miscs
@@ -172,6 +172,17 @@ deliberately narrow alternative to `gdexcp`: the destination is always
172
172
  directory is rejected, and every source must be readable by the calling user
173
173
  rather than only by `gdexdata`.
174
174
 
175
+ An absolute `-t` under `/tmp/` names any sub-path of `/tmp` instead of a sub-path
176
+ of the dataset directory, so that a drop can be tried out without touching the
177
+ dataset tree. The dataset of `-ds` is still checked against the access list, and
178
+ because `/tmp` is world writable — unlike a dataset directory — the part of the
179
+ path that already exists must belong to the calling user or to `gdexdata`.
180
+
181
+ Everything dropped, including sub-directories created for `-t`, is also set to the
182
+ GDEX group `PGLOG['GDEXGRP']`. The setuid wrapper switches only the user to
183
+ `gdexdata`, not the group, so without that step a dropped file would stay in the
184
+ calling user's group unless the directory it landed in carried the setgid bit.
185
+
175
186
  Who may drop into which dataset is read from the access list
176
187
  `/glade/u/home/gdexdata/config/gdexdrop.conf`. It must be owned by `gdexdata`
177
188
  and must not be writable by group or others, otherwise `gdexdrop` refuses to