viur-cli 2.0.2__tar.gz → 2.1.0.dev1__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 (42) hide show
  1. {viur_cli-2.0.2/src/viur_cli.egg-info → viur_cli-2.1.0.dev1}/PKG-INFO +7 -1
  2. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/README.md +6 -0
  3. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/setup.cfg +0 -1
  4. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/src/viur_cli/cloud.py +118 -141
  5. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/src/viur_cli/conf.py +6 -13
  6. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/src/viur_cli/local.py +7 -0
  7. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/src/viur_cli/package.py +27 -1
  8. viur_cli-2.1.0.dev1/src/viur_cli/setup.py +110 -0
  9. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/src/viur_cli/tool.py +80 -114
  10. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/src/viur_cli/version.py +1 -1
  11. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1/src/viur_cli.egg-info}/PKG-INFO +7 -1
  12. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/src/viur_cli.egg-info/SOURCES.txt +1 -2
  13. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/src/viur_cli.egg-info/entry_points.txt +0 -1
  14. viur_cli-2.0.2/src/viur_cli/scripts/viur_2to3.py +0 -153
  15. viur_cli-2.0.2/src/viur_cli/setup.py +0 -51
  16. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/LICENSE +0 -0
  17. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/pyproject.toml +0 -0
  18. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/setup.py +0 -0
  19. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/src/viur_cli/__init__.py +0 -0
  20. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/src/viur_cli/build.py +0 -0
  21. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/src/viur_cli/cli.py +0 -0
  22. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/src/viur_cli/deprecated.py +0 -0
  23. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/src/viur_cli/scriptor/__init__.py +0 -0
  24. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/src/viur_cli/scriptor/cli.py +0 -0
  25. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/src/viur_cli/scriptor/scriptor/__init__.py +0 -0
  26. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/src/viur_cli/scriptor/scriptor/csvwriter.py +0 -0
  27. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/src/viur_cli/scriptor/scriptor/dialog.py +0 -0
  28. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/src/viur_cli/scriptor/scriptor/logger.py +0 -0
  29. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/src/viur_cli/scriptor/scriptor/module.py +0 -0
  30. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/src/viur_cli/scriptor/scriptor/network.py +0 -0
  31. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/src/viur_cli/scriptor/scriptor/progressbar.py +0 -0
  32. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/src/viur_cli/scriptor/scriptor/readers.py +0 -0
  33. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/src/viur_cli/scriptor/scriptor/utils.py +0 -0
  34. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/src/viur_cli/scriptor/scriptor/viur.py +0 -0
  35. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/src/viur_cli/scriptor/scriptor/writer.py +0 -0
  36. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/src/viur_cli/scripts/__init__.py +0 -0
  37. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/src/viur_cli/scripts/get_pyodide.py +0 -0
  38. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/src/viur_cli/update.py +0 -0
  39. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/src/viur_cli/utils.py +0 -0
  40. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/src/viur_cli.egg-info/dependency_links.txt +0 -0
  41. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/src/viur_cli.egg-info/requires.txt +0 -0
  42. {viur_cli-2.0.2 → viur_cli-2.1.0.dev1}/src/viur_cli.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: viur_cli
3
- Version: 2.0.2
3
+ Version: 2.1.0.dev1
4
4
  Summary: Command-line interface for ViUR application maintenance.
5
5
  Home-page: https://github.com/viur-framework/viur-cli
6
6
  Author: Andreas H. Kelch
@@ -194,6 +194,12 @@ It contains the default viur project profile and it can be expanded with several
194
194
  "kind": "npm",
195
195
  "source": ""
196
196
  }
197
+ /* OPTIONAL arguments, can be set in default or in a specific profile */
198
+ "appyaml": "app_stub.yaml", // Use a name other than "app.yaml"
199
+ "appyaml_substitition": true, // Set to true to replace only standard variables in app.yaml
200
+ "appyaml_substitition": { // Set to an object to replace these in addition to the standard variables in app.yaml
201
+ "$REGION": "europe-west3"
202
+ }
197
203
  },
198
204
  "gcloud": {
199
205
  "functions": { //Declarations for a cloud function
@@ -174,6 +174,12 @@ It contains the default viur project profile and it can be expanded with several
174
174
  "kind": "npm",
175
175
  "source": ""
176
176
  }
177
+ /* OPTIONAL arguments, can be set in default or in a specific profile */
178
+ "appyaml": "app_stub.yaml", // Use a name other than "app.yaml"
179
+ "appyaml_substitition": true, // Set to true to replace only standard variables in app.yaml
180
+ "appyaml_substitition": { // Set to an object to replace these in addition to the standard variables in app.yaml
181
+ "$REGION": "europe-west3"
182
+ }
177
183
  },
