Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve mutable default #538

Open
wants to merge 8 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## [3006.3.0] - 2023-10-22

- int: bugfix and deps update

- feat: i18n (#353)

[3006.3.0]: https://github.com/latenighttales/alcali/compare/v3006.3.0...HEAD

## [3003.1.0] - 2021-04-23

- int: updated deps (#317)
Expand All @@ -10,7 +18,7 @@

- int: offline version (#225)

[3003.1.0]: https://github.com/latenighttales/alcali/compare/v3003.1.0...HEAD
[3003.1.0]: https://github.com/latenighttales/alcali/compare/v3003.1.0...v3006.3.0

## [3000.1.0] - 2020-04-26

Expand All @@ -24,7 +32,7 @@

- fix: responsive layout (#178)

[3000.1.0]: https://github.com/latenighttales/alcali/compare/v2019.2.5...HEAD
[3000.1.0]: https://github.com/latenighttales/alcali/compare/v2019.2.5...v3003.1.0

## [2019.2.4] - 2020-02-14

Expand Down
302 changes: 296 additions & 6 deletions api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,23 +254,287 @@ class Meta:
def generate_key():
return binascii.hexlify(os.urandom(20)).decode()

def load_usersettings():
with open(
os.path.join(Path(__file__).parent.absolute(), "migrations/usersettings.json"),
"r",
) as fh:
return json.load(fh)

class UserSettings(models.Model):
"""
The default authorization token model.
"""
import binascii
import json
import os
from pathlib import Path

from django.contrib.auth.models import User
from django.db.models import Q

from django.db import models


class FindJobManager(models.Manager):
def get_queryset(self):
return super().get_queryset().exclude(fun="saltutil.find_job")


class Jids(models.Model):
jid = models.CharField(primary_key=True, db_index=True, max_length=255)
load = models.TextField()

def loaded_load(self):
return json.loads(self.load)

def user(self):
if "user" in self.loaded_load():
return self.loaded_load()["user"]
return ""

class Meta:
managed = False
db_table = "jids"
app_label = "api"


class SaltReturns(models.Model):
fun = models.CharField(max_length=50, db_index=True)
jid = models.CharField(max_length=255, db_index=True)
# Field renamed because it was a Python reserved word.
return_field = models.TextField(db_column="return")
id = models.CharField(max_length=255, primary_key=True)
success = models.CharField(max_length=10)
full_ret = models.TextField()
alter_time = models.DateTimeField()

objects = FindJobManager()

def loaded_ret(self):
return json.loads(self.full_ret)

def user(self):
# TODO: find a better way?
return Jids.objects.get(jid=self.jid).user()

def arguments(self):
ret = self.loaded_ret()
if "fun_args" in ret and ret["fun_args"]:
return " ".join(str(i) for i in ret["fun_args"] if "=" not in str(i))
return ""

def keyword_arguments(self):
ret = self.loaded_ret()
if "fun_args" in ret and ret["fun_args"]:
return " ".join(str(i) for i in ret["fun_args"] if "=" in str(i))
return ""

def success_bool(self):
ret = self.loaded_ret()
if "success" in ret:
return ret["success"]
if "return" in ret:
# It shouldn't happened unless you have a custom module
# so let's assume we can trust retcode
if isinstance(ret["return"], str) or isinstance(ret["return"], bool):
return True if "retcode" in ret and ret["retcode"] == 0 else False
if "success" in ret["return"]:
return ret["return"]["success"]
if "result" in ret["return"]:
return ret["return"]["result"]
return self.jid

class Meta:
managed = False
db_table = "salt_returns"
app_label = "api"


class SaltEvents(models.Model):
id = models.BigAutoField(primary_key=True)
tag = models.CharField(max_length=255, db_index=True)
data = models.TextField()
alter_time = models.DateTimeField()
master_id = models.CharField(max_length=255)

class Meta:
managed = False
db_table = "salt_events"
app_label = "api"


# Alcali custom.
class Functions(models.Model):
name = models.CharField(max_length=255)
type = models.CharField(max_length=255)
description = models.TextField()

def __str__(self):
return "{}".format(self.name)

class Meta:
db_table = "salt_functions"
app_label = "api"


class JobTemplate(models.Model):
name = models.CharField(max_length=255)
job = models.TextField()

def __str__(self):
return "{}".format(self.name)

class Meta:
db_table = "salt_job_template"
app_label = "api"


class Minions(models.Model):
minion_id = models.CharField(max_length=128, null=False, blank=False)
grain = models.TextField()
pillar = models.TextField()

def loaded_grain(self):
return json.loads(self.grain)

def loaded_pillar(self):
return json.loads(self.pillar)

def last_job(self):
return (
SaltReturns.objects.filter(id=self.minion_id)
.order_by("-alter_time")
.first()
)

def last_highstate(self):
# Get all potential jobs.
states = SaltReturns.objects.filter(
Q(fun="state.apply") | Q(fun="state.highstate"), id=self.minion_id
).order_by("-jid")[0:2]
states = sorted(states, key=lambda x: x.jid)

# Remove jobs with arguments.
for state in states:
if (
not state.loaded_ret()["fun_args"]
or state.loaded_ret()["fun_args"][0] == {"test": True}
or state.loaded_ret()["fun_args"][0] == "test=True"
):
return state
return None

def conformity(self):
last_highstate = self.last_highstate()
if not last_highstate:
return None
highstate_ret = last_highstate.loaded_ret()

# Flat out error(return is a string)
return_item = highstate_ret.get("return")
if not return_item or isinstance(return_item, list):
return False

for state in return_item:
# One of the state is not ok
if not return_item.get(state, {}).get("result"):
return False
return True

def custom_conformity(self, fun, *args):
# First, filter with fun.
jobs = SaltReturns.objects.filter(fun=fun, id=self.minion_id).order_by(
"-alter_time"
)
if not jobs:
return False
if args:
for job in jobs:
ret = job.loaded_ret()
# if provided args are the same.
if not list(
set(args) ^ {i for i in ret["fun_args"] if isinstance(i, str)}
):
return ret["return"]
# If no args or kwargs, just return the first job.
else:
job = jobs.first()
return job.loaded_ret()["return"]

def __str__(self):
return "{}".format(self.minion_id)

class Meta:
db_table = "salt_minions"
app_label = "api"


class Keys(models.Model):
KEY_STATUS = (
("accepted", "accepted"),
("rejected", "rejected"),
("denied", "denied"),
("unaccepted", "unaccepted"),
)
minion_id = models.CharField(max_length=255)
pub = models.TextField(blank=True)
status = models.CharField(max_length=64, choices=KEY_STATUS)

def __str__(self):
return "{}".format(self.minion_id)

class Meta:
# TODO add constraints (only one accepted per minion_id)
db_table = "salt_keys"
app_label = "api"


class MinionsCustomFields(models.Model):
name = models.CharField(max_length=255)
value = models.TextField()
minion = models.ForeignKey(
Minions, related_name="custom_fields", on_delete=models.CASCADE
)
function = models.CharField(max_length=255)

def __str__(self):
return "{}: {}".format(self.name, self.function)

class Meta:
db_table = "minions_custom_fields"
app_label = "api"


class Schedule(models.Model):
minion = models.CharField(max_length=128, null=False, blank=False)
name = models.CharField(max_length=255, blank=False, null=False)
job = models.TextField()

def loaded_job(self):
return json.loads(self.job)

class Meta:
app_label = "api"


def generate_key():
return binascii.hexlify(os.urandom(20)).decode()

def load_usersettings():
with open(
os.path.join(Path(__file__).parent.absolute(), "migrations/usersettings.json"),
"r",
) as fh:
data = json.load(fh)
return json.load(fh)

class UserSettings(models.Model):
"""
The default authorization token model.
"""

user = models.OneToOneField(
User, primary_key=True, related_name="user_settings", on_delete=models.CASCADE
)
token = models.CharField(max_length=40)
created = models.DateTimeField(auto_now_add=True)
settings = models.JSONField(default=data)
settings = models.JSONField(default=load_usersettings)
salt_permissions = models.TextField()

def generate_token(self):
Expand All @@ -290,6 +554,32 @@ def __str__(self):
return str(self.user)


class Conformity(models.Model):
name = models.CharField(max_length=255)
function = models.CharField(max_length=255)

class Meta:
db_table = "conformity"
app_label = "api"


def generate_token(self):
self.token = generate_key()
self.save()

class Meta:
db_table = "user_settings"
app_label = "api"

def save(self, *args, **kwargs):
if not self.token:
self.token = generate_key()
return super(UserSettings, self).save(*args, **kwargs)

def __str__(self):
return str(self.user)


class Conformity(models.Model):
name = models.CharField(max_length=255)
function = models.CharField(max_length=255)
Expand Down