move installed software to nats wh1te909/rmmagent@b5b5297350

This commit is contained in:
wh1te909 2020-11-23 06:59:26 +00:00
parent f9edc9059a
commit 8935ce4ccf
4 changed files with 128 additions and 146 deletions

View File

@ -1,3 +1,4 @@
import asyncio
import string
from time import sleep
from loguru import logger
@ -89,35 +90,36 @@ def update_chocos():
@app.task
def get_installed_software(pk):
agent = Agent.objects.get(pk=pk)
r = agent.salt_api_cmd(
timeout=30,
func="pkg.list_pkgs",
kwargs={"include_components": False, "include_updates": False},
)
if not agent.has_nats:
logger.error(f"{agent.salt_id} software list only available in agent >= 1.1.0")
return
if r == "timeout" or r == "error":
logger.error(f"Timed out trying to get installed software on {agent.salt_id}")
r = asyncio.run(agent.nats_cmd({"func": "softwarelist"}, timeout=15))
if r == "timeout" or r == "natsdown":
logger.error(f"{agent.salt_id} {r}")
return
printable = set(string.printable)
try:
software = [
sw = []
for s in r:
sw.append(
{
"name": "".join(filter(lambda x: x in printable, k)),
"version": "".join(filter(lambda x: x in printable, v)),
"name": "".join(filter(lambda x: x in printable, s["name"])),
"version": "".join(filter(lambda x: x in printable, s["version"])),
"publisher": "".join(filter(lambda x: x in printable, s["publisher"])),
"install_date": s["install_date"],
"size": s["size"],
"source": s["source"],
"location": s["location"],
"uninstall": s["uninstall"],
}
for k, v in r.items()
]
except Exception as e:
logger.error(f"Unable to get installed software on {agent.salt_id}: {e}")
return
)
if not InstalledSoftware.objects.filter(agent=agent).exists():
InstalledSoftware(agent=agent, software=software).save()
InstalledSoftware(agent=agent, software=sw).save()
else:
s = agent.installedsoftware_set.get()
s.software = software
s = agent.installedsoftware_set.first()
s.software = sw
s.save(update_fields=["software"])
return "ok"

View File