178
184
  "gcloud": {
179
185
  "functions": { //Declarations for a cloud function
@@ -32,7 +32,6 @@ where = src
32
32
  [options.entry_points]
33
33
  console_scripts =
34
34
  viur = viur_cli:cli
35
- viur-2to3 = viur_cli.scripts.viur_2to3:main
36
35
  get-pyodide = viur_cli.scripts.get_pyodide:main
37
36
 
38
37
  [egg_info]
@@ -1,7 +1,9 @@
1
1
  import json
2
+ from pathlib import Path
2
3
  import subprocess
3
4
  import os
4
5
  import string
6
+ import time
5
7
  import click
6
8
  import yaml
7
9
  from viur_cli import echo_positive, echo_warning, echo_fatal
@@ -15,6 +17,53 @@ def cloud():
15
17
  """This method defines a command group for working with cloud resources."""
16
18
 
17
19
 
20
+ @cloud.command(context_settings={"ignore_unknown_options": True})
21
+ @click.argument("action", type=click.Choice(["bucket2bucket", "bucket2local", "local2bucket"]))
22
+ @click.argument("profile", default="default")
23
+ def copy(action, profile):
24
+ if action == "bucket2bucket":
25
+ if user_check_login():
26
+ storage_copy()
27
+
28
+ if action == "bucket2local":
29
+ if user_check_login():
30
+ datastore_import(profile)
31
+
32
+ if action == "local2bucket":
33
+ if user_check_login():
34
+ datastore_export(profile)
35
+
36
+
37
+ def user_check_login():
38
+ return click.confirm("Are you logged in with your gcloud admin account?", default=False, show_default=True)
39
+
40
+
41
+ def storage_copy():
42
+ # https://console.cloud.google.com/transfer/jobs
43
+ source = click.prompt('Source bucketname')
44
+ target = click.prompt('Target bucketname')
45
+ if not click.confirm(text=f"Copy from {source} to {target}", default=True):
46
+ print("Abort ...")
47
+ return 0
48
+ print(f"gsutil -m cp -r gs://{source}/ gs://{target}/")
49
+ os.system(f"gsutil -m cp -r gs://{source} gs://{target}")
50
+
51
+
52
+ def datastore_import(profile):
53
+ conf = config.get_profile(profile)
54
+ target = click.prompt('path to overall_export_metadata')
55
+ os.system(f"gcloud datastore import gs://{target} --project={conf['application_name']}")
56
+
57
+
58
+ def datastore_export(profile):
59
+ conf = config.get_profile(profile)
60
+ target = click.prompt('bucketname')
61
+ timestamp = f'{datetime.now().strftime("%Y%m%d-%H%M%S")}-manual'
62
+ format = "default"
63
+ os.system(
64
+ f"gcloud datastore export gs://{target}/{timestamp}-{format} --format={format} --project={conf['application_name']} ")
65
+
66
+
18
67
  @cloud.command(context_settings={"ignore_unknown_options": True})
19
68
  @click.argument("action", type=click.Choice(["backup"]))
20
69
  def enable(action):
@@ -196,15 +245,19 @@ def disable_gcp_backup():
196
245
 
197
246
 
198
247
  @cloud.command(context_settings={"ignore_unknown_options": True})
199
- @click.argument("action", type=click.Choice(["gcloud", "gcroles"]))
248
+ @click.argument("action", type=click.Choice(["gcroles"]))
200
249
  @click.argument("profile", default="default")
201
250
  def setup(action, profile):
202
251
  """
203
252
  Set up the specified action for the given profile.
204
253
  """
205
254
  if action == "gcloud":
206
- gcloud_setup()
207
-
255
+ if os.path.exists('deploy'):
256
+ gcloud_setup()
257
+ else:
258
+ echo_error("No 'deploy' directory found in your current working directory."
259
+ "\n Please make sure you are in the correct directory."
260
+ "\n If you want to create a new ViUR Project use 'viur create {name}'")
208
261
  if action == "gcroles":
209
262
  gcloud_setup_roles(profile)
210
263
 
@@ -366,121 +419,6 @@ def transform_dict_to_yaml(transformed_data):
366
419
  return original_data
367
420
 
368
421
 
369
- def gcloud_setup():
370
- """
371
- Set up the Google Cloud Platform (GCP) environment for a ViUR project.
372
-
373
- This method performs the following steps:
374
- 1. Prompts the user to enter the GCP project ID.
375
- 2. Checks if the user is authorized with gcloud.
376
- - If not authorized, prompts the user to authenticate with gcloud and login.
377
- 3. Checks if the GCP App Engine app already exists.
378
- - If not, prompts the user to create the app and confirm the project is connected to a billing account.
379
- 4. Enables necessary APIs and services for the project.
380
- 5. Configures Google Cloud Storage for the project.
381
- 6. Deploys necessary deployment files (cron.yaml, queue.yaml, index.yaml) to the project.
382
- 7. Checks if the app engine default credentials are set.
383
- - If not set, prompts the user to authenticate and set the application default user.
384
- 8. Prints a success message with instructions on how to run the project locally.
385
-
386
- Note: This method does not return anything.
387
- """
388
- project = input("Enter PROJECT_ID: ").strip()
389
-
390
- if not project:
391
- echo_fatal("Usage: viur setup gcloud PROJECT_ID")
392
- return
393
-
394
- echo_info("Check if user is authorized with gcloud....")
395
-
396
- try:
397
- run_command("gcloud auth print-access-token")
398
- except subprocess.CalledProcessError:
399
- echo_warning(
400
- "##############################################################\n"
401
- "# Please authenticate your Google user with gcloud SDK to #\n"
402
- "# execute administrative commands. #\n"
403
- "# In this step, a separate browser window opens to #\n"
404
- "# authenticate. #\n"
405
- "# This step is only required once on this computer. #\n"
406
- "##############################################################\n"
407
- )
408
- response = input("Are you ready?[Y/n]")
409
- if not response.lower() in ("y", ""):
410
- echo_fatal("User aborted")
411
- return
412
-
413
- run_command("gcloud auth login --no-ptomote")
414
-
415
- # Check if App already exists
416
- try:
417
- run_command(f"gcloud app describe --project={project}")
418
- except subprocess.CalledProcessError:
419
- echo_warning(
420
- "##############################################################\n"
421
- "# Please check and confirm that your project is created and #\n"
422
- "# connected with a billing account in Google Cloud console. #\n"
423
- "# Otherwise, some of the following calls may fail. #\n"
424
- "##############################################################"
425
- )
426
- response = input("Continue? [Y/n] ")
427
- if not response.lower() in ("y", ""):
428
- echo_error("User aborted.")
429
- return
430
-
431
- # Create the Appengine app
432
- run_command(f"gcloud app create --project={project} --region=europe-west3")
433
-
434
- # Activate APIs and Services
435
- services = [
436
- "datastore.googleapis.com",
437
- "firestore.googleapis.com",
438
- "iamcredentials.googleapis.com",
439
- "cloudbuild.googleapis.com",
440
- "cloudtasks.googleapis.com",
441
- "cloudscheduler.googleapis.com",
442
- "secretmanager.googleapis.com"
443
- ]
444
-
445
- for service in services:
446
- run_command(f"gcloud services enable --project={project} {service}")
447
-
448
- # Configure Google Cloud Storage
449
- run_command(f"gsutil uniformbucketlevelaccess set on gs://{project}.appspot.com/")
450
-
451
- for yaml in ["cron.yaml", "queue.yaml", "index.yaml"]:
452
- run_command(f"cd deploy && gcloud app deploy -q --project={project} {yaml}")
453
-
454
- echo_info("Check if app engine default credentials are set...")
455
- try:
456
- run_command("gcloud auth application-default print-access-token")
457
-
458
- except subprocess.CalledProcessError:
459
- echo_warning(
460
- "##############################################################\n"
461
- "# Please authenticate your Google user with gcloud SDK now #\n"
462
- "# to set the application default user. This step is required #\n"
463
- "# to run ViUR applications locally without further #\n"
464
- "# credentials that must be supplied from a file. #\n"
465
- "# This step is only required once on this computer. #\n"
466
- "##############################################################")
467
- response = input("Are you ready? [Y/n] ")
468
- if not response.lower() in ("y", ""):
469
- echo_fatal("User aborted.")
470
- return
471
-
472
- run_command("gcloud auth application-default login")
473
-
474
- echo_positive(
475
- "All done!\n"
476
- "You should now be able to run your project locally with\n"
477
- " viur run \n"
478
- "At the first run, it might happen that some functions are\n"
479
- "causing error 500 because indexes are not immediately\n"
480
- "served. Therefore, maybe wait a few minutes.\n"
481
- "Have a nice day.\n")
482
-
483
-
484
422
  # Helper function for running Commands in subprocess and getting the Output
485
423
  def run_command(command):
486
424
  """
@@ -501,20 +439,23 @@ def run_command(command):
501
439
  @click.argument("additional_args", nargs=-1)
502
440
  @click.option("--ext", "-e", default=None)
503
441
  @click.option("--yes", "-y", is_flag=True, default=False)
442
+ @click.option("--skip_checks", is_flag=True, help="Skip the security checks before the deployment")
504
443
  @click.option("--name", "-n", default=None)
505
- def deploy(action, profile, name, ext, yes, additional_args):
506
- """ Deploy the specified action to a cloud service."""
444
+ def deploy(action, profile, name, ext, yes, skip_checks: bool, additional_args):
445
+ """Deploy the specified action to a cloud"""
507
446
 
508
447
  conf = config.get_profile(profile)
509
448
 
510
449
  if action == "app":
511
- from . import do_checks
512
- if not do_checks(dev=False):
513
- # --yes will not be implemented here because deploying security issues should be an explicit decission
514
- if not click.confirm(f"The checks were not successful, do you want to continue?"):
515
- return
516
- else:
517
- echo_info("\U00002714 No vulnerabilities found.")
450
+ if not skip_checks:
451
+ from . import do_checks
452
+ if not do_checks(dev=False):
453
+ # --yes will not be implemented here because deploying security issues should be an explicit decission
454
+ if not click.confirm(f"The checks were not successful, do you want to continue?"):
455
+ return
456
+ else:
457
+ echo_info("\U00002714 No vulnerabilities found.")
458
+
518
459
  version = replace_vars(
519
460
  conf["version"],
520
461
  {k: v for k, v in conf.items() if k not in ["version"]}
@@ -529,10 +470,46 @@ def deploy(action, profile, name, ext, yes, additional_args):
529
470
  # rebuild requirements.txt
530
471
  create_req(yes, profile, confirm_value=False)
531
472
 
532
- os.system(
533
- f'gcloud app deploy --project={conf["application_name"]} --version={version} '
534
- f'--no-promote {" ".join(additional_args)} {conf["distribution_folder"]} {"-q" if yes else ""}'
535
- )
473
+ app_yaml = Path(conf["distribution_folder"]) / conf.get("appyaml", "app.yaml")
474
+ app_yaml_tmp = app_yaml_hidden = None
475
+ if appyaml_substitition := conf.get("appyaml_substitition"):
476
+ app_yaml_tmp = app_yaml.with_stem(f"app{time.time_ns()}.tmp")
477
+
478
+ susbtitutions = {
479
+ "$PROJECT_ID": conf["application_name"],
480
+ "$PROJECT_VERSION": version,
481
+ "$CLI_PROFILE": profile,
482
+ }
483
+ if isinstance(appyaml_substitition, dict):
484
+ susbtitutions |= appyaml_substitition
485
+
486
+ new_content = app_yaml.read_text()
487
+ for pattern, replacment in susbtitutions.items():
488
+ new_content = new_content.replace(pattern, replacment)
489
+ app_yaml_tmp.write_text(new_content)
490
+ additional_args = [f"--appyaml={app_yaml_tmp.resolve()}", *additional_args]
491
+
492
+ # Sadly the --appyaml does only work if the deploy dir does not contain an app.yaml,
493
+ # thefore we make it "hidden" for the gcloud CLI if there is "app.yaml" is not
494
+ # named differently
495
+ if app_yaml.name == "app.yaml":
496
+ app_yaml_hidden = app_yaml.with_stem(f".{app_yaml.stem}")
497
+ app_yaml.rename(app_yaml_hidden)
498
+
499
+ elif app_yaml.name != "app.yaml":
500
+ # No substitution is used, but an different app.yaml name
501
+ additional_args = [f"--appyaml={app_yaml_tmp.resolve()}", *additional_args]
502
+
503
+ try:
504
+ os.system(
505
+ f'gcloud app deploy --project={conf["application_name"]} --version={version} '
506
+ f'--no-promote {" ".join(additional_args)} {conf["distribution_folder"]} {"-q" if yes else ""}'
507
+ )
508
+ finally:
509
+ if app_yaml_tmp is not None:
510
+ app_yaml_tmp.unlink()
511
+ if app_yaml_hidden is not None:
512
+ app_yaml_hidden.rename(app_yaml)
536
513
 
537
514
  elif action == "cloudfunction":
538
515
  os.system(build_deploy_command(name, conf["gcloud"]))
@@ -594,7 +571,6 @@ def deploy(action, profile, name, ext, yes, additional_args):
594
571
  f'gcloud app deploy --project={conf["application_name"]} {" ".join(additional_args)} {yaml_file} {"-q" if yes else ""}')
595
572
 
596
573
 
597
-
598
574
  def build_deploy_command(name, conf):
599
575
  """
600
576
 
@@ -627,9 +603,8 @@ def build_deploy_command(name, conf):
627
603
  echo_fatal(f"The cloudfunction {name} was not found your project.json\n "
628
604
  f"You can create a cloudfunction entry by calling 'viur cloud create function'")
629
605
 
630
-
631
606
  command = (
632
- f"gcloud functions deploy "
607
+ f"gcloud run deploy "
633
608
  f"{name} "
634
609
  f"--region='{conf['region']}'"
635
610
  f"--max-instances={conf['max-instances']}"
@@ -676,10 +651,10 @@ def create(profile, action, gen, source, name, entrypoint, env_vars_file, memory
676
651
  function_dict = conf["gcloud"]["functions"].get(function_name, {})
677
652
 
678
653
  function_dict["gen"] = function_dict.get("gen",
679
- gen if gen else click.prompt(
680
- "Please enter your cloud function generation",
681
- default="2")
682
- )
654
+ gen if gen else click.prompt(
655
+ "Please enter your cloud function generation",
656
+ default="2")
657
+ )
683
658
 
684
659
  function_dict["entry-point"] = function_dict.get("entry-point",
685
660
  entrypoint if entrypoint else click.prompt(
@@ -708,12 +683,13 @@ def create(profile, action, gen, source, name, entrypoint, env_vars_file, memory
708
683
  function_dict["trigger"] = function_dict.get("trigger",
709
684
  trigger if trigger else click.prompt(
710
685
  "Please enter your cloud function trigger type",
711
- default="http")
686
+ default="https")
687
+
712
688
  )
713
689
 
714
690
  function_dict["source"] = function_dict.get("source",
715
691
  source if source else click.prompt(
716
- "Enter the directory of your cloud functio"
692
+ "Enter the directory of your cloud function"
717
693
  "(deploy/cloudfunction/{FileName})")
718
694
  )
719
695
 
@@ -722,4 +698,5 @@ def create(profile, action, gen, source, name, entrypoint, env_vars_file, memory
722
698
  config[profile] = conf
723
699
  config.migrate()
724
700
  echo_positive("Your cloud function creation was successful, if you want to add more flags, "
725
- "add them in your project.json under")
701
+ "add them in your project.json")
702
+
@@ -17,7 +17,7 @@ class ProjectConfig(dict):
17
17
  super().__init__()
18
18
  self["default"] = {}
19
19
  self["format"] = PROJECT_CONFIG_VERSION
20
- self.load()
20
+ self.initial_load = False
21
21
 
22
22
  def load(self):
23
23
  """
@@ -72,21 +72,16 @@ class ProjectConfig(dict):
72
72
 
73
73
  def get_profile(self, profile):
74
74
  """Get profile configuration"""
75
+ if not self.initial_load:
76
+ self.load()
77
+ self.initial_load = True
78
+
75
79
  if profile == "format":
76
80
  echo_fatal("Your profile can not be named 'Format' ")
77
81
  if profile not in self:
78
82
  echo_fatal(f"{profile!r} is not a valid profile name")
79
83
  return self["default"].copy() | self[profile]
80
84
 
81
- def delete(self):
82
- """Delete profile cofniguration"""
83
- configname = click.prompt('name')
84
- try:
85
- del self[configname]
86
- self.save()
87
- except:
88
- raise click.ClickException(click.style(f"{configname} not found", fg="red"))
89
-
90
85
  def find_key(self, dictionary, target_key, target, keep=False):
91
86
  if target_key in dictionary:
92
87
  if keep:
@@ -122,7 +117,7 @@ class ProjectConfig(dict):
122
117
  self.find_key(self, target_key="version", target="default", keep=True)
123
118
  # Fail Safe
124
119
  if "version" in self:
125
- del self["version"] 4
120
+ del self["version"]
126
121
  self.remove_key(self, target_key="core")
127
122
 
128
123
  if old_format := self["default"].get("format"):
@@ -167,8 +162,6 @@ class ProjectConfig(dict):
167
162
  format_version_updated = False
168
163
 
169
164
  for entry in ("admin", "scriptor", "vi"):
170
- if not self["default"]["builds"]:
171
- self["default"]["builds"]
172
165
  if entry in self["default"]:
173
166
  version_value = self["default"][entry].lstrip("v")
174
167
  self["default"]["builds"][entry] = {
@@ -46,6 +46,13 @@ def run(profile, additional_args):
46
46
  f"Please install the 'gcloud' tool or Log in with an appropriate account.")
47
47
 
48
48
  conf = config.get_profile(profile)
49
+ additional_args = list(additional_args)
50
+
51
+ if conf.get("port"):
52
+ additional_args.append(f"--port={conf['port']}")
53
+ if conf.get("gunicorn_port"):
54
+ additional_args.append(f"--gunicorn_port={conf['gunicorn_port']}")
55
+
49
56
  utils.system(
50
57
  f'app_server -A={conf["application_name"]} {conf["distribution_folder"]} {" ".join(additional_args)}')
51
58
 
@@ -85,12 +85,13 @@ def get_version_info(software: str, version: str) -> tuple[str, str]:
85
85
  @cli.command()
86
86
  @click.argument('operation', type=click.Choice(['update', 'install']))
87
87
  @click.argument('component', type=click.Choice(['vi', 'admin', 'scriptor', 'all']))
88
- @click.argument('profile', default='default')
89
88
  @click.argument("version", default="latest")
89
+ @click.argument('profile', default='default')
90
90
  def package(operation, component, profile, version):
91
91
  """
92
92
  Performs installements and updates of ViUR Ecosystem packages
93
93
  """
94
+
94
95
  conf = config.get_profile(profile)
95
96
  operations_links = {
96
97
  'vi': vi,
@@ -141,6 +142,13 @@ def scriptor(version, target, profile):
141
142
 
142
143
  real_version, download_url = get_version_info("scriptor", version)
143
144
 
145
+ old_version=conf.get('builds').get('scriptor').get('version')
146
+
147
+ if old_version == real_version.strip("v"):
148
+ if not click.confirm(f"You have already installed version {old_version} of scriptor.\n"
149
+ f"Do you want to continue and install it again?"):
150
+ return
151
+
144
152
  scriptor_path = Path(dist_folder, target)
145
153
  tmp_zip_file = Path("scriptor.zip")
146
154
 
@@ -168,6 +176,7 @@ def scriptor(version, target, profile):
168
176
  tmp_zip_file.unlink()
169
177
  bar.label = "updated successful"
170
178
 
179
+ echo_positive(f"Updated scriptor from {old_version} to {real_version}")
171
180
 
172
181
  def admin(version: str, target: str, profile):
173
182
  """Update the admin to a specific version."""
@@ -176,6 +185,13 @@ def admin(version: str, target: str, profile):
176
185
 
177
186
  real_version, download_url = get_version_info("admin", version)
178
187
 
188
+ old_version=conf.get('builds').get('admin').get('version')
189
+
190
+ if old_version == real_version.strip("v"):
191
+ if not click.confirm(f"You have already installed the version {old_version} of admin.\n"
192
+ f"Do you want to continue and install it again?"):
193
+ return
194
+
179
195
  admin_path = Path(dist_folder, target)
180
196
  tmp_zip_file = Path("vi-admin.zip")
181
197
 
@@ -207,6 +223,8 @@ def admin(version: str, target: str, profile):
207
223
  tmp_zip_file.unlink()
208
224
  bar.label = "updated successful"
209
225
 
226
+ echo_positive(f"Updated admin from {old_version} to {real_version}")
227
+
210
228
 
211
229
  def vi(version, target, profile):
212
230
  """Updates Vi to the specified version."""
@@ -215,6 +233,12 @@ def vi(version, target, profile):
215
233
  dist_folder = conf["distribution_folder"]
216
234
 
217
235
  real_version, download_url = get_version_info("vi", version)
236
+ old_version = conf.get('builds').get('vi').get('version')
237
+
238
+ if old_version == real_version.strip("v"):
239
+ if not click.confirm(f"You have already installed the version {old_version} of vi.\n"
240
+ f"Do you want to continue and install it again?"):
241
+ return
218
242
 
219
243
  vi_path = Path(dist_folder, target)
220
244
  tmp_zip_file = Path("vi.zip")
@@ -246,3 +270,5 @@ def vi(version, target, profile):
246
270
  elif element == 5:
247
271
  tmp_zip_file.unlink()
248
272
  bar.label = "updated successful"
273
+
274
+ echo_positive(f"Updated Vi from {old_version} to {real_version}")
@@ -0,0 +1,110 @@
1
+ import os
2
+ import subprocess
3
+ import click
4
+ from .cli import cli
5
+ from .utils import *
6
+
7
+
8
+ def clean_base(app_id, author=None):
9
+ """
10
+ Sets up a clean ViUR project base.
11
+
12
+ Args:
13
+ clean_history (bool, optional): Whether to clean the git history. Defaults to True.
14
+ app_id (str, optional): The application-id to use. If not provided, prompts the user.
15
+ author (str, optional): The author's name to use. If not provided, defaults to the current user.
16
+ """
17
+
18
+ try:
19
+ whoami = getpass.getuser()
20
+ except Exception:
21
+ whoami = "viur"
22
+
23
+ timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
24
+ os.chdir(app_id)
25
+
26
+ file_list = ["viur-project.md"]
27
+ replacements = {"{{app_id}}": app_id, "{{whoami}}": whoami, "{{timestamp}}": timestamp}
28
+
29
+ for subdir, dirs, files in os.walk("."):
30
+ for file in files:
31
+ filepath = subdir + os.sep + file
32
+
33
+ if any([filepath.endswith(ext) for ext in
34
+ [".py", ".yaml", ".html", ".md", ".sh", ".json", ".js", ".less"]]):
35
+ file_list.append(filepath)
36
+
37
+ for file_obj in file_list:
38
+ lines = []
39
+ with open(file_obj, "r") as infile:
40
+ for line in infile:
41
+ for src, target in replacements.items():
42
+ line = line.replace(src, target)
43
+ lines.append(line)
44
+ with open(file_obj, "w") as outfile:
45
+ for line in lines:
46
+ outfile.write(line)
47
+
48
+ if os.path.exists(".git"):
49
+ echo_info("Cleaning git history")
50
+ subprocess.check_output("git checkout --orphan main_tmp", shell=True)
51
+ echo_info(subprocess.check_output("git branch -D main", shell=True).decode().rstrip("\n"))
52
+
53
+ subprocess.check_output("git branch -m main", shell=True)
54
+ branch_current = subprocess.check_output('git branch --show-current', shell=True).decode().rstrip('\n')
55
+ echo_info(f"Current branch is: {branch_current}")
56
+ echo_info("---")
57
+
58
+ echo_info("Generating project documentation...")
59
+ sys.stdout.flush()
60
+
61
+ os.remove("README.md") # Remove README.md if it exists
62
+ os.rename("viur-project.md", "README.md")
63
+ os.remove(sys.argv[0]) # Remove the script itself
64
+
65
+ echo_positive("Project repository has been set-up now.")
66
+
67
+
68
+ @cli.command()
69
+ @click.argument("name")
70
+ @click.pass_context
71
+ def create(ctx, name):
72
+ """
73
+ Create a new ViUR project.
74
+
75
+ The 'create' command allows you to create a new ViUR project by cloning the ViUR base project and configuring it.
76
+ You can specify the name of the new project as the 'name' argument.
77
+
78
+ The 'create' command performs the following steps:
79
+
80
+ 1. Clones the ViUR base project from the official GitHub repository.
81
+
82
+ 2. Configures the new project by running 'clean-base.py'.
83
+
84
+ 3. Optionally configures the project as a new gcloud project (if confirmed).
85
+
86
+ Note:
87
+
88
+ - This command initializes the new ViUR project based on the ViUR base project.
89
+
90
+ - Make sure to provide a unique project name to avoid conflicts with existing folders.
91
+
92
+ """
93
+ if os.path.exists(f'./{name}'):
94
+ echo_error(f'"{name}" Folder already exists. Please use a different name or remove this folder ./{name}')
95
+ return
96
+
97
+ # fetch base project
98
+ git_clonne_cmd = ['git', 'clone', f'https://github.com/viur-framework/viur-base.git', name]
99
+ subprocess.run(git_clonne_cmd, check=True)
100
+
101
+ wdir = f"{os.getcwd()}/{name}"
102
+
103
+ # Run clean-base.py
104
+ clean_base_cmd = ['python3', 'clean-base.py', '-A', f'{name}']
105
+ subprocess.run(clean_base_cmd, check=True, cwd=wdir)
106
+
107
+ # Run gcloud config (if confirmed)
108
+ if click.confirm(f'Do you want to configure "{name}" as a new gcloud project?'):
109
+ gcloud_setup_cmd = ['./viur-gcloud-setup.sh', name]
110
+ subprocess.run(gcloud_setup_cmd, check=True, cwd=wdir)
@@ -1,114 +1,80 @@
1
- import click
2
- import os
3
-
4
- from . import cli
5
-
6
-
7
- @cli.group()
8
- def tool():
9
- """
10
- Run different ViUR-related scripts.
11
-
12
- The 'tool' group allows you to execute various ViUR-related scripts that help with tasks such as project porting,
13
- Pyodide installation, and SSL certificate fixes.
14
-
15
- Available Commands:
16
-
17
- - '2to3': ViUR porting script.
18
-
19
- - 'pyodide': Run the get_pyodide command.
20
-
21
- - 'ssl_fix': SSL certificate fix for macOS.
22
- """
23
-
24
-
25
- @tool.command(name="2to3")
26
- @click.argument("path")
27
- @click.option('--dryrun', '-d', is_flag=True, default=False)
28
- @click.option('--daredevil', '-x', is_flag=True, default=False)
29
- def two_to_three(path, *args, **kwargs):
30
- """
31
- ViUR2 to ViUR3 porting script.
32
-
33
- The '2to3' command allows you to port an existing ViUR2 project to ViUR3. This script is used for
34
- migrating projects from Python 2 to Python 3.
35
-
36
- :param path: str
37
- The path to the project to be ported.
38
- :param dryrun: bool, optional
39
- Perform a dry run to test the porting process (default: False).
40
- :param daredevil: bool, optional
41
- Use the daredevil mode for porting (default: False).
42
-
43
- Example Usage:
44
- ```
45
- viur tool 2to3 /path/to/project
46
- ```
47
-
48
- :return: None
49
- """
50
- command = f"viur-2to3 {path}"
51
- for option, value in kwargs.items():
52
- if value:
53
- command += f" --{option}"
54
-
55
- os.system(command)
56
-
57
- @tool.command()
58
- @click.option('--version', '-v')
59
- @click.option('--package', '-p')
60
- @click.option('--target', '-t')
61
- @click.option('--help', '-h')
62
- def pyodide(additional_args, version, package, target, help):
63
- """
64
- The 'pyodide' command allows you to run the 'get_pyodide' command for Pyodide installation.
65
-
66
- :param additional_args: tuple
67
- Additional arguments to pass to the 'get_pyodide' command.
68
- :param version: str, optional
69
- Specify the version of Pyodide.
70
- :param package: str, optional
71
- Specify the package for Pyodide.
72
- :param target: str, optional
73
- Specify the target for Pyodide.
74
- :param help: bool, optional
75
- Display help for the 'get_pyodide' command.
76
-
77
- Example Usage:
78
- ```
79
- viur tool pyodide -v 0.19.1 -p mypackage -t mytarget
80
- ```
81
-
82
- :return: None
83
- """
84
- command = "get-pyodide"
85
- if help:
86
- os.system("get-pyodide -h")
87
-
88
- if version:
89
- command += f" -v {version}"
90
-
91
- if package:
92
- command += f" -p {package}"
93
-
94
- if target:
95
- command += f" -t {target}"
96
-
97
- os.system(command)
98
-
99
-
100
- @tool.command()
101
- def ssl_fix():
102
- """
103
- SSL certificate fix for macOS.
104
-
105
- The 'ssl_fix' command is used to perform an SSL certificate fix for macOS.
106
-
107
- Example Usage:
108
- ```
109
- viur tool ssl_fix
110
- ```
111
-
112
- :return: None
113
- """
114
- os.system("chmod +x scripts/macos_certificate_fix.command && ./scripts/macos_certificate_fix.command")
1
+ import click
2
+ import os
3
+
4
+ from . import cli
5
+
6
+
7
+ @cli.group()
8
+ def tool():
9
+ """
10
+ Run different ViUR-related scripts.
11
+
12
+ The 'tool' group allows you to execute various ViUR-related scripts that help with tasks such as project porting,
13
+ Pyodide installation, and SSL certificate fixes.
14
+
15
+ Available Commands:
16
+
17
+ - 'pyodide': Run the get_pyodide command.
18
+
19
+ - 'ssl_fix': SSL certificate fix for macOS.
20
+ """
21
+
22
+
23
+ @tool.command()
24
+ @click.option('--version', '-v')
25
+ @click.option('--package', '-p')
26
+ @click.option('--target', '-t')
27
+ @click.option('--help', '-h')
28
+ def pyodide(additional_args, version, package, target, help):
29
+ """
30
+ The 'pyodide' command allows you to run the 'get_pyodide' command for Pyodide installation.
31
+
32
+ :param additional_args: tuple
33
+ Additional arguments to pass to the 'get_pyodide' command.
34
+ :param version: str, optional
35
+ Specify the version of Pyodide.
36
+ :param package: str, optional
37
+ Specify the package for Pyodide.
38
+ :param target: str, optional
39
+ Specify the target for Pyodide.
40
+ :param help: bool, optional
41
+ Display help for the 'get_pyodide' command.
42
+
43
+ Example Usage:
44
+ ```
45
+ viur tool pyodide -v 0.19.1 -p mypackage -t mytarget
46
+ ```
47
+
48
+ :return: None
49
+ """
50
+ command = "get-pyodide"
51
+ if help:
52
+ os.system("get-pyodide -h")
53
+
54
+ if version:
55
+ command += f" -v {version}"
56
+
57
+ if package:
58
+ command += f" -p {package}"
59
+
60
+ if target:
61
+ command += f" -t {target}"
62
+
63
+ os.system(command)
64
+
65
+
66
+ @tool.command()
67
+ def ssl_fix():
68
+ """
69
+ SSL certificate fix for macOS.
70
+
71
+ The 'ssl_fix' command is used to perform an SSL certificate fix for macOS.
72
+
73
+ Example Usage:
74
+ ```
75
+ viur tool ssl_fix
76
+ ```
77
+
78
+ :return: None
79
+ """
80
+ os.system("chmod +x scripts/macos_certificate_fix.command && ./scripts/macos_certificate_fix.command")
@@ -1,3 +1,3 @@
1
- __version__ = "2.0.2"
1
+ __version__ = "2.1.0.dev1"
2
2
  MINIMAL_PIPENV = "2023.11.15"
3
3
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: viur_cli
3
- Version: 2.0.2
3
+ Version: 2.1.0.dev1
4
4
  Summary: Command-line interface for ViUR application maintenance.
5
5
  Home-page: https://github.com/viur-framework/viur-cli
6
6
  Author: Andreas H. Kelch
@@ -194,6 +194,12 @@ It contains the default viur project profile and it can be expanded with several
194
194
  "kind": "npm",
195
195
  "source": ""
196
196
  }
197
+ /* OPTIONAL arguments, can be set in default or in a specific profile */
198
+ "appyaml": "app_stub.yaml", // Use a name other than "app.yaml"
199
+ "appyaml_substitition": true, // Set to true to replace only standard variables in app.yaml
200
+ "appyaml_substitition": { // Set to an object to replace these in addition to the standard variables in app.yaml
201
+ "$REGION": "europe-west3"
202
+ }
197
203
  },
198
204
  "gcloud": {
199
205
  "functions": { //Declarations for a cloud function
@@ -36,5 +36,4 @@ src/viur_cli/scriptor/scriptor/utils.py
36
36
  src/viur_cli/scriptor/scriptor/viur.py
37
37
  src/viur_cli/scriptor/scriptor/writer.py
38
38
  src/viur_cli/scripts/__init__.py
39
- src/viur_cli/scripts/get_pyodide.py
40
- src/viur_cli/scripts/viur_2to3.py
39
+ src/viur_cli/scripts/get_pyodide.py
@@ -1,4 +1,3 @@
1
1
  [console_scripts]
2
2
  get-pyodide = viur_cli.scripts.get_pyodide:main
3
3
  viur = viur_cli:cli
4
- viur-2to3 = viur_cli.scripts.viur_2to3:main
@@ -1,153 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Naive ViUR3 project porting script with a simple search & replace mechanism using lookup table.
4
- """
5
-
6
- import os, argparse, difflib
7
-
8
- # Naive lookup table. Could be done better later...
9
- lookup = {
10
- # old: new
11
- "BasicApplication": "SkelModule",
12
- "addItemSuccess": "addSuccess",
13
- "callDeferred": "CallDeferred",
14
- "editItemSuccess": "editSuccess",
15
- "from server import": "from viur.core import",
16
- "from server.bones import": "from viur.core.bones import",
17
- "getEmtpyValueFunc": "getEmptyValueFunc",
18
- "isLocalDevelopmentServer": "conf[\"viur.instance.is_dev_server\"]",
19
- "onItemAdded": "onAdded",
20
- "onItemDeleted": "onDeleted",
21
- "onItemEdited": "onEdited",
22
- "projectID": "conf[\"viur.instance.project_id\"]",
23
- "utils.currentLanguage": "current.language",
24
- "utils.currentRequestData": "current.request_data",
25
- "utils.currentRequest": "current.request",
26
- "utils.currentSession": "current.session",
27
- "utils.getCurrentUser": "current.user.get",
28
- "utils.isLocalDevelopmentServer": "conf[\"viur.instance.is_dev_server\"]",
29
- "utils.projectID": "conf[\"viur.instance.project_id\"]",
30
- "clearUpdateTag=True": "update_relations=False",
31
- "seoLanguageMap": "seo_language_map", # 800
32
- "forcePost": "force_post", # 800
33
- "forceSSL": "force_ssl", # 800
34
- "internalExposed": "internal_exposed", # 800
35
- "Session.sameSite": "Session.same_site",
36
- "Session.useSessionCookie": "Session.use_session_cookie",
37
- "Session.cookieName": "Session.cookie_name",
38
- }
39
-
40
- bones = [
41
- "base",
42
- "boolean",
43
- "captcha",
44
- "color",
45
- "credential",
46
- "date",
47
- "email",
48
- "file",
49
- "key",
50
- "numeric",
51
- "password",
52
- "randomSlice",
53
- "raw",
54
- "record",
55
- "relational",
56
- "selectCountry",
57
- "select",
58
- "sortindex",
59
- "spatial",
60
- "string",
61
- "text",
62
- "treeLeaf",
63
- "treeNode",
64
- "user"
65
- ]
66
-
67
- lookup.update({
68
- f"{name}Bone": f"{name[0].upper()}{name[1:]}Bone" for name in bones
69
- })
70
-
71
-
72
- def make_2to3(args, filename):
73
- """
74
- Performs the conversion on a file with the provided options.
75
- """
76
- with open(filename, "r") as f:
77
- original_content = content = f.read()
78
-
79
- count = 0
80
- for k, v in lookup.items():
81
- if k in content:
82
- content = content.replace(k, v)
83
- count += 1
84
-
85
- if count:
86
- if not args.dryrun:
87
- if not args.daredevil:
88
- os.rename(filename, filename + ".bak")
89
-
90
- with open(filename, "w") as f:
91
- f.write(content)
92
-
93
- print("Modified %r" % filename)
94
- else:
95
- print(
96
- "\n".join(
97
- difflib.unified_diff(
98
- original_content.splitlines(),
99
- content.splitlines(),
100
- filename,
101
- filename
102
- )
103
- )
104
- )
105
-
106
-
107
- def main():
108
- # Get arguments
109
- ap = argparse.ArgumentParser(
110
- description="ViUR3 porting tool"
111
- )
112
-
113
- ap.add_argument(
114
- "path",
115
- type=str,
116
- help="Path to file or folder"
117
- )
118
-
119
- ap.add_argument(
120
- "-d", "--dryrun",
121
- action="store_true",
122
- help="Dry-run for testing, don't modify files"
123
- )
124
- ap.add_argument(
125
- "-x", "--daredevil",
126
- action="store_true",
127
- help="Don't make backups of files, just replace and deal with it"
128
- )
129
-
130
- args = ap.parse_args()
131
-
132
- if os.path.isfile(args.path):
133
- make_2to3(args, args.path)
134
- else:
135
- assert os.path.isdir(args.path), f"The path {args.path!r} is invalid!"
136
-
137
- # Iterate all files in current folder
138
- for root, dirs, files in os.walk(args.path):
139
- # Ignore ViUR library folders
140
- if any(ignore in root for ignore in ["viur", "flare", "html5"]):
141
- continue
142
-
143
- for filename in files:
144
- # Ignore anything without a .py-extension
145
- ext = os.path.splitext(filename)[1].lower()[1:]
146
- if ext not in ["py"]:
147
- continue
148
-
149
- make_2to3(args, os.path.join(root, filename))
150
-
151
-
152
- if __name__ == "__main__":
153
- main()
@@ -1,51 +0,0 @@
1
- import click
2
- import os
3
- from .conf import config
4
- from .cli import cli
5
- from .utils import *
6
-
7
-
8
- @cli.command()
9
- @click.argument("name")
10
- @click.pass_context
11
- def create(ctx, name):
12
- """
13
- Create a new ViUR project.
14
-
15
- The 'create' command allows you to create a new ViUR project by cloning the ViUR base project and configuring it.
16
- You can specify the name of the new project as the 'name' argument.
17
-
18
- The 'create' command performs the following steps:
19
-
20
- 1. Clones the ViUR base project from the official GitHub repository.
21
-
22
- 2. Configures the new project by running 'clean-base.py'.
23
-
24
- 3. Optionally configures the project as a new gcloud project (if confirmed).
25
-
26
- Note:
27
-
28
- - This command initializes the new ViUR project based on the ViUR base project.
29
-
30
- - Make sure to provide a unique project name to avoid conflicts with existing folders.
31
-
32
- """
33
- if os.path.exists(f'./{name}'):
34
- echo_error(f'"{name}" Folder exists. Please use a different name or remove this folder ./{name}')
35
- return
36
-
37
- # fetch base project
38
- os.system(
39
- f'git clone https://github.com/viur-framework/viur-base.git {name}')
40
- project_json_path = f'./{name}/project.json'
41
-
42
- # collect project info
43
- conf = config.get_profile("default")
44
- appname = conf['application_name']
45
-
46
- # run clean-base
47
- os.system(f'cd ./{name} && python3 clean-base.py -A={appname}')
48
-
49
- # run gcloud config
50
- if click.confirm(f'Do you want to configure "{appname}" as a new gcloud project?'):
51
- os.system(f'cd ./{name} && ./viur-gcloud-setup.sh {appname}')
File without changes
File without changes
File without changes