move installed software to nats wh1te909/rmmagent@b5b5297350
This commit is contained in:
parent
f9edc9059a
commit
8935ce4ccf
|
@ -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"
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in New Issue