@ -62,72 +62,6 @@ class TestSoftwareViews(TacticalTestCase):
self.check_not_authenticated("get", url)
@patch("agents.models.Agent.salt_api_cmd")
def test_chocos_refresh(self, salt_api_cmd):
salt_return = {"git": "2.3.4", "docker": "1.0.2"}
# test a call where agent doesn't exist
resp = self.client.get("/software/refresh/500/", format="json")
self.assertEqual(resp.status_code, 404)
agent = baker.make_recipe("agents.agent")
url = f"/software/refresh/{agent.pk}/"
# test failed attempt
salt_api_cmd.return_value = "timeout"
resp = self.client.get(url, format="json")
self.assertEqual(resp.status_code, 400)
salt_api_cmd.assert_called_with(
timeout=20,
func="pkg.list_pkgs",
kwargs={"include_components": False, "include_updates": False},
)
salt_api_cmd.reset_mock()
salt_api_cmd.return_value = "error"
resp = self.client.get(url, format="json")
self.assertEqual(resp.status_code, 400)
salt_api_cmd.assert_called_with(
timeout=20,
func="pkg.list_pkgs",
kwargs={"include_components": False, "include_updates": False},
)
salt_api_cmd.reset_mock()
# test success and created new software object
salt_api_cmd.return_value = salt_return
resp = self.client.get(url, format="json")
self.assertEqual(resp.status_code, 200)
salt_api_cmd.assert_called_with(
timeout=20,
func="pkg.list_pkgs",
kwargs={"include_components": False, "include_updates": False},
)
self.assertTrue(InstalledSoftware.objects.filter(agent=agent).exists())
salt_api_cmd.reset_mock()
# test success and updates software object
salt_api_cmd.return_value = salt_return
resp = self.client.get(url, format="json")
self.assertEqual(resp.status_code, 200)
salt_api_cmd.assert_called_with(
timeout=20,
func="pkg.list_pkgs",
kwargs={"include_components": False, "include_updates": False},
)
software = agent.installedsoftware_set.get()
expected = [
{"name": "git", "version": "2.3.4"},
{"name": "docker", "version": "1.0.2"},
]
self.assertTrue(InstalledSoftware.objects.filter(agent=agent).exists())
self.assertEquals(software.software, expected)
self.check_not_authenticated("get", url)
class TestSoftwareTasks(TacticalTestCase):
@patch("agents.models.Agent.salt_api_cmd")
@ -186,43 +120,57 @@ class TestSoftwareTasks(TacticalTestCase):
salt_api_cmd.assert_any_call(timeout=200, func="chocolatey.list")
self.assertEquals(salt_api_cmd.call_count, 2)
@patch("agents.models.Agent.salt_api_cmd")
def test_get_installed_software(self, salt_api_cmd):
@patch("agents.models.Agent.nats_cmd")
def test_get_installed_software(self, nats_cmd):
from .tasks import get_installed_software
agent = baker.make_recipe("agents.agent")
salt_return = {"git": "2.3.4", "docker": "1.0.2"}
# test failed attempt
salt_api_cmd.return_value = "timeout"
ret = get_installed_software(agent.pk)
self.assertFalse(ret)
salt_api_cmd.assert_called_with(
timeout=30,
func="pkg.list_pkgs",
kwargs={"include_components": False, "include_updates": False},
)
salt_api_cmd.reset_mock()
# test successful attempt
salt_api_cmd.return_value = salt_return
ret = get_installed_software(agent.pk)
self.assertTrue(ret)
salt_api_cmd.assert_called_with(
timeout=30,
func="pkg.list_pkgs",
kwargs={"include_components": False, "include_updates": False},
)
software = agent.installedsoftware_set.get()
expected = [
{"name": "git", "version": "2.3.4"},
{"name": "docker", "version": "1.0.2"},
nats_return = [
{
"name": "Mozilla Maintenance Service",
"size": "336.9 kB",
"source": "",
"version": "73.0.1",
"location": "",
"publisher": "Mozilla",
"uninstall": '"C:\\Program Files (x86)\\Mozilla Maintenance Service\\uninstall.exe"',
"install_date": "0001-01-01 00:00:00 +0000 UTC",
},
{
"name": "OpenVPN 2.4.9-I601-Win10 ",
"size": "8.7 MB",
"source": "",
"version": "2.4.9-I601-Win10",
"location": "C:\\Program Files\\OpenVPN\\",
"publisher": "OpenVPN Technologies, Inc.",
"uninstall": "C:\\Program Files\\OpenVPN\\Uninstall.exe",
"install_date": "0001-01-01 00:00:00 +0000 UTC",
},
{
"name": "Microsoft Office Professional Plus 2019 - en-us",
"size": "0 B",
"source": "",
"version": "16.0.10368.20035",
"location": "C:\\Program Files\\Microsoft Office",
"publisher": "Microsoft Corporation",
"uninstall": '"C:\\Program Files\\Common Files\\Microsoft Shared\\ClickToRun\\OfficeClickToRun.exe" scenario=install scenariosubtype=ARP sourcetype=None productstoremove=ProPlus2019Volume.16_en-us_x-none culture=en-us version.16=16.0',
"install_date": "0001-01-01 00:00:00 +0000 UTC",
},
]
self.assertTrue(InstalledSoftware.objects.filter(agent=agent).exists())
self.assertEquals(software.software, expected)
# test failed attempt
nats_cmd.return_value = "timeout"
ret = get_installed_software(agent.pk)
self.assertFalse(ret)
nats_cmd.assert_called_with({"func": "softwarelist"}, timeout=15)
nats_cmd.reset_mock()
# test successful attempt
nats_cmd.return_value = nats_return
ret = get_installed_software(agent.pk)
self.assertTrue(ret)
nats_cmd.assert_called_with({"func": "softwarelist"}, timeout=15)
@patch("agents.models.Agent.salt_api_cmd")
@patch("software.tasks.get_installed_software.delay")

View File

@ -1,3 +1,4 @@
import asyncio
import string
from django.shortcuts import get_object_or_404
@ -41,35 +42,34 @@ def get_installed(request, pk):
@api_view()
def refresh_installed(request, pk):
agent = get_object_or_404(Agent, pk=pk)
r = agent.salt_api_cmd(
timeout=20,
func="pkg.list_pkgs",
kwargs={"include_components": False, "include_updates": False},
)
if not agent.has_nats:
return notify_error("Requires agent version 1.1.0 or greater")
if r == "timeout":
r = asyncio.run(agent.nats_cmd({"func": "softwarelist"}, timeout=15))
if r == "timeout" or r == "natsdown":
return notify_error("Unable to contact the agent")
elif r == "error":
return notify_error("Something went wrong")
printable = set(string.printable)
try:
software = [
sw = []
for s in r:
sw.append(
{
"name": "".join(filter(lambda x: x in printable, k)),
"version": "".join(filter(lambda x: x in printable, v)),
"name": "".join(filter(lambda x: x in printable, s["name"])),
"version": "".join(filter(lambda x: x in printable, s["version"])),
"publisher": "".join(filter(lambda x: x in printable, s["publisher"])),
"install_date": s["install_date"],
"size": s["size"],
"source": s["source"],
"location": s["location"],
"uninstall": s["uninstall"],
}
for k, v in r.items()
]
except Exception:
return notify_error("Something went wrong")
)
if not InstalledSoftware.objects.filter(agent=agent).exists():
InstalledSoftware(agent=agent, software=software).save()
InstalledSoftware(agent=agent, software=sw).save()
else:
s = agent.installedsoftware_set.get()
s.software = software
s = agent.installedsoftware_set.first()
s.software = sw
s.save(update_fields=["software"])
return Response("ok")

View File

@ -2,15 +2,24 @@
<div v-if="!this.selectedAgentPk">No agent selected</div>
<div v-else-if="!Array.isArray(software) || !software.length">No software</div>
<div v-else>
<q-btn
size="sm"
color="grey-5"
icon="fas fa-plus"
label="Install Software"
text-color="black"
@click="showInstallSoftware = true"
/>
<q-btn dense flat push @click="refreshSoftware" icon="refresh" />
<div class="row q-pt-xs items-start">
<q-btn
size="xs"
color="grey-5"
icon="fas fa-plus"
label="Install Software"
text-color="black"
@click="showInstallSoftware = true"
/>
<q-btn dense flat push @click="refreshSoftware" icon="refresh" />
<q-space />
<q-input v-model="filter" outlined label="Search" dense clearable>
<template v-slot:prepend>
<q-icon name="search" color="primary" />
</template>
</q-input>
</div>
<q-table
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
class="tabs-tbl-sticky"
@ -18,6 +27,7 @@
dense
:data="software"
:columns="columns"
:filter="filter"
:pagination.sync="pagination"
binary-state-sort
hide-bottom
@ -51,6 +61,7 @@ export default {
return {
showInstallSoftware: false,
loading: false,
filter: "",
pagination: {
rowsPerPage: 0,
sortBy: "name",
@ -64,6 +75,27 @@ export default {
field: "name",
sortable: true,
},
{
name: "publisher",
align: "left",
label: "Publisher",
field: "publisher",
sortable: true,
},
{
name: "install_date",
align: "left",
label: "Installed On",
field: "install_date",
sortable: false,
},
{
name: "size",
align: "left",
label: "Size",
field: "size",
sortable: false,
},
{
name: "version",
align: "left",