diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..d28ae8116 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +venv/ +*.pyc +__pycache__/ +coldfront.db +coldfront.egg-info +.gitignore +script-export-coldfront/ +nginx/ +docs/ diff --git a/.env b/.env new file mode 100644 index 000000000..6cb233acb --- /dev/null +++ b/.env @@ -0,0 +1,45 @@ +#ColdFront config +#DEBUG=True +#DJANGO_SUPERUSER_USERNAME='superuser' +#DJANGO_SUPERUSER_EMAIL='mail@mail.example' +#DJANGO_SUPERUSER_PASSWORD='password' +#SECRET_KEY=klajsfkljasdfkljasdfkljadsfjklads +#CENTER_NAME="BlaBla Cloud" +#CENTER_HELP_URL="https://blabla.it/" +#PLUGIN_SLURM: "True" + +#Ldap config: +#PLUGIN_AUTH_LDAP: "True" +#AUTH_LDAP_SERVER_URI: "ldaps://ldapserver" +#AUTH_LDAP_START_TLS: "False" +#AUTH_LDAP_BIND_DN: 'CN=aa,DC=aa,DC=aa' +#AUTH_LDAP_BIND_PASSWORD: 'secretpassword' +#AUTH_LDAP_USER_SEARCH_BASE: 'DC=aa,DC=aa' +#AUTH_LDAP_GROUP_SEARCH_BASE: 'cn=aa,ou=aa,dc=aa,dc=aa' +#AUTH_LDAP_MIRROR_GROUPS: "False" +#AUTH_LDAP_BIND_AS_AUTHENTICATING_USER: "False" + +#ONDEMAND_URL: "http://ondemand.hpc" + +#Redis config +#REDIS_HOST=127.0.0.1 +#REDIS_PORT=6379 +#REDIS_DB=0 + +#Queue configuration +#Q_CLUSTER_NAME=coldfront +#Q_CLUSTER_TIMEOUT=60 + + +# Database configuration for ColdFront service +DATABASE_HOST='coldfront_database' +DATABASE_PORT=3306 +DATABASE_NAME='coldfront' +DATABASE_USER='coldfront' +DATABASE_PASSWORD='nomoresecret' + +# Environment variables for the ColdFront Worker service +#DEBUG=True +#INITIAL_SETUP=False +#LOAD_TEST_DATA=False +#REDIS_HOST="coldfront_redis" diff --git a/.gitignore b/.gitignore index 8754014b0..b2de8f808 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,9 @@ coldfront.db *.code-workspace .vscode db.json -.env .devcontainer/* .bin/* +docker-clean.bat +script-export-coldfront/clusters +script-export-coldfront/ldapserver +script-export-coldfront/storage diff --git a/Dockerfile b/Dockerfile index cb550c69c..e74c22ef4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,26 @@ -FROM python:3.8 +FROM python:3.9-slim-bullseye + RUN apt-get update \ && apt-get install -y --no-install-recommends \ + && apt-get install -y netcat \ + && apt-get install -y default-libmysqlclient-dev build-essential \ + && apt-get install -y libldap2-dev libsasl2-dev \ && rm -rf /var/lib/apt/lists/* -WORKDIR /usr/src/app +RUN pip3 config --user set global.progress_bar off + +WORKDIR /opt COPY requirements.txt ./ -RUN pip3 install -r requirements.txt +RUN pip3 install --upgrade pip +RUN pip3 install -r /opt/requirements.txt COPY . . +RUN python3 setup.py build +RUN python3 setup.py install +RUN pip3 install /opt +ENV DEBUG="True" -RUN python3 ./manage.py initial_setup -RUN python3 ./manage.py load_test_data +EXPOSE 8000 -ENV DEBUG=True -EXPOSE 8000 -CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"] +CMD [ "/opt/entrypoint.sh" ] \ No newline at end of file diff --git a/coldfront/__init__.py b/coldfront/__init__.py index 912fb13f1..de6b096c7 100644 --- a/coldfront/__init__.py +++ b/coldfront/__init__.py @@ -1,10 +1,14 @@ +from __future__ import absolute_import, unicode_literals import os import sys -__version__ = '1.1.6' + +__version__ = '1.1.7' VERSION = __version__ + + def manage(): os.environ.setdefault("DJANGO_SETTINGS_MODULE", "coldfront.config.settings") from django.core.management import execute_from_command_line diff --git a/coldfront/config/base.py b/coldfront/config/base.py index d9f71d972..9cc3a5770 100644 --- a/coldfront/config/base.py +++ b/coldfront/config/base.py @@ -21,7 +21,8 @@ SECRET_KEY = ENV.str('SECRET_KEY', default='') if len(SECRET_KEY) == 0: SECRET_KEY = get_random_secret_key() - + +SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") #------------------------------------------------------------------------------ # Locale settings #------------------------------------------------------------------------------ @@ -98,8 +99,14 @@ # Django Q #------------------------------------------------------------------------------ Q_CLUSTER = { + 'name': ENV.str('Q_CLUSTER_NAME', default='coldfront'), 'timeout': ENV.int('Q_CLUSTER_TIMEOUT', default=120), 'retry': ENV.int('Q_CLUSTER_RETRY', default=120), + 'redis': { + 'host': ENV.str('REDIS_HOST', default='127.0.0.1'), + 'port': ENV.int('REDIS_PORT', default=6379), + 'db': ENV.int('REDIS_DB', default=0), + }, } diff --git a/coldfront/config/plugins/storage.py b/coldfront/config/plugins/storage.py new file mode 100644 index 000000000..e42fcd6c8 --- /dev/null +++ b/coldfront/config/plugins/storage.py @@ -0,0 +1,8 @@ +from coldfront.config.base import INSTALLED_APPS +from coldfront.config.env import ENV + +INSTALLED_APPS += [ + 'coldfront.plugins.storage', +] + +#VARIABLE = ENV.str('VARIABLE', default='variable') diff --git a/coldfront/config/settings.py b/coldfront/config/settings.py index 1b0f06dd8..76e0dbd08 100644 --- a/coldfront/config/settings.py +++ b/coldfront/config/settings.py @@ -22,6 +22,7 @@ 'PLUGIN_AUTH_OIDC': 'plugins/openid.py', 'PLUGIN_AUTH_LDAP': 'plugins/ldap.py', 'PLUGIN_LDAP_USER_SEARCH': 'plugins/ldap_user_search.py', + 'PLUGIN_STORAGE': 'plugins/storage.py', } # This allows plugins to be enabled via environment variables. Can alternatively diff --git a/coldfront/core/allocation/admin.py b/coldfront/core/allocation/admin.py index 729c2faad..60b9b183c 100644 --- a/coldfront/core/allocation/admin.py +++ b/coldfront/core/allocation/admin.py @@ -1,4 +1,5 @@ import textwrap +from django import forms from django.contrib import admin from django.utils.translation import gettext_lazy as _ @@ -113,11 +114,34 @@ def save_formset(self, request, form, formset, change): class AttributeTypeAdmin(admin.ModelAdmin): list_display = ('name', ) +class AllocationAttributeTypeForm(forms.ModelForm): + class Meta: + model = AllocationAttributeType + fields = '__all__' + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['get_usage_command'].widget = forms.TextInput(attrs={'size': 40}) + self.fields['get_usage_command'].required = False + @admin.register(AllocationAttributeType) class AllocationAttributeTypeAdmin(admin.ModelAdmin): + form = AllocationAttributeTypeForm list_display = ('pk', 'name', 'attribute_type', 'has_usage', 'is_private') + fields = ('name', 'attribute_type', 'has_usage', 'get_usage_command', 'is_required', 'is_unique', 'is_private', 'is_changeable') + + class Media: + js = ('custom/allocation_attribute_type.js',) + def get_fields(self, request, obj=None): + fields = super().get_fields(request, obj) + return fields + + def save_model(self, request, obj, form, change): + if obj.has_usage and not obj.get_usage_command: + obj.get_usage_command = '' # Assicurati che questo sia l'effetto desiderato + super().save_model(request, obj, form, change) class AllocationAttributeUsageInline(admin.TabularInline): model = AllocationAttributeUsage @@ -361,4 +385,3 @@ class AllocationChangeRequestAdmin(admin.ModelAdmin): @admin.register(AllocationAttributeChangeRequest) class AllocationChangeStatusChoiceAdmin(admin.ModelAdmin): list_display = ('pk', 'allocation_change_request', 'allocation_attribute', 'new_value', ) - diff --git a/coldfront/core/allocation/migrations/0006_alter_historicalallocation_options_and_more.py b/coldfront/core/allocation/migrations/0006_alter_historicalallocation_options_and_more.py new file mode 100644 index 000000000..c71378702 --- /dev/null +++ b/coldfront/core/allocation/migrations/0006_alter_historicalallocation_options_and_more.py @@ -0,0 +1,86 @@ +# Generated by Django 4.2.11 on 2024-07-22 14:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('allocation', '0005_auto_20211117_1413'), + ] + + operations = [ + migrations.AlterModelOptions( + name='historicalallocation', + options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical allocation', 'verbose_name_plural': 'historical allocations'}, + ), + migrations.AlterModelOptions( + name='historicalallocationattribute', + options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical allocation attribute', 'verbose_name_plural': 'historical allocation attributes'}, + ), + migrations.AlterModelOptions( + name='historicalallocationattributechangerequest', + options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical allocation attribute change request', 'verbose_name_plural': 'historical allocation attribute change requests'}, + ), + migrations.AlterModelOptions( + name='historicalallocationattributetype', + options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical allocation attribute type', 'verbose_name_plural': 'historical allocation attribute types'}, + ), + migrations.AlterModelOptions( + name='historicalallocationattributeusage', + options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical allocation attribute usage', 'verbose_name_plural': 'historical allocation attribute usages'}, + ), + migrations.AlterModelOptions( + name='historicalallocationchangerequest', + options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical allocation change request', 'verbose_name_plural': 'historical allocation change requests'}, + ), + migrations.AlterModelOptions( + name='historicalallocationuser', + options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical allocation user', 'verbose_name_plural': 'historical Allocation User Status'}, + ), + migrations.AddField( + model_name='allocationattributetype', + name='get_usage_command', + field=models.TextField(blank=True, null=True), + ), + migrations.AddField( + model_name='historicalallocationattributetype', + name='get_usage_command', + field=models.TextField(blank=True, null=True), + ), + migrations.AlterField( + model_name='historicalallocation', + name='history_date', + field=models.DateTimeField(db_index=True), + ), + migrations.AlterField( + model_name='historicalallocationattribute', + name='history_date', + field=models.DateTimeField(db_index=True), + ), + migrations.AlterField( + model_name='historicalallocationattributechangerequest', + name='history_date', + field=models.DateTimeField(db_index=True), + ), + migrations.AlterField( + model_name='historicalallocationattributetype', + name='history_date', + field=models.DateTimeField(db_index=True), + ), + migrations.AlterField( + model_name='historicalallocationattributeusage', + name='history_date', + field=models.DateTimeField(db_index=True), + ), + migrations.AlterField( + model_name='historicalallocationchangerequest', + name='history_date', + field=models.DateTimeField(db_index=True), + ), + migrations.AlterField( + model_name='historicalallocationuser', + name='history_date', + field=models.DateTimeField(db_index=True), + ), + ] diff --git a/coldfront/core/allocation/models.py b/coldfront/core/allocation/models.py index b89826b2c..ebbb89a05 100644 --- a/coldfront/core/allocation/models.py +++ b/coldfront/core/allocation/models.py @@ -404,9 +404,11 @@ class AllocationAttributeType(TimeStampedModel): is_changeable (bool): indicates whether or not the attribute type is changeable """ + attribute_type = models.ForeignKey(AttributeType, on_delete=models.CASCADE) name = models.CharField(max_length=50) has_usage = models.BooleanField(default=False) + get_usage_command = models.TextField(blank=True, null=True) # Campo aggiornato is_required = models.BooleanField(default=False) is_unique = models.BooleanField(default=False) is_private = models.BooleanField(default=True) @@ -416,6 +418,11 @@ class AllocationAttributeType(TimeStampedModel): def __str__(self): return '%s' % (self.name) + def clean(self): + super().clean() + if self.has_usage and not self.get_usage_command: + raise ValidationError('Command string is required when has_usage is True.') + class Meta: ordering = ['name', ] diff --git a/coldfront/core/allocation/tasks.py b/coldfront/core/allocation/tasks.py index e1a37fc05..93afab141 100644 --- a/coldfront/core/allocation/tasks.py +++ b/coldfront/core/allocation/tasks.py @@ -1,12 +1,13 @@ import datetime # import the logging library import logging - -from coldfront.core.allocation.models import (Allocation, - AllocationStatusChoice) +import subprocess +from coldfront.core.allocation.models import (Allocation, AllocationStatusChoice, AllocationAttribute, AllocationAttributeUsage, AllocationAttributeType) from coldfront.core.user.models import User from coldfront.core.utils.common import import_from_settings from coldfront.core.utils.mail import send_email_template +from django_q.tasks import async_task +from django_q.models import Schedule # Get an instance of a logger logger = logging.getLogger(__name__) @@ -206,4 +207,85 @@ def send_expiry_emails(): admin_template_context, EMAIL_SENDER, [EMAIL_ADMIN_LIST,] - ) \ No newline at end of file + ) + +def update_allocations_usage(): + print("Starting update_allocations_usage task...") + + # Find all allocation attribute types that are consumable and have a defined usage command + consumable_attributes = AllocationAttributeType.objects.filter( + has_usage=True, get_usage_command__isnull=False + ) + print(f"Found {consumable_attributes.count()} consumable attributes with usage commands.") + + for attr_type in consumable_attributes: + print(f"Processing attribute type: {attr_type.name}") + + # Find all AllocationAttributes that use this attribute type + allocation_attributes = AllocationAttribute.objects.filter( + allocation_attribute_type=attr_type + ) + print(f"Found {allocation_attributes.count()} allocation attributes for type {attr_type.name}.") + + for allocation_attr in allocation_attributes: + print(f"Processing allocation attribute ID: {allocation_attr.id}") + + # Find the associated Allocation object + allocation = Allocation.objects.filter( + pk=allocation_attr.pk # Assumes there is a relationship between Allocation and AllocationAttribute + ).first() + + if allocation: + project = allocation.project + print(f"Project for allocation attribute ID {allocation_attr.id}: {project}") + else: + print(f"Allocation not found for attribute ID {allocation_attr.id}") + continue + + # Retrieve the value of the slurm_account_name associated with the allocation + slurm_account_name_attr = AllocationAttribute.objects.filter( + allocation_attribute_type__name='slurm_account_name', + allocation=allocation + ).first() + + if slurm_account_name_attr: + slurm_account_name = slurm_account_name_attr.value + print(f"Slurm account name for allocation ID {allocation_attr.id}: {slurm_account_name}") + else: + print(f"Slurm account name attribute not found for allocation ID {allocation_attr.id}") + continue + + # Retrieve the command to execute + command = attr_type.get_usage_command + if command: + # Replace "%SLURM_ACCOUNT_NAME%" in the command if present + command = command.replace("%SLURM_ACCOUNT_NAME%", slurm_account_name) + print(f"Executing command: {command}") + + try: + # Execute the command + result = subprocess.run(command, shell=True, capture_output=True, text=True) + print(f"Command output: {result.stdout}") + + # Save the returned value as an integer in the database + usage_value = int(result.stdout.strip()) + print(f"Parsed usage value: {usage_value}") + + # Find or create an AllocationAttributeUsage object + usage, created = AllocationAttributeUsage.objects.get_or_create( + allocation_attribute=allocation_attr, + defaults={'value': usage_value} + ) + + if created: + print(f"Created new AllocationAttributeUsage for attribute ID {allocation_attr.id} with value {usage_value}") + else: + # Update the value if the object already exists + usage.value = usage_value + usage.save() + print(f"Updated AllocationAttributeUsage for attribute ID {allocation_attr.id} with new value {usage_value}") + + except Exception as e: + print(f"Error executing command: {e}") + + print("Finished update_allocations_usage task.") diff --git a/coldfront/core/allocation/utils.py b/coldfront/core/allocation/utils.py index b166997b7..0df1770d9 100644 --- a/coldfront/core/allocation/utils.py +++ b/coldfront/core/allocation/utils.py @@ -58,4 +58,4 @@ def get_user_resources(user_obj): def test_allocation_function(allocation_pk): - print('test_allocation_function', allocation_pk) + print('test_allocation_function', allocation_pk) \ No newline at end of file diff --git a/coldfront/core/allocation/views.py b/coldfront/core/allocation/views.py index 216c3531c..050a542cc 100644 --- a/coldfront/core/allocation/views.py +++ b/coldfront/core/allocation/views.py @@ -57,7 +57,8 @@ from coldfront.core.allocation.utils import (generate_guauge_data_from_usage, get_user_resources) from coldfront.core.project.models import (Project, ProjectUser, ProjectPermission, - ProjectUserStatusChoice) + ProjectUserStatusChoice, ProjectAttribute, ProjectAttributeUsage) +from coldfront.core.project.views import (ProjectDetailView) from coldfront.core.resource.models import Resource from coldfront.core.utils.common import get_domain_url, import_from_settings from coldfront.core.utils.mail import send_allocation_admin_email, send_allocation_customer_email @@ -209,6 +210,29 @@ def post(self, request, *args, **kwargs): allocation_obj.is_changeable = form_data.get('is_changeable') allocation_obj.status = form_data.get('status') + # Simula la creazione della vista per ottenere i dati sugli attributi + project_view = ProjectDetailView() + project_view.kwargs = {'pk': allocation_obj.project.pk} + allocations_data = ProjectDetailView.get_allocation_attributes_usage(request, allocation_obj.project.pk) + + # Use allocations_data as needed + total_values = allocations_data['total_values'] + print(f"Total values of allocation attributes: {total_values}") + + # Update the project's attributes usage + project_obj = allocation_obj.project + for attribute_name, total_value in total_values.items(): + try: + project_attribute = project_obj.projectattribute_set.get(proj_attr_type__name=attribute_name) + project_attribute_usage = project_attribute.projectattributeusage + project_attribute_usage.value = total_value + project_attribute_usage.save() + except ProjectAttribute.DoesNotExist: + logger.error(f"Project attribute '{attribute_name}' does not exist.") + except ProjectAttributeUsage.DoesNotExist: + logger.error(f"Project attribute usage for '{attribute_name}' does not exist.") + + if 'approve' in action: allocation_obj.status = AllocationStatusChoice.objects.get(name='Active') elif action == 'deny': @@ -1842,4 +1866,4 @@ def get(self, request, pk): messages.success( request, 'Allocation attribute change request successfully deleted.') - return HttpResponseRedirect(reverse('allocation-change-detail', kwargs={'pk': allocation_change_pk})) + return HttpResponseRedirect(reverse('allocation-change-detail', kwargs={'pk': allocation_change_pk})) \ No newline at end of file diff --git a/coldfront/core/grant/migrations/0003_alter_historicalgrant_options_and_more.py b/coldfront/core/grant/migrations/0003_alter_historicalgrant_options_and_more.py new file mode 100644 index 000000000..b4dfea3bd --- /dev/null +++ b/coldfront/core/grant/migrations/0003_alter_historicalgrant_options_and_more.py @@ -0,0 +1,54 @@ +# Generated by Django 4.2.11 on 2024-07-22 14:18 + +import coldfront.core.grant.models +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('grant', '0002_auto_20230406_1310'), + ] + + operations = [ + migrations.AlterModelOptions( + name='historicalgrant', + options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical grant', 'verbose_name_plural': 'historical Grants'}, + ), + migrations.AlterField( + model_name='grant', + name='direct_funding', + field=coldfront.core.grant.models.MoneyField(max_length=100), + ), + migrations.AlterField( + model_name='grant', + name='percent_credit', + field=coldfront.core.grant.models.PercentField(max_length=100, validators=[django.core.validators.MaxValueValidator(100)]), + ), + migrations.AlterField( + model_name='grant', + name='total_amount_awarded', + field=coldfront.core.grant.models.MoneyField(max_length=100), + ), + migrations.AlterField( + model_name='historicalgrant', + name='direct_funding', + field=coldfront.core.grant.models.MoneyField(max_length=100), + ), + migrations.AlterField( + model_name='historicalgrant', + name='history_date', + field=models.DateTimeField(db_index=True), + ), + migrations.AlterField( + model_name='historicalgrant', + name='percent_credit', + field=coldfront.core.grant.models.PercentField(max_length=100, validators=[django.core.validators.MaxValueValidator(100)]), + ), + migrations.AlterField( + model_name='historicalgrant', + name='total_amount_awarded', + field=coldfront.core.grant.models.MoneyField(max_length=100), + ), + ] diff --git a/coldfront/core/project/admin.py b/coldfront/core/project/admin.py index 61dd88422..a6b2f9f2a 100644 --- a/coldfront/core/project/admin.py +++ b/coldfront/core/project/admin.py @@ -104,8 +104,10 @@ class AttributeTypeAdmin(admin.ModelAdmin): @admin.register(ProjectAttributeType) class ProjectAttributeTypeAdmin(admin.ModelAdmin): - list_display = ('pk', 'name', 'attribute_type', 'has_usage', 'is_private') + list_display = ('pk', 'name', 'attribute_type', 'has_usage', 'is_private', 'is_changeable', 'is_default') + class Media: + js = ('custom/project_attribute_type.js',) class ProjectAttributeUsageInline(admin.TabularInline): model = ProjectAttributeUsage diff --git a/coldfront/core/project/forms.py b/coldfront/core/project/forms.py index beeea24e9..2a55f4665 100644 --- a/coldfront/core/project/forms.py +++ b/coldfront/core/project/forms.py @@ -1,6 +1,7 @@ import datetime from django import forms +from django.db.models import Q from django.db.models.functions import Lower from django.shortcuts import get_object_or_404 from ast import Constant @@ -143,7 +144,7 @@ def __init__(self, *args, **kwargs): user =(kwargs.get('initial')).get('user') self.fields['proj_attr_type'].queryset = self.fields['proj_attr_type'].queryset.order_by(Lower('name')) if not user.is_superuser: - self.fields['proj_attr_type'].queryset = self.fields['proj_attr_type'].queryset.filter(is_private=False) + self.fields['proj_attr_type'].queryset = self.fields['proj_attr_type'].queryset.filter(is_private=False, is_changeable=True) class ProjectAttributeDeleteForm(forms.Form): pk = forms.IntegerField(required=False, disabled=True) diff --git a/coldfront/core/project/migrations/0005_alter_historicalproject_options_and_more.py b/coldfront/core/project/migrations/0005_alter_historicalproject_options_and_more.py new file mode 100644 index 000000000..1ba1e9444 --- /dev/null +++ b/coldfront/core/project/migrations/0005_alter_historicalproject_options_and_more.py @@ -0,0 +1,73 @@ +# Generated by Django 4.2.11 on 2024-07-22 14:18 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('project', '0004_auto_20230406_1133'), + ] + + operations = [ + migrations.AlterModelOptions( + name='historicalproject', + options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical project', 'verbose_name_plural': 'historical projects'}, + ), + migrations.AlterModelOptions( + name='historicalprojectattribute', + options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical project attribute', 'verbose_name_plural': 'historical project attributes'}, + ), + migrations.AlterModelOptions( + name='historicalprojectattributetype', + options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical project attribute type', 'verbose_name_plural': 'historical project attribute types'}, + ), + migrations.AlterModelOptions( + name='historicalprojectattributeusage', + options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical project attribute usage', 'verbose_name_plural': 'historical project attribute usages'}, + ), + migrations.AlterModelOptions( + name='historicalprojectreview', + options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical project review', 'verbose_name_plural': 'historical project reviews'}, + ), + migrations.AlterModelOptions( + name='historicalprojectuser', + options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical project user', 'verbose_name_plural': 'historical Project User Status'}, + ), + migrations.AlterField( + model_name='historicalproject', + name='history_date', + field=models.DateTimeField(db_index=True), + ), + migrations.AlterField( + model_name='historicalprojectattribute', + name='history_date', + field=models.DateTimeField(db_index=True), + ), + migrations.AlterField( + model_name='historicalprojectattributetype', + name='history_date', + field=models.DateTimeField(db_index=True), + ), + migrations.AlterField( + model_name='historicalprojectattributeusage', + name='history_date', + field=models.DateTimeField(db_index=True), + ), + migrations.AlterField( + model_name='historicalprojectreview', + name='history_date', + field=models.DateTimeField(db_index=True), + ), + migrations.AlterField( + model_name='historicalprojectuser', + name='history_date', + field=models.DateTimeField(db_index=True), + ), + migrations.AlterUniqueTogether( + name='project', + unique_together={('title', 'pi')}, + ), + ] diff --git a/coldfront/core/project/migrations/0006_historicalprojectattributetype_is_default_and_more.py b/coldfront/core/project/migrations/0006_historicalprojectattributetype_is_default_and_more.py new file mode 100644 index 000000000..3d1b28c8a --- /dev/null +++ b/coldfront/core/project/migrations/0006_historicalprojectattributetype_is_default_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 4.2.11 on 2024-07-30 09:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('project', '0005_alter_historicalproject_options_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='historicalprojectattributetype', + name='is_default', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='historicalprojectattributetype', + name='is_default_value', + field=models.CharField(blank=True, max_length=128, null=True), + ), + migrations.AddField( + model_name='projectattributetype', + name='is_default', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='projectattributetype', + name='is_default_value', + field=models.CharField(blank=True, max_length=128, null=True), + ), + ] diff --git a/coldfront/core/project/models.py b/coldfront/core/project/models.py index 45f9470ac..afaaecf66 100644 --- a/coldfront/core/project/models.py +++ b/coldfront/core/project/models.py @@ -384,6 +384,7 @@ class ProjectAttributeType(TimeStampedModel): is_unique (bool): indicates whether or not the value is unique is_private (bool): indicates whether or not the attribute type is private is_changeable (bool): indicates whether or not the attribute type is changeable + is_default (bool): indicates whether or not the attribute type is default added to all new projects """ attribute_type = models.ForeignKey(AttributeType, on_delete=models.CASCADE) @@ -393,6 +394,8 @@ class ProjectAttributeType(TimeStampedModel): is_unique = models.BooleanField(default=False) is_private = models.BooleanField(default=True) is_changeable = models.BooleanField(default=False) + is_default = models.BooleanField(default=False) + is_default_value = models.CharField(max_length=128, blank=True, null=True) history = HistoricalRecords() def __str__(self): diff --git a/coldfront/core/project/templates/project/project_detail.html b/coldfront/core/project/templates/project/project_detail.html index 16183edf4..328425e18 100644 --- a/coldfront/core/project/templates/project/project_detail.html +++ b/coldfront/core/project/templates/project/project_detail.html @@ -253,20 +253,22 @@

Attri {% for attribute in attributes %} - {% if attribute.proj_attr_type.is_private and is_allowed_to_update_project %} - - {{attribute}} + {% if attribute.proj_attr_type.is_changeable and is_allowed_to_update_project %} + + {{attribute}} {{attribute.value}} Edit - {% else %} - - {{attribute}} + {% else %} + + {{attribute}} {{attribute.value}} - {% if is_allowed_to_update_project %} - Edit + {% if is_superuser and is_allowed_to_update_project %} + Edit + {% else %} + {% endif %} {% endif %} diff --git a/coldfront/core/project/views.py b/coldfront/core/project/views.py index a71dda349..bd77f4eed 100644 --- a/coldfront/core/project/views.py +++ b/coldfront/core/project/views.py @@ -46,6 +46,7 @@ ProjectAttributeUpdateForm) from coldfront.core.project.models import (Project, ProjectAttribute, + ProjectAttributeType, ProjectReview, ProjectReviewStatusChoice, ProjectStatusChoice, @@ -95,11 +96,87 @@ def test_func(self): self.request, 'You do not have permission to view the previous page.') return False + def get_project_attributes_usage(request, pk): + project_obj = get_object_or_404(Project, pk=pk) + + if request.user.is_superuser: + attributes_with_usage = [ + attribute for attribute in project_obj.projectattribute_set.all() + if hasattr(attribute, 'projectattributeusage') + ] + else: + attributes_with_usage = [ + attribute for attribute in project_obj.projectattribute_set.filter(proj_attr_type__is_private=False) + if hasattr(attribute, 'projectattributeusage') + ] + + allocations_data = [] + invalid_attributes = [] + + for attribute in attributes_with_usage: + try: + allocations_data.append({ + 'name': attribute.proj_attr_type.name, + 'value': float(attribute.value), + 'usage': float(attribute.projectattributeusage.value) + }) + except ValueError: + logger.error("Attribute '%s' is not an int but has a usage", attribute.proj_attr_type.name) + invalid_attributes.append(attribute) + + for a in invalid_attributes: + attributes_with_usage.remove(a) + + return {'allocations_data': allocations_data} + + def get_allocation_attributes_usage(request, pk): + project_obj = get_object_or_404(Project, pk=pk) + + allocations = Allocation.objects.filter(project=project_obj) + allocations_data = [] + total_values = {} # Dictionario per accumulare i valori totali per ciascun tipo di attributo + + for allocation in allocations: + attributes_with_usage = [ + attribute for attribute in allocation.allocationattribute_set.all() + if hasattr(attribute, 'allocationattributeusage') + ] + + invalid_attributes = [] + + for attribute in attributes_with_usage: + try: + value = float(attribute.value) + usage = float(attribute.allocationattributeusage.value) + attribute_name = attribute.allocation_attribute_type.name + + allocations_data.append({ + 'allocation': allocation.pk, + 'name': attribute_name, + 'value': value, + 'usage': usage + }) + + if attribute_name not in total_values: + total_values[attribute_name] = 0.0 + total_values[attribute_name] += value + + except ValueError: + logger.error("Attribute '%s' is not an int but has a usage", attribute.allocation_attribute_type.name) + invalid_attributes.append(attribute) + + for a in invalid_attributes: + attributes_with_usage.remove(a) + + return {'allocations_data': allocations_data, 'total_values': total_values} + + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # Can the user update the project? if self.request.user.is_superuser: context['is_allowed_to_update_project'] = True + context['is_superuser'] = True elif self.object.projectuser_set.filter(user=self.request.user).exists(): project_user = self.object.projectuser_set.get( user=self.request.user) @@ -468,13 +545,23 @@ def form_valid(self, form): project_obj.save() self.object = project_obj - project_user_obj = ProjectUser.objects.create( + # Creazione del ProjectUser + ProjectUser.objects.create( user=self.request.user, project=project_obj, role=ProjectUserRoleChoice.objects.get(name='Manager'), status=ProjectUserStatusChoice.objects.get(name='Active') ) + # Aggiunta degli attributi predefiniti + default_attributes = ProjectAttributeType.objects.filter(is_default=True) + for attr_type in default_attributes: + ProjectAttribute.objects.create( + proj_attr_type=attr_type, + project=project_obj, + value=attr_type.is_default_value + ) + return super().form_valid(form) def get_success_url(self): @@ -1274,7 +1361,7 @@ def test_func(self): def get_avail_attrs(self, project_obj): if not self.request.user.is_superuser: - avail_attrs = ProjectAttribute.objects.filter(project=project_obj, proj_attr_type__is_private=False) + avail_attrs = ProjectAttribute.objects.filter(project=project_obj, proj_attr_type__is_private=False, proj_attr_type__is_changeable=True) else: avail_attrs = ProjectAttribute.objects.filter(project=project_obj) avail_attrs_dicts = [ diff --git a/coldfront/core/publication/migrations/0005_alter_historicalpublication_options_and_more.py b/coldfront/core/publication/migrations/0005_alter_historicalpublication_options_and_more.py new file mode 100644 index 000000000..c571da4ee --- /dev/null +++ b/coldfront/core/publication/migrations/0005_alter_historicalpublication_options_and_more.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.11 on 2024-07-22 14:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('publication', '0004_add_manual_publication_source'), + ] + + operations = [ + migrations.AlterModelOptions( + name='historicalpublication', + options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical publication', 'verbose_name_plural': 'historical publications'}, + ), + migrations.AlterField( + model_name='historicalpublication', + name='history_date', + field=models.DateTimeField(db_index=True), + ), + ] diff --git a/coldfront/core/research_output/migrations/0002_alter_historicalresearchoutput_options_and_more.py b/coldfront/core/research_output/migrations/0002_alter_historicalresearchoutput_options_and_more.py new file mode 100644 index 000000000..6a39faa8c --- /dev/null +++ b/coldfront/core/research_output/migrations/0002_alter_historicalresearchoutput_options_and_more.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.11 on 2024-07-22 14:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('research_output', '0001_initial'), + ] + + operations = [ + migrations.AlterModelOptions( + name='historicalresearchoutput', + options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical research output', 'verbose_name_plural': 'historical research outputs'}, + ), + migrations.AlterField( + model_name='historicalresearchoutput', + name='history_date', + field=models.DateTimeField(db_index=True), + ), + ] diff --git a/coldfront/core/resource/migrations/0003_alter_historicalresource_options_and_more.py b/coldfront/core/resource/migrations/0003_alter_historicalresource_options_and_more.py new file mode 100644 index 000000000..6d73dd089 --- /dev/null +++ b/coldfront/core/resource/migrations/0003_alter_historicalresource_options_and_more.py @@ -0,0 +1,54 @@ +# Generated by Django 4.2.11 on 2024-07-22 14:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('resource', '0002_auto_20191017_1141'), + ] + + operations = [ + migrations.AlterModelOptions( + name='historicalresource', + options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical resource', 'verbose_name_plural': 'historical resources'}, + ), + migrations.AlterModelOptions( + name='historicalresourceattribute', + options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical resource attribute', 'verbose_name_plural': 'historical resource attributes'}, + ), + migrations.AlterModelOptions( + name='historicalresourceattributetype', + options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical resource attribute type', 'verbose_name_plural': 'historical resource attribute types'}, + ), + migrations.AlterModelOptions( + name='historicalresourcetype', + options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical resource type', 'verbose_name_plural': 'historical resource types'}, + ), + migrations.AlterField( + model_name='historicalresource', + name='history_date', + field=models.DateTimeField(db_index=True), + ), + migrations.AlterField( + model_name='historicalresourceattribute', + name='history_date', + field=models.DateTimeField(db_index=True), + ), + migrations.AlterField( + model_name='historicalresourceattributetype', + name='history_date', + field=models.DateTimeField(db_index=True), + ), + migrations.AlterField( + model_name='historicalresourcetype', + name='history_date', + field=models.DateTimeField(db_index=True), + ), + migrations.AlterField( + model_name='resource', + name='linked_resources', + field=models.ManyToManyField(blank=True, to='resource.resource'), + ), + ] diff --git a/coldfront/core/utils/management/commands/add_scheduled_tasks.py b/coldfront/core/utils/management/commands/add_scheduled_tasks.py index 0f78c8ea7..6606af555 100644 --- a/coldfront/core/utils/management/commands/add_scheduled_tasks.py +++ b/coldfront/core/utils/management/commands/add_scheduled_tasks.py @@ -23,3 +23,7 @@ def handle(self, *args, **options): schedule('coldfront.core.allocation.tasks.send_expiry_emails', schedule_type=Schedule.DAILY, next_run=date) + + schedule('coldfront.core.allocation.tasks.update_allocations_usage', + schedule_type=Schedule.HOURLY, + next_run=date) \ No newline at end of file diff --git a/coldfront/plugins/storage/README.md b/coldfront/plugins/storage/README.md new file mode 100644 index 000000000..2b3342831 --- /dev/null +++ b/coldfront/plugins/storage/README.md @@ -0,0 +1,24 @@ +# Storage Plugin for ColdFront + +The storage plugin allows to dump the Storage Allocations info (Storage Quota (GB), Storage_Group_Name, Status) to a file or standard output. + +## Configuration + + +The plugin checks for Allocations with `Storage Quota (GB)` or `Storage Quota (TB)` and `Storage_Group_Name`. +If are defined, it exports them to a file in the format +`Storage Name|Storage_Group_Name|gigabytes|Status` + +Example: + + root@localhost:/opt# coldfront storage_dump + Found 2 allocations. + Processing allocation ID: 2 + Attributes for allocation 2: {'Storage_Group_Name': 'project_a', 'Storage Quota (GB)': '4000'} + Prepared quota: 4000.0 GB for storage group: project_a, Status: Denied + Processing allocation ID: 3 + Attributes for allocation 3: {} + Beegfs Fast Storage|project_a|4000|Denied + + +To save the output as a file use `-o output.txt` or `--output_file output.txt` diff --git a/coldfront/plugins/storage/__init__.py b/coldfront/plugins/storage/__init__.py new file mode 100644 index 000000000..587f8f46e --- /dev/null +++ b/coldfront/plugins/storage/__init__.py @@ -0,0 +1 @@ +default_app_config = 'coldfront.plugins.storage.apps.StorageConfig' diff --git a/coldfront/plugins/storage/apps.py b/coldfront/plugins/storage/apps.py new file mode 100644 index 000000000..085049d83 --- /dev/null +++ b/coldfront/plugins/storage/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class StorageConfig(AppConfig): + name = 'coldfront.plugins.storage' diff --git a/coldfront/plugins/storage/management/__init__.py b/coldfront/plugins/storage/management/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/coldfront/plugins/storage/management/commands/__init__.py b/coldfront/plugins/storage/management/commands/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/coldfront/plugins/storage/management/commands/storage_dump.py b/coldfront/plugins/storage/management/commands/storage_dump.py new file mode 100644 index 000000000..9adaed2ca --- /dev/null +++ b/coldfront/plugins/storage/management/commands/storage_dump.py @@ -0,0 +1,76 @@ +import logging +from django.core.management.base import BaseCommand +from coldfront.core.allocation.models import ( + Allocation, AllocationAttribute, AllocationStatusChoice +) + +logger = logging.getLogger(__name__) + +class Command(BaseCommand): + help = 'Export storage quotas for allocations to a file or console.' + + def add_arguments(self, parser): + parser.add_argument( + '-o', '--output_file', + type=str, + help='The file to write the storage quotas to. If not provided, output will be printed to the console.' + ) + + def handle(self, *args, **kwargs): + output_file = kwargs.get('output_file') + + # Fetch all allocations + allocations = Allocation.objects.all() + self.stdout.write(self.style.SUCCESS(f'Found {allocations.count()} allocations.')) + + if allocations.count() == 0: + self.stdout.write(self.style.WARNING('No allocations found in the database.')) + return + + output = [] + for allocation in allocations: + self.stdout.write(self.style.SUCCESS(f'Processing allocation ID: {allocation.id}')) + + attributes = AllocationAttribute.objects.filter(allocation=allocation) + attr_dict = {attr.allocation_attribute_type.name: attr.value for attr in attributes} + + self.stdout.write(self.style.SUCCESS(f'Attributes for allocation {allocation.id}: {attr_dict}')) + + storage_group_name_key = 'Storage_Group_Name' + storage_quota_keys = ['Storage Quota (GB)', 'Storage Quota (TB)'] + + # Get resource type + resource_types = [resource.name for resource in allocation.resources.all()] + status_name = allocation.status.name if allocation.status else 'Unknown' + + if (storage_group_name_key in attr_dict and + any(key in attr_dict for key in storage_quota_keys)): + + storage_group_name = attr_dict[storage_group_name_key] + + if 'Storage Quota (GB)' in attr_dict: + quota = attr_dict['Storage Quota (GB)'] + unit = 'GB' + elif 'Storage Quota (TB)' in attr_dict: + quota = float(attr_dict['Storage Quota (TB)']) * 1024 # Convert TB to GB + unit = 'TB' + else: + self.stdout.write(self.style.ERROR(f'No valid storage quota found for allocation ID: {allocation.id}')) + continue + + # Convert quota to GB if needed + quota_gb = float(quota) if unit == 'GB' else quota + for resource_type in resource_types: + output.append(f'{resource_type}|{storage_group_name}|{int(quota_gb)}|{status_name}') + self.stdout.write(self.style.SUCCESS(f'Prepared quota: {quota_gb} GB for storage group: {storage_group_name}, Status: {status_name}')) + + if output_file: + with open(output_file, 'w') as file: + file.write('\n'.join(output)) + self.stdout.write(self.style.SUCCESS(f'Storage quotas successfully exported to {output_file}')) + else: + # Print the output to the console + if output: + self.stdout.write(self.style.SUCCESS('\n'.join(output))) + else: + self.stdout.write(self.style.WARNING('No data to export.')) diff --git a/coldfront/static/custom/allocation_attribute_type.js b/coldfront/static/custom/allocation_attribute_type.js new file mode 100644 index 000000000..ad9bbd46e --- /dev/null +++ b/coldfront/static/custom/allocation_attribute_type.js @@ -0,0 +1,22 @@ +// allocation_attribute_type.js + +document.addEventListener('DOMContentLoaded', function () { + var hasUsageField = document.querySelector('#id_has_usage'); + var getUsageCommandField = document.querySelector('.field-get_usage_command'); + + function toggleGetUsageCommand() { + if (hasUsageField.checked) { + getUsageCommandField.style.display = 'block'; + } else { + getUsageCommandField.style.display = 'none'; + } + } + + if (hasUsageField && getUsageCommandField) { + toggleGetUsageCommand(); + + hasUsageField.addEventListener('change', function () { + toggleGetUsageCommand(); + }); + } +}); diff --git a/coldfront/static/custom/project_attribute_type.js b/coldfront/static/custom/project_attribute_type.js new file mode 100644 index 000000000..00a3aac35 --- /dev/null +++ b/coldfront/static/custom/project_attribute_type.js @@ -0,0 +1,22 @@ +// project_attribute_type.js + +document.addEventListener('DOMContentLoaded', function () { + var isDefaultField = document.querySelector('#id_is_default'); + var isDefaultValueField = document.querySelector('.field-is_default_value'); + + function toggleIsDefaultCommand() { + if (isDefaultField.checked) { + isDefaultValueField.style.display = 'block'; + } else { + isDefaultValueField.style.display = 'none'; + } + } + + if (isDefaultField && isDefaultValueField) { + toggleIsDefaultCommand(); + + isDefaultField.addEventListener('change', function () { + toggleIsDefaultCommand(); + }); + } +}); diff --git a/create_superuser.py b/create_superuser.py new file mode 100644 index 000000000..a7c9d739e --- /dev/null +++ b/create_superuser.py @@ -0,0 +1,19 @@ +import os +import django +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "coldfront.config.settings") + +django.setup() + +from django.contrib.auth.models import User + +def create_superuser(): + # Leggi le variabili di ambiente + username = os.getenv('DJANGO_SUPERUSER_USERNAME', 'superuser') + email = os.getenv('DJANGO_SUPERUSER_EMAIL', 'mail@mail.example') + password = os.getenv('DJANGO_SUPERUSER_PASSWORD', 'password') + + # Crea o aggiorna l'utente super + User.objects.create_superuser(username, email, password) + +if __name__ == '__main__': + create_superuser() diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..a4f8ffb61 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,112 @@ +version: '3.8' + +services: + + nginx: + image: nginx:latest + container_name: nginx + privileged: true + ports: + - "80:80" + - "443:443" + environment: + SERVER_NAME: "coldfront.hpc" + volumes: + - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf + - ./nginx/server.crt:/etc/nginx/ssl/server.crt + - ./nginx/server.key:/etc/nginx/ssl/server.key + - static_volume:/opt/static_root + depends_on: + - coldfront + + coldfront: + build: + context: . + dockerfile: Dockerfile + container_name: coldfront + privileged: true + environment: + DEBUG: "False" + INITIAL_SETUP: "False" #<----- CHANGE TO FALSE AFTER THE FIRTS RUN + LOAD_TEST_DATA: "False" + DJANGO_SUPERUSER_USERNAME: "superuser" + DJANGO_SUPERUSER_EMAIL: "mail@mail.example" + DJANGO_SUPERUSER_PASSWORD: "password" + PLUGIN_SLURM: "True" + PLUGIN_STORAGE: "True" + PLUGIN_AUTH_LDAP: "True" + AUTH_LDAP_SERVER_URI: "ldaps://ldapserver" + AUTH_LDAP_START_TLS: "False" + AUTH_LDAP_BIND_DN: 'CN=aa,DC=aa,DC=aa' + AUTH_LDAP_BIND_PASSWORD: 'secretpassword' + AUTH_LDAP_USER_SEARCH_BASE: 'DC=aa,DC=aa' + AUTH_LDAP_GROUP_SEARCH_BASE: 'cn=aa,ou=aa,dc=aa,dc=aa' + AUTH_LDAP_MIRROR_GROUPS: "False" + AUTH_LDAP_BIND_AS_AUTHENTICATING_USER: "False" + ONDEMAND_URL: "http://ondemand.hpc" + SECRET_KEY: "klajsfkljasdfkljasdfkljadsfjklads" + DATABASE_HOST: 'coldfront_database' + DATABASE_PORT: 3306 + DATABASE_NAME: 'coldfront' + DATABASE_USER: 'coldfront' + DATABASE_PASSWORD: 'nomoresecret' + DB_URL: "mysql://${DATABASE_USER}:${DATABASE_PASSWORD}@${DATABASE_HOST}:${DATABASE_PORT}/${DATABASE_NAME}" + REDIS_HOST: "coldfront_redis" + CENTER_NAME: "Blabla Cloud" + CENTER_HELP_URL: "https://blabla.it/" + PROJECT_ROOT: "/opt" + volumes: + - static_volume:/opt/static_root + - /opt/coldfront/coldfront_dump:/opt/coldfront/coldfront_dump #this folder is used by script-export-coldfront + #- ./coldfront.db:/opt/coldfront.db + ports: + - "8000:8000" + + coldfront_redis: + privileged: true + image: redis:latest + container_name: coldfront_redis + command: redis-server + ports: + - "6379:6379" + + coldfront_worker: + privileged: true + build: + context: . + dockerfile: Dockerfile + container_name: coldfront_worker + command: coldfront qcluster + depends_on: + - coldfront_redis + environment: + SECRET_KEY: "klajsfkljasdfkljasdfkljadsfjklads" + DATABASE_HOST: 'coldfront_database' + DATABASE_PORT: 3306 + DATABASE_NAME: 'coldfront' + DATABASE_USER: 'coldfront' + DATABASE_PASSWORD: 'nomoresecret' + DB_URL: "mysql://${DATABASE_USER}:${DATABASE_PASSWORD}@${DATABASE_HOST}:${DATABASE_PORT}/${DATABASE_NAME}" + REDIS_HOST: "coldfront_redis" + Q_CLUSTER_NAME: "coldfront" + Q_CLUSTER_TIMEOUT: "60" +# volumes: +# - ./coldfront.db:/opt/coldfront.db + + + coldfront_database: + privileged: true + image: mariadb:latest + container_name: coldfront_database + environment: + MYSQL_ROOT_PASSWORD: nomoresecret + MYSQL_DATABASE: coldfront + MYSQL_USER: coldfront + MYSQL_PASSWORD: nomoresecret + volumes: + - mariadb_data:/var/lib/mysql + + +volumes: + mariadb_data: + static_volume: \ No newline at end of file diff --git a/dump.rdb b/dump.rdb new file mode 100644 index 000000000..76ca5d756 Binary files /dev/null and b/dump.rdb differ diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 000000000..88f4a4ad6 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,32 @@ +#!/bin/bash +#set -e + +>&2 echo "Waiting for database..." +DATABASE_PORT=${DATABASE_PORT:-3306} +while ! echo exit | nc $DATABASE_HOST $DATABASE_PORT; do sleep 5; done +>&2 echo "Database is up - Starting" +sleep 10 + + +if [[ "$INITIAL_SETUP" == "True" ]]; then + echo "yes" | coldfront initial_setup + python create_superuser.py +fi + +if [[ "$LOAD_TEST_DATA" == "True" ]]; then + echo "yes" | coldfront load_test_data +fi + +coldfront migrate + +# Avvia il server Django +echo "Avviando il server Django..." +if [[ "$DEBUG" == "True" ]]; then + echo "Starting debug server" + coldfront runserver 0.0.0.0:8000 +else + echo "Starting prod gunicorn server" + echo "yes" | python manage.py collectstatic + python -m gunicorn coldfront.config.wsgi -b 0.0.0.0:8000 --capture-output +fi + diff --git a/nginx/generate_cert.sh b/nginx/generate_cert.sh new file mode 100644 index 000000000..dab2fabe9 --- /dev/null +++ b/nginx/generate_cert.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +# Configura i nomi +PREFIX="coldfront" +DOMAIN=".hpc" + +# Chiave privata e certificato della CA (non necessario per un certificato auto-firmato semplice) +openssl genrsa -out server.key 2048 + +# Genera il certificato SSL auto-firmato +openssl req -new -x509 -days 365 -key server.key -subj "/C=IT/ST=IT/L=LZ/O=$PREFIX Acme/CN=$PREFIX$DOMAIN" -out server.crt + +# Opzionale: verifica il certificato generato +openssl x509 -in server.crt -text -noout diff --git a/nginx/nginx.conf b/nginx/nginx.conf new file mode 100644 index 000000000..755a8b7b9 --- /dev/null +++ b/nginx/nginx.conf @@ -0,0 +1,45 @@ +server { + listen 80; + server_name ${SERVER_NAME}; + + location / { + proxy_pass http://coldfront:8000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /static/ { + autoindex on; + alias /opt/static_root/; + } + + # Redirect all HTTP requests to HTTPS + return 301 https://$host$request_uri; +} + +server { + listen 443 ssl; + server_name ${SERVER_NAME}; + + ssl_certificate /etc/nginx/ssl/server.crt; + ssl_certificate_key /etc/nginx/ssl/server.key; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; + + location / { + proxy_pass http://coldfront:8000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /static/ { + autoindex on; + alias /opt/static_root/; + } +} \ No newline at end of file diff --git a/nginx/server.crt b/nginx/server.crt new file mode 100644 index 000000000..26d73b9fd --- /dev/null +++ b/nginx/server.crt @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDkTCCAnmgAwIBAgIUHJzRRuNK1PLXiD+/ndqOlzS9AqYwDQYJKoZIhvcNAQEL +BQAwWDELMAkGA1UEBhMCSVQxCzAJBgNVBAgMAklUMQswCQYDVQQHDAJMWjEXMBUG +A1UECgwOY29sZGZyb250IEFjbWUxFjAUBgNVBAMMDWNvbGRmcm9udC5ocGMwHhcN +MjQwNzI1MTIyODI5WhcNMjUwNzI1MTIyODI5WjBYMQswCQYDVQQGEwJJVDELMAkG +A1UECAwCSVQxCzAJBgNVBAcMAkxaMRcwFQYDVQQKDA5jb2xkZnJvbnQgQWNtZTEW +MBQGA1UEAwwNY29sZGZyb250LmhwYzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAMWSIXEGMCLbGW9NKJr3ZgrWAqyxhpnCeCFB2ZC1+aiUL3Pf0LIE/cnC +2CGiCLRT377SwV46NEv7Od6hCyBXns/DHYBYYGFnc+HMUvXUADqt2AF3bSqC0U50 +X6Vv1eVca3H3dQRanUGSRS0epxZc+DkDr4hxxk77d7ovOQmGiuli6QT/L2bXbCUe +1F4r+5tRzh+qt2xt5mk0x8hFDc73ZWl/hyErs5X4o8dzpF9fJjSIn3twEnbAYTHf +4rtCDmOMt44H46k9eDnk+dOxHlYclxDQ7E2iG2iC7CaQ/XM+LwVrsJ4sjOnSxIws +1808n98Z8D3IzW72LQx4KVFPjbZk7OMCAwEAAaNTMFEwHQYDVR0OBBYEFIazsrqX +AcRDm+qHa9f1dyFSsKUnMB8GA1UdIwQYMBaAFIazsrqXAcRDm+qHa9f1dyFSsKUn +MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBABhJG9QVmBT/P3AV +8Vu8xTLzxaX0piiQemblqI21GHSaYxhhDG80N+7yR7AGt6ZovrSrQTBb8L0JMhJ7 +q54EqJNe0Ab9tshuDCpUATeV1RrP6WTE9PAvsO32ceTY4fnvHCLG4bVnKRz/g7Ul +0o1+i70HT34nVcGbmdhX0RnoMG7tL1ADkNU8vP9cfIMNBPAO0irB0N2yaTYNc/3Z +taGsjGGEM5WhYxWhGSc/TMneSB9+6rLGRrL65GMFDBKgREGqaI8zDP1gQrrz0v8e +VCKpbLdzLEOgltHqWYkrMqKLXwx5P086P+OPutjDvPwZgPSaEtNDyje1wp9p1Lvi +aU/ORl0= +-----END CERTIFICATE----- diff --git a/nginx/server.key b/nginx/server.key new file mode 100644 index 000000000..7f4007f94 --- /dev/null +++ b/nginx/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAxZIhcQYwItsZb00omvdmCtYCrLGGmcJ4IUHZkLX5qJQvc9/Q +sgT9ycLYIaIItFPfvtLBXjo0S/s53qELIFeez8MdgFhgYWdz4cxS9dQAOq3YAXdt +KoLRTnRfpW/V5Vxrcfd1BFqdQZJFLR6nFlz4OQOviHHGTvt3ui85CYaK6WLpBP8v +ZtdsJR7UXiv7m1HOH6q3bG3maTTHyEUNzvdlaX+HISuzlfijx3OkX18mNIife3AS +dsBhMd/iu0IOY4y3jgfjqT14OeT507EeVhyXENDsTaIbaILsJpD9cz4vBWuwniyM +6dLEjCzXzTyf3xnwPcjNbvYtDHgpUU+NtmTs4wIDAQABAoIBAQCJ6HDzZfBfxDRC +3scDNMHDupLvXJOp7HbSMBbfzkZQh+9/oLEaMiW8mgcnouUUip9Zod9cGKC8kMZa +QmtzzfWK7JVBK29LTl0zNd6KvcrTKtnmXCiVTe8wJkdFQYU6roJJcQP7YAz44lLr +JcRX9dlGYu45/cEBDMML8T6NCZ8ZPLmWZJmDVhH3LiVUCn33DHa1mtSsc+az8DrX +ncAl7r7+h7JuvGQBBpRI6CCWDfJYA01y1GVE8h/775yY+z3ArJLQu6hq/J+cnxSb +tzOiPYnaRHgLm02pbcvP1nzY5elr7mQkSo79HMObzi0gHD0KEHQoT+yrcVE2WF+r +PdvBs8aRAoGBAOHZdTRatUCeHe5BMOOogNON5Urk6Tz32BDVBghjP5nq91KuBmXd +GJMkwGFdHccq3sFMnxEbphW/oUWW1Qn0CNfGtrL+ctEdS5y3Rr8cHwVc9YrajCKw +sWyTFtLZN6gV1ROaFkMpGMQJYCSXlqS3G/LdhVpbXPJJ5GvX5ceq2Nb9AoGBAN/y +O9NnpL4YCCjoTMv9R1aJH0YN8ecfxtijwW82KHkfm1ly6tHtmT6PZwujyjdq3r43 +lLCVzzNnrm62rPoScyq/gJgOsukjkWYu9k0K0eKlWZrdhDU91lOR/Kn+wFmY1p8P +cXmrK/URvKJL5u8zgwT4pTJvCghL0ZDi9WcAy0lfAoGABj2vGnJDPfTgmNveUPww +CyiJpIcs1s12gAiS8RplAIjYqsU6UghJDI/gbRBYqU9K9oZ6gsuDzGgOOdEQNAU1 +VVkdZVbYr2Si2ULLRq6tQMWv11Vwt+iwDnMGF29/NTrdN3xzjwmMsKYdrQUvWX/h +2tU6QSPzLpLSKBTAOcND2LUCgYEAsL7x0KvgLdexuPd+hRDlGLsoX5Xp8cv2mEzG +AgvwbYgwh6xXAFBRvjpXzav7kTlbiy06wRO7cIrH9MFgTA37ryVtTJVtRuu3ebpe +0fdUJ1jxG3TxP0QKv/JcQ564GP6zhN4fZTNfgg/5nO3i2a8CHQ5Q8zkH5fxQ7dTy +cdUCnGkCgYAzPjV1aeBnsL1r6wJh1epj1mn2mqi44Xublat6o3Y49+MPdPlJVOEa +nw3N5ZJ8r3r1utuyjok+052/wxaJN4SgEdvZ/eq7uwd4IOCD/vtTN0IIAVQKsuY/ +8XCv2j92peszAR//FF51XDQqIxa5W2VDFhe+NsweWkKxOUUHvBv0rA== +-----END RSA PRIVATE KEY----- diff --git a/requirements.txt b/requirements.txt index efcf7fbbe..4bd0d5b8b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,6 +6,7 @@ certifi==2024.2.2 chardet==5.2.0 charset-normalizer==3.3.2 Django==4.2.11 +django-auth-ldap==4.8.0 django-crispy-forms==2.1 crispy-bootstrap4==2024.1 django-environ==0.11.2 @@ -23,8 +24,11 @@ Faker==24.1.0 fontawesome-free==5.15.4 FormEncode==2.1.0 future==1.0.0 +gunicorn humanize==4.9.0 idna==3.6 +ldap3==2.9.1 +mysqlclient==2.1.1 pyparsing==3.1.2 python-dateutil==2.9.0.post0 python-memcached==1.62 diff --git a/script-export-coldfront/script.sh b/script-export-coldfront/script.sh new file mode 100644 index 000000000..19d2628e5 --- /dev/null +++ b/script-export-coldfront/script.sh @@ -0,0 +1,498 @@ +#!/bin/bash + +#this script has to be launched via crontab. +#you need 3 files in the same directory: clusters, ldapserver, storage +#each file must contains: +#hostname ip ssh_user ssh_password + +# Variabili configurabili +quota_size="10000" # Sostituisci XXXX con il valore desiderato in mb +home_server_ip="XX.XX.XX.XX" #ricordati di copiare la chiave pubblica ssh su questo server + +# quota default per beegfs se non definita in fs/disk su coldfront +default_fs_disk=20000 # Quota predefinita in MB + +# Define the Docker container name +container_name="coldfront" + +# Define the output directory inside the container +output_dir="/opt/coldfront/coldfront_dump" + +# Run the coldfront slurm_dump command inside the Docker container +docker exec "${container_name}" coldfront slurm_dump -o "${output_dir}" + +# Check if the command was successful +if [[ $? -eq 0 ]]; then + echo "The slurm_dump command executed successfully." +else + echo "Error: The slurm_dump command failed." +fi + +# Ottieni la data e l'ora attuali nel formato desiderato (YYYYMMDD_HHMM) +current_time=$(date +'%Y%m%d_%H%M') +current_date=$(date +'%Y%m%d') + +# Percorso dove sono salvati i file .cfg +dump_dir="${output_dir}/coldfront_dump_${current_time}" + +# Funzione per eseguire SCP per copiare il file .cfg +copy_cfg_file() { + local ip=$1 + local username=$2 + local password=$3 + local cfg_file=$4 + + # Copia il file .cfg sul server remoto + sshpass -p "${password}" scp -o "StrictHostKeyChecking=no" "${cfg_file}" "${username}@${ip}:/tmp/" +} + +# Funzione per eseguire SSH e caricare il file .cfg con sacctmgr +load_cfg_with_sacctmgr() { + local ip=$1 + local username=$2 + local password=$3 + local cfg_file=$4 + + # Carica il file .cfg con sacctmgr + sshpass -p "${password}" ssh -o "StrictHostKeyChecking=no" "${username}@${ip}" "echo 'y' | sacctmgr load /tmp/$(basename ${cfg_file})" +} + +# Funzione per eseguire sacctmgr dump e copiare il file dump +dump_and_copy_file() { + local ip=$1 + local username=$2 + local password=$3 + local cluster=$4 + + # Nome del file di dump da creare + local dump_file="/tmp/${cluster}_dump_data_${current_date}.cfg" + + # Esegui sacctmgr dump sul cluster remoto + sshpass -p "${password}" ssh -o "StrictHostKeyChecking=no" "${username}@${ip}" "sacctmgr dump ${cluster} file=${dump_file}" + + # Copia il file dump dal server remoto al server locale + sshpass -p "${password}" scp -o "StrictHostKeyChecking=no" "${username}@${ip}:${dump_file}" "${dump_file}" +} + +# Funzione per estrarre solo i comandi sacctmgr dall'output di coldfront slurm_check +extract_sacctmgr_commands() { + local cluster=$1 + + coldfront slurm_check -i "/tmp/${cluster}_dump_data_${current_date}.cfg" -s -n 2>&1 | grep "NOOP - Slurm cmd: /usr/bin/" | sed 's/^NOOP - Slurm cmd: \/usr\/bin\///' | + while IFS= read -r line; do + echo "$line" + execute_sacctmgr_commands "$ip" "$username" "$password" "$line" + done +} + +# Funzione per eseguire i comandi sacctmgr su un cluster remoto +execute_sacctmgr_commands() { + local ip=$1 + local username=$2 + local password=$3 + shift 3 # Shift per rimuovere ip, username, password e ottenere solo i comandi + + echo "Esecuzione dei comandi sacctmgr su $ip" + + # Itera su ogni comando passato come argomento + for cmd in "$@"; do + echo "Singolo comando nel for: ${cmd}" + # Esegui il comando con sshpass e ssh + sshpass -p "${password}" ssh -o "StrictHostKeyChecking=no" "${username}@${ip}" "$cmd" + + # Controlla lo stato di uscita del comando + if [[ $? -eq 0 ]]; then + echo "Comando \"$cmd\" eseguito con successo su $ip" + else + echo "Errore durante l'esecuzione del comando \"$cmd\" su $ip" + # Puoi decidere di gestire eventuali errori qui + fi + done +} + +# Funzione per verificare se il percorso esiste su un server remoto +check_path_on_storage() { + local ip=$1 + local username=$2 + local password=$3 + local path=$4 + + sshpass -p "${password}" ssh -o "StrictHostKeyChecking=no" "${username}@${ip}" "[[ -d $path ]] && echo 'Path $path exists on $ip' || echo 'Path $path does not exist on $ip'" +} + +# Funzione per gestire le directory e i gruppi degli account +manage_account_directories_and_groups() { + local ip=$1 + local username=$2 + local password=$3 + local account=$4 + local users=$5 + + echo "Connettendosi a ${ip} con utente ${username} per gestire l'account ${account}..." + + # Sostituzione delle variabili esterne al comando SSH + local ssh_command=$(cat < /dev/null 2>&1; then + echo "Il gruppo ${account} non esiste. Creazione in corso..." + #groupadd "${account}" + echo "faccio group add ${account}; group commit" + cmsh -c "group add ${account}; group commit" + echo "Creato gruppo ${account}" + else + echo "Il gruppo ${account} esiste già." + fi + + echo "Verifica della ownership della cartella /mnt/beegfs/proj/${account}..." + ####if [[ \$(stat -c %G "/mnt/beegfs/proj/${account}") != "${account}" ]]; then + echo "Aggiornamento della ownership della cartella /mnt/beegfs/proj/${account} al gruppo ${account}..." + chown :${account} "/mnt/beegfs/proj/${account}" + chmod g+wrs,o-rwx "/mnt/beegfs/proj/${account}" + echo "Aggiornata ownership della cartella /mnt/beegfs/proj/${account} al gruppo ${account}" + ####fi + echo "**********************************************" + echo "Verifica per cancellazione degli utenti del gruppo ${account}..." + echo "**********************************************" + current_users=\$(getent group "${account}" | cut -d':' -f4 | sed "s/,/ /g") + if [[ -n "\${current_users}" ]]; then + echo "current_users = \${current_users}" + for user in \${current_users}; do + echo "utente ciclo =\$user utenti del progetto = ${users} utenti del gruppo linux = \${current_users}" + if ! echo "${users}" | grep -qw "\${user}"; then + echo "Rimozione dell'utente \${user} dal gruppo ${account}..." + # gpasswd -d "\${user}" "${account}" + echo "faccio group removefrom ${account} members \${user}; group commit" + cmsh -c "group removefrom ${account} members \${user}; group commit" + echo "Rimosso utente \${user} dal gruppo ${account}" + fi + done + fi + + echo "**********************************************" + echo "Aggiunta degli utenti ${users} al gruppo ${account}..." + echo "**********************************************" + utenti="${users}" + for user in ${users}; do + echo "ecco utente \${user} ... di ${users}" + if id -nG "\${user}" ; then + if ! id -nG "\${user}" | grep -qw "${account}"; then + echo "Aggiunta dell utente \${user} al gruppo ${account}..." + #usermod -aG "${account}" "\${user}" + echo "faccio group append ${account} members \${user}; group commit" + cmsh -c "group append ${account} members \${user}; group commit" + echo "Aggiunto utente \${user} al gruppo ${account}" + fi + fi + done + echo "...lancio il comando..." +EOF +) + +###echo "[command]\n ${ssh_command}" >> /tmp/scripts-command.log + + # Esecuzione del comando SSH con il comando costruito + sshpass -p "${password}" ssh -o "StrictHostKeyChecking=no" "${username}@${ip}" "${ssh_command}" +} + +# Funzione per controllare i file di configurazione e gestire directory e gruppi +check_clusters_and_cfg_files() { + for cfg_file in "${dump_dir}"/*.cfg; do + file_name=$(basename "${cfg_file}" .cfg) + if [[ -n ${hosts_map[$file_name]} ]]; then + echo "Contenuto del file di configurazione ${cfg_file}:" + cat "${cfg_file}" + + declare -A account_users_map + while IFS= read -r line; do + if [[ $line == Account* ]]; then + account=$(echo "$line" | grep -oP "(?<=Account - ')[^']+") + fs_disk=$(echo "$line" | grep -oP "(?<=fs/disk=)[^:]+") + account_users_map["$account"]="" + if [[ -n $fs_disk ]]; then + echo "Account: $account, Quota storage (fs/disk): $fs_disk" + else + echo "Account: $account, Quota storage (fs/disk): non definita" + fi + elif [[ $line == Parent* ]]; then + parent=$(echo "$line" | grep -oP "(?<=Parent - ')[^']+") + elif [[ $line == User* ]]; then + user=$(echo "$line" | grep -oP "(?<=User - ')[^']+") + if [[ -n ${account_users_map[$parent]} ]]; then + account_users_map["$parent"]="${account_users_map[$parent]} $user" + else + account_users_map["$parent"]="$user" + fi + fi + done < "${cfg_file}" + + for account in "${!account_users_map[@]}"; do + users=${account_users_map[$account]} + if [[ -n $users ]]; then + echo "Account: $account, Users: $users" + ####manage_account_directories_and_groups "${storage_map["ip"]}" "${storage_map["username"]}" "${storage_map["password"]}" "$account" "$users" + manage_account_directories_and_groups "${ldap_map["ip"]}" "${ldap_map["username"]}" "${ldap_map["password"]}" "$account" "$users" + fi + done + + else + echo "Nessuna corrispondenza trovata per $file_name nel file hosts" + fi + done +} + + +# Funzione per controllare i file di configurazione e gestire directory e gruppi +check_users_for_quotas() { + for cfg_file in "${dump_dir}"/*.cfg; do + file_name=$(basename "${cfg_file}" .cfg) + if [[ -n ${hosts_map[$file_name]} ]]; then + echo "Contenuto del file di configurazione ${cfg_file}:" + cat "${cfg_file}" + + declare -A account_users_map +#leggo il file intero + readarray -t linee < ${cfg_file} + for line in "${linee[@]}"; do + if [[ $line == Account* ]]; then + account=$(echo "$line" | grep -oP "(?<=Account - ')[^']+") + fs_disk=$(echo "$line" | grep -oP "(?<=fs/disk=)[^:]+") + account_users_map["$account"]="" + if [[ -n $fs_disk ]]; then + echo "Account: $account, Quota storage (fs/disk): $fs_disk" + set_beegfs_quota "${storage_map["ip"]}" "${storage_map["username"]}" "${storage_map["password"]}" "${account}" "${fs_disk}" + else + echo "Account: $account, Quota storage (fs/disk): non definita. Defaulting to ${default_fs_disk}MB" + set_beegfs_quota "${storage_map["ip"]}" "${storage_map["username"]}" "${storage_map["password"]}" "${account}" + fi + elif [[ $line == Parent* ]]; then + parent=$(echo "$line" | grep -oP "(?<=Parent - ')[^']+") + elif [[ $line == User* ]]; then + user=$(echo "$line" | grep -oP "(?<=User - ')[^']+") + if [[ -n ${account_users_map[$parent]} ]]; then + account_users_map["$parent"]="${account_users_map[$parent]} $user" + else + account_users_map["$parent"]="$user" + fi + fi + done + + + for account in "${!account_users_map[@]}"; do + users=${account_users_map[$account]} + for user in ${users}; do + user_id=$(get_user_id "${storage_map["ip"]}" "${storage_map["username"]}" "${storage_map["password"]}" "$account" "$user") + echo "Id for user ${user} is ${user_id}" + set_user_quota_on_remote_server "$home_server_ip" "$user_id" "$quota_size" + done + done + + else + echo "Nessuna corrispondenza trovata per $file_name nel file hosts" + fi + done +} + +read_ldap_info() { + while read -r ldap_name ldap_ip ldap_username ldap_password; do + ldap_map["ip"]=$ldap_ip + ldap_map["username"]=$ldap_username + ldap_map["password"]=$ldap_password + done < /opt/script-export-coldfront/ldapserver +} + + + +read_storage_info() { + while read -r storage_name storage_ip storage_username storage_password; do + storage_map["ip"]=$storage_ip + storage_map["username"]=$storage_username + storage_map["password"]=$storage_password + done < /opt/script-export-coldfront/storage +} + +# Funzione per recuperare l'ID utente su Linux +get_user_id() { + local ip=$1 + local username=$2 + local password=$3 + local account=$4 + local user=$5 + + + # Sostituzione delle variabili esterne al comando SSH + local ssh_command=$(cat <