Merge branch 'staging' of github-DDS:dod-ccpo/atst into gi-updates-wo-20191209
This commit is contained in:
commit
2a77dbe063
@ -3,7 +3,7 @@
|
||||
"files": "^.secrets.baseline$|^.*pgsslrootcert.yml$",
|
||||
"lines": null
|
||||
},
|
||||
"generated_at": "2019-12-05T17:54:05Z",
|
||||
"generated_at": "2019-12-06T21:22:07Z",
|
||||
"plugins_used": [
|
||||
{
|
||||
"base64_limit": 4.5,
|
||||
@ -161,7 +161,7 @@
|
||||
"hashed_secret": "e4f14805dfd1e6af030359090c535e149e6b4207",
|
||||
"is_secret": false,
|
||||
"is_verified": false,
|
||||
"line_number": 31,
|
||||
"line_number": 41,
|
||||
"type": "Hex High Entropy String"
|
||||
}
|
||||
],
|
||||
|
1
Pipfile
1
Pipfile
@ -29,6 +29,7 @@ azure-mgmt-subscription = "*"
|
||||
azure-graphrbac = "*"
|
||||
msrestazure = "*"
|
||||
azure-mgmt-authorization = "*"
|
||||
azure-mgmt-managementgroups = "*"
|
||||
|
||||
[dev-packages]
|
||||
bandit = "*"
|
||||
|
177
Pipfile.lock
generated
177
Pipfile.lock
generated
@ -1,7 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "6d2ab855267daac877ae7464de9dba5b62b7d89288992f87d8fc6ff0c0d2520f"
|
||||
"sha256": "c2b19c436646705ea3bf4df8c35c2833083f048da37fc619e66f7236153607c5"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
@ -39,11 +39,11 @@
|
||||
},
|
||||
"apache-libcloud": {
|
||||
"hashes": [
|
||||
"sha256:201751f738109f25d58dcdfb5804e17216e0dc8f68b522e9e26ac16e0b9ff2ea",
|
||||
"sha256:40215db1bd489d17dc1abfdb289d7f035313c7297b6a7462c79d8287cbbeae91"
|
||||
"sha256:9bc5cd5c32151bb7a04a7c7de0be9b4a4b8271e348ac91dd79eaaeeae627115f",
|
||||
"sha256:fcc165f2cc2db9a379c6d3a17b3beb9081bb64ba5c0bf7bbb58da864810092f0"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.6.0"
|
||||
"version": "==2.6.1"
|
||||
},
|
||||
"azure-common": {
|
||||
"hashes": [
|
||||
@ -68,6 +68,14 @@
|
||||
"index": "pypi",
|
||||
"version": "==0.60.0"
|
||||
},
|
||||
"azure-mgmt-managementgroups": {
|
||||
"hashes": [
|
||||
"sha256:3d5237947458dc94b4a392141174b1c1258d26611241ee104e9006d1d798f682",
|
||||
"sha256:8194ee6274df865eccd1ed9d385ea625aeba9b8058b9e4fdf547f5207271a775"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.2.0"
|
||||
},
|
||||
"azure-mgmt-subscription": {
|
||||
"hashes": [
|
||||
"sha256:504b4c42ba859070c3c50637ec07ca36aca600e613fcccaa398db22822fe21f1",
|
||||
@ -117,10 +125,10 @@
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50",
|
||||
"sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef"
|
||||
"sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3",
|
||||
"sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"
|
||||
],
|
||||
"version": "==2019.9.11"
|
||||
"version": "==2019.11.28"
|
||||
},
|
||||
"cffi": {
|
||||
"hashes": [
|
||||
@ -248,10 +256,10 @@
|
||||
},
|
||||
"importlib-metadata": {
|
||||
"hashes": [
|
||||
"sha256:aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26",
|
||||
"sha256:d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af"
|
||||
"sha256:b044f07694ef14a6683b097ba56bd081dbc7cdc7c7fe46011e499dfecc082f21",
|
||||
"sha256:e6ac600a142cf2db707b1998382cc7fc3b02befb7273876e01b8ad10b9652742"
|
||||
],
|
||||
"version": "==0.23"
|
||||
"version": "==1.1.0"
|
||||
},
|
||||
"isodate": {
|
||||
"hashes": [
|
||||
@ -330,10 +338,10 @@
|
||||
},
|
||||
"more-itertools": {
|
||||
"hashes": [
|
||||
"sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832",
|
||||
"sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4"
|
||||
"sha256:53ff73f186307d9c8ef17a9600309154a6ae27f25579e80af4db8f047ba14bc2",
|
||||
"sha256:a0ea684c39bc4315ba7aae406596ef191fd84f873d2d2751f84d64e81a7a2d45"
|
||||
],
|
||||
"version": "==7.2.0"
|
||||
"version": "==8.0.0"
|
||||
},
|
||||
"msrest": {
|
||||
"hashes": [
|
||||
@ -422,11 +430,11 @@
|
||||
},
|
||||
"pyopenssl": {
|
||||
"hashes": [
|
||||
"sha256:aeca66338f6de19d1aa46ed634c3b9ae519a64b458f8468aec688e7e3c20f200",
|
||||
"sha256:c727930ad54b10fc157015014b666f2d8b41f70c0d03e83ab67624fd3dd5d1e6"
|
||||
"sha256:621880965a720b8ece2f1b2f54ea2071966ab00e2970ad2ce11d596102063504",
|
||||
"sha256:9a24494b2602aaf402be5c9e30a0b82d4a5c67528fe8fb475e3f3bc00dd69507"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==19.0.0"
|
||||
"version": "==19.1.0"
|
||||
},
|
||||
"python-dateutil": {
|
||||
"hashes": [
|
||||
@ -459,22 +467,20 @@
|
||||
},
|
||||
"pyyaml": {
|
||||
"hashes": [
|
||||
"sha256:0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9",
|
||||
"sha256:01adf0b6c6f61bd11af6e10ca52b7d4057dd0be0343eb9283c878cf3af56aee4",
|
||||
"sha256:5124373960b0b3f4aa7df1707e63e9f109b5263eca5976c66e08b1c552d4eaf8",
|
||||
"sha256:5ca4f10adbddae56d824b2c09668e91219bb178a1eee1faa56af6f99f11bf696",
|
||||
"sha256:7907be34ffa3c5a32b60b95f4d95ea25361c951383a894fec31be7252b2b6f34",
|
||||
"sha256:7ec9b2a4ed5cad025c2278a1e6a19c011c80a3caaac804fd2d329e9cc2c287c9",
|
||||
"sha256:87ae4c829bb25b9fe99cf71fbb2140c448f534e24c998cc60f39ae4f94396a73",
|
||||
"sha256:9de9919becc9cc2ff03637872a440195ac4241c80536632fffeb6a1e25a74299",
|
||||
"sha256:a5a85b10e450c66b49f98846937e8cfca1db3127a9d5d1e31ca45c3d0bef4c5b",
|
||||
"sha256:b0997827b4f6a7c286c01c5f60384d218dca4ed7d9efa945c3e1aa623d5709ae",
|
||||
"sha256:b631ef96d3222e62861443cc89d6563ba3eeb816eeb96b2629345ab795e53681",
|
||||
"sha256:bf47c0607522fdbca6c9e817a6e81b08491de50f3766a7a0e6a5be7905961b41",
|
||||
"sha256:f81025eddd0327c7d4cfe9b62cf33190e1e736cc6e97502b3ec425f574b3e7a8"
|
||||
"sha256:0e7f69397d53155e55d10ff68fdfb2cf630a35e6daf65cf0bdeaf04f127c09dc",
|
||||
"sha256:2e9f0b7c5914367b0916c3c104a024bb68f269a486b9d04a2e8ac6f6597b7803",
|
||||
"sha256:35ace9b4147848cafac3db142795ee42deebe9d0dad885ce643928e88daebdcc",
|
||||
"sha256:38a4f0d114101c58c0f3a88aeaa44d63efd588845c5a2df5290b73db8f246d15",
|
||||
"sha256:483eb6a33b671408c8529106df3707270bfacb2447bf8ad856a4b4f57f6e3075",
|
||||
"sha256:4b6be5edb9f6bb73680f5bf4ee08ff25416d1400fbd4535fe0069b2994da07cd",
|
||||
"sha256:7f38e35c00e160db592091751d385cd7b3046d6d51f578b29943225178257b31",
|
||||
"sha256:8100c896ecb361794d8bfdb9c11fce618c7cf83d624d73d5ab38aef3bc82d43f",
|
||||
"sha256:c0ee8eca2c582d29c3c2ec6e2c4f703d1b7f1fb10bc72317355a746057e7346c",
|
||||
"sha256:e4c015484ff0ff197564917b4b4246ca03f411b9bd7f16e02a2f586eb48b6d04",
|
||||
"sha256:ebc4ed52dcc93eeebeae5cf5deb2ae4347b3a81c3fa12b0b8c976544829396a4"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==5.1.2"
|
||||
"version": "==5.2"
|
||||
},
|
||||
"redis": {
|
||||
"hashes": [
|
||||
@ -650,10 +656,10 @@
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50",
|
||||
"sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef"
|
||||
"sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3",
|
||||
"sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"
|
||||
],
|
||||
"version": "==2019.9.11"
|
||||
"version": "==2019.11.28"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
@ -785,10 +791,10 @@
|
||||
},
|
||||
"importlib-metadata": {
|
||||
"hashes": [
|
||||
"sha256:aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26",
|
||||
"sha256:d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af"
|
||||
"sha256:b044f07694ef14a6683b097ba56bd081dbc7cdc7c7fe46011e499dfecc082f21",
|
||||
"sha256:e6ac600a142cf2db707b1998382cc7fc3b02befb7273876e01b8ad10b9652742"
|
||||
],
|
||||
"version": "==0.23"
|
||||
"version": "==1.1.0"
|
||||
},
|
||||
"ipdb": {
|
||||
"hashes": [
|
||||
@ -799,11 +805,11 @@
|
||||
},
|
||||
"ipython": {
|
||||
"hashes": [
|
||||
"sha256:dfd303b270b7b5232b3d08bd30ec6fd685d8a58cabd54055e3d69d8f029f7280",
|
||||
"sha256:ed7ebe1cba899c1c3ccad6f7f1c2d2369464cc77dba8eebc65e2043e19cda995"
|
||||
"sha256:c66c7e27239855828a764b1e8fc72c24a6f4498a2637572094a78c5551fb9d51",
|
||||
"sha256:f186b01b36609e0c5d0de27c7ef8e80c990c70478f8c880863004b3489a9030e"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==7.9.0"
|
||||
"version": "==7.10.1"
|
||||
},
|
||||
"ipython-genutils": {
|
||||
"hashes": [
|
||||
@ -908,30 +914,30 @@
|
||||
},
|
||||
"more-itertools": {
|
||||
"hashes": [
|
||||
"sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832",
|
||||
"sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4"
|
||||
"sha256:53ff73f186307d9c8ef17a9600309154a6ae27f25579e80af4db8f047ba14bc2",
|
||||
"sha256:a0ea684c39bc4315ba7aae406596ef191fd84f873d2d2751f84d64e81a7a2d45"
|
||||
],
|
||||
"version": "==7.2.0"
|
||||
"version": "==8.0.0"
|
||||
},
|
||||
"mypy": {
|
||||
"hashes": [
|
||||
"sha256:1521c186a3d200c399bd5573c828ea2db1362af7209b2adb1bb8532cea2fb36f",
|
||||
"sha256:31a046ab040a84a0fc38bc93694876398e62bc9f35eca8ccbf6418b7297f4c00",
|
||||
"sha256:3b1a411909c84b2ae9b8283b58b48541654b918e8513c20a400bb946aa9111ae",
|
||||
"sha256:48c8bc99380575deb39f5d3400ebb6a8a1cb5cc669bbba4d3bb30f904e0a0e7d",
|
||||
"sha256:540c9caa57a22d0d5d3c69047cc9dd0094d49782603eb03069821b41f9e970e9",
|
||||
"sha256:672e418425d957e276c291930a3921b4a6413204f53fe7c37cad7bc57b9a3391",
|
||||
"sha256:6ed3b9b3fdc7193ea7aca6f3c20549b377a56f28769783a8f27191903a54170f",
|
||||
"sha256:9371290aa2cad5ad133e4cdc43892778efd13293406f7340b9ffe99d5ec7c1d9",
|
||||
"sha256:ace6ac1d0f87d4072f05b5468a084a45b4eda970e4d26704f201e06d47ab2990",
|
||||
"sha256:b428f883d2b3fe1d052c630642cc6afddd07d5cd7873da948644508be3b9d4a7",
|
||||
"sha256:d5bf0e6ec8ba346a2cf35cb55bf4adfddbc6b6576fcc9e10863daa523e418dbb",
|
||||
"sha256:d7574e283f83c08501607586b3167728c58e8442947e027d2d4c7dcd6d82f453",
|
||||
"sha256:dc889c84241a857c263a2b1cd1121507db7d5b5f5e87e77147097230f374d10b",
|
||||
"sha256:f4748697b349f373002656bf32fede706a0e713d67bfdcf04edf39b1f61d46eb"
|
||||
"sha256:02d9bdd3398b636723ecb6c5cfe9773025a9ab7f34612c1cde5c7f2292e2d768",
|
||||
"sha256:088f758a50af31cf8b42688118077292370c90c89232c783ba7979f39ea16646",
|
||||
"sha256:28e9fbc96d13397a7ddb7fad7b14f373f91b5cff538e0772e77c270468df083c",
|
||||
"sha256:30e123b24931f02c5d99307406658ac8f9cd6746f0d45a3dcac2fe5fbdd60939",
|
||||
"sha256:3294821b5840d51a3cd7a2bb63b40fc3f901f6a3cfb3c6046570749c4c7ef279",
|
||||
"sha256:41696a7d912ce16fdc7c141d87e8db5144d4be664a0c699a2b417d393994b0c2",
|
||||
"sha256:4f42675fa278f3913340bb8c3371d191319704437758d7c4a8440346c293ecb2",
|
||||
"sha256:54d205ccce6ed930a8a2ccf48404896d456e8b87812e491cb907a355b1a9c640",
|
||||
"sha256:6992133c95a2847d309b4b0c899d7054adc60481df6f6b52bb7dee3d5fd157f7",
|
||||
"sha256:6ecbd0e8e371333027abca0922b0c2c632a5b4739a0c61ffbd0733391e39144c",
|
||||
"sha256:83fa87f556e60782c0fc3df1b37b7b4a840314ba1ac27f3e1a1e10cb37c89c17",
|
||||
"sha256:c87ac7233c629f305602f563db07f5221950fe34fe30af072ac838fa85395f78",
|
||||
"sha256:de9ec8dba773b78c49e7bec9a35c9b6fc5235682ad1fc2105752ae7c22f4b931",
|
||||
"sha256:f385a0accf353ca1bca4bbf473b9d83ed18d923fdb809d3a70a385da23e25b6a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.740"
|
||||
"version": "==0.750"
|
||||
},
|
||||
"mypy-extensions": {
|
||||
"hashes": [
|
||||
@ -961,10 +967,10 @@
|
||||
},
|
||||
"pbr": {
|
||||
"hashes": [
|
||||
"sha256:2c8e420cd4ed4cec4e7999ee47409e876af575d4c35a45840d59e8b5f3155ab8",
|
||||
"sha256:b32c8ccaac7b1a20c0ce00ce317642e6cf231cf038f9875e0280e28af5bf7ac9"
|
||||
"sha256:139d2625547dbfa5fb0b81daebb39601c478c21956dc57e2e07b74450a8c506b",
|
||||
"sha256:61aa52a0f18b71c5cc58232d2cf8f8d09cd67fcad60b742a60124cb8d6951488"
|
||||
],
|
||||
"version": "==5.4.3"
|
||||
"version": "==5.4.4"
|
||||
},
|
||||
"pexpect": {
|
||||
"hashes": [
|
||||
@ -983,18 +989,17 @@
|
||||
},
|
||||
"pluggy": {
|
||||
"hashes": [
|
||||
"sha256:0db4b7601aae1d35b4a033282da476845aa19185c1e6964b25cf324b5e4ec3e6",
|
||||
"sha256:fa5fa1622fa6dd5c030e9cad086fa19ef6a0cf6d7a2d12318e10cb49d6d68f34"
|
||||
"sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
|
||||
"sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"
|
||||
],
|
||||
"version": "==0.13.0"
|
||||
"version": "==0.13.1"
|
||||
},
|
||||
"prompt-toolkit": {
|
||||
"hashes": [
|
||||
"sha256:46642344ce457641f28fc9d1c9ca939b63dadf8df128b86f1b9860e59c73a5e4",
|
||||
"sha256:e7f8af9e3d70f514373bf41aa51bc33af12a6db3f71461ea47fea985defb2c31",
|
||||
"sha256:f15af68f66e664eaa559d4ac8a928111eebd5feda0c11738b5998045224829db"
|
||||
"sha256:0278d2f51b5ceba6ea8da39f76d15684e84c996b325475f6e5720edc584326a7",
|
||||
"sha256:63daee79aa8366c8f1c637f1a4876b890da5fc92a19ebd2f7080ebacb901e990"
|
||||
],
|
||||
"version": "==2.0.10"
|
||||
"version": "==3.0.2"
|
||||
},
|
||||
"ptyprocess": {
|
||||
"hashes": [
|
||||
@ -1012,10 +1017,10 @@
|
||||
},
|
||||
"pygments": {
|
||||
"hashes": [
|
||||
"sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127",
|
||||
"sha256:881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297"
|
||||
"sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b",
|
||||
"sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe"
|
||||
],
|
||||
"version": "==2.4.2"
|
||||
"version": "==2.5.2"
|
||||
},
|
||||
"pylint": {
|
||||
"hashes": [
|
||||
@ -1058,11 +1063,11 @@
|
||||
},
|
||||
"pytest-mock": {
|
||||
"hashes": [
|
||||
"sha256:b3514caac35fe3f05555923eabd9546abce11571cc2ddf7d8615959d04f2c89e",
|
||||
"sha256:ea502c3891599c26243a3a847ccf0b1d20556678c528f86c98e3cd6d40c5cf11"
|
||||
"sha256:96a0cebc66e09930be2a15b03333d90b59584d3fb011924f81c14b50ee0afbba",
|
||||
"sha256:e5381be2608e49547f5e47633c5f81241ebf6206d17ce516a7a18d5a917e3859"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.11.2"
|
||||
"version": "==1.12.1"
|
||||
},
|
||||
"pytest-watch": {
|
||||
"hashes": [
|
||||
@ -1080,22 +1085,20 @@
|
||||
},
|
||||
"pyyaml": {
|
||||
"hashes": [
|
||||
"sha256:0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9",
|
||||
"sha256:01adf0b6c6f61bd11af6e10ca52b7d4057dd0be0343eb9283c878cf3af56aee4",
|
||||
"sha256:5124373960b0b3f4aa7df1707e63e9f109b5263eca5976c66e08b1c552d4eaf8",
|
||||
"sha256:5ca4f10adbddae56d824b2c09668e91219bb178a1eee1faa56af6f99f11bf696",
|
||||
"sha256:7907be34ffa3c5a32b60b95f4d95ea25361c951383a894fec31be7252b2b6f34",
|
||||
"sha256:7ec9b2a4ed5cad025c2278a1e6a19c011c80a3caaac804fd2d329e9cc2c287c9",
|
||||
"sha256:87ae4c829bb25b9fe99cf71fbb2140c448f534e24c998cc60f39ae4f94396a73",
|
||||
"sha256:9de9919becc9cc2ff03637872a440195ac4241c80536632fffeb6a1e25a74299",
|
||||
"sha256:a5a85b10e450c66b49f98846937e8cfca1db3127a9d5d1e31ca45c3d0bef4c5b",
|
||||
"sha256:b0997827b4f6a7c286c01c5f60384d218dca4ed7d9efa945c3e1aa623d5709ae",
|
||||
"sha256:b631ef96d3222e62861443cc89d6563ba3eeb816eeb96b2629345ab795e53681",
|
||||
"sha256:bf47c0607522fdbca6c9e817a6e81b08491de50f3766a7a0e6a5be7905961b41",
|
||||
"sha256:f81025eddd0327c7d4cfe9b62cf33190e1e736cc6e97502b3ec425f574b3e7a8"
|
||||
"sha256:0e7f69397d53155e55d10ff68fdfb2cf630a35e6daf65cf0bdeaf04f127c09dc",
|
||||
"sha256:2e9f0b7c5914367b0916c3c104a024bb68f269a486b9d04a2e8ac6f6597b7803",
|
||||
"sha256:35ace9b4147848cafac3db142795ee42deebe9d0dad885ce643928e88daebdcc",
|
||||
"sha256:38a4f0d114101c58c0f3a88aeaa44d63efd588845c5a2df5290b73db8f246d15",
|
||||
"sha256:483eb6a33b671408c8529106df3707270bfacb2447bf8ad856a4b4f57f6e3075",
|
||||
"sha256:4b6be5edb9f6bb73680f5bf4ee08ff25416d1400fbd4535fe0069b2994da07cd",
|
||||
"sha256:7f38e35c00e160db592091751d385cd7b3046d6d51f578b29943225178257b31",
|
||||
"sha256:8100c896ecb361794d8bfdb9c11fce618c7cf83d624d73d5ab38aef3bc82d43f",
|
||||
"sha256:c0ee8eca2c582d29c3c2ec6e2c4f703d1b7f1fb10bc72317355a746057e7346c",
|
||||
"sha256:e4c015484ff0ff197564917b4b4246ca03f411b9bd7f16e02a2f586eb48b6d04",
|
||||
"sha256:ebc4ed52dcc93eeebeae5cf5deb2ae4347b3a81c3fa12b0b8c976544829396a4"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==5.1.2"
|
||||
"version": "==5.2"
|
||||
},
|
||||
"regex": {
|
||||
"hashes": [
|
||||
|
@ -3,6 +3,7 @@ import re
|
||||
from uuid import uuid4
|
||||
|
||||
from atst.models.user import User
|
||||
from atst.models.application import Application
|
||||
from atst.models.environment import Environment
|
||||
from atst.models.environment_role import EnvironmentRole
|
||||
|
||||
@ -399,13 +400,14 @@ REMOTE_ROOT_ROLE_DEF_ID = "/providers/Microsoft.Authorization/roleDefinitions/00
|
||||
|
||||
class AzureSDKProvider(object):
|
||||
def __init__(self):
|
||||
from azure.mgmt import subscription, authorization
|
||||
from azure.mgmt import subscription, authorization, managementgroups
|
||||
import azure.graphrbac as graphrbac
|
||||
import azure.common.credentials as credentials
|
||||
from msrestazure.azure_cloud import AZURE_PUBLIC_CLOUD
|
||||
|
||||
self.subscription = subscription
|
||||
self.authorization = authorization
|
||||
self.managementgroups = managementgroups
|
||||
self.graphrbac = graphrbac
|
||||
self.credentials = credentials
|
||||
# may change to a JEDI cloud
|
||||
@ -428,42 +430,23 @@ class AzureCloudProvider(CloudProviderInterface):
|
||||
def create_environment(
|
||||
self, auth_credentials: Dict, user: User, environment: Environment
|
||||
):
|
||||
# since this operation would only occur within a tenant, should we source the tenant
|
||||
# via lookup from environment once we've created the portfolio csp data schema
|
||||
# something like this:
|
||||
# environment_tenant = environment.application.portfolio.csp_data.get('tenant_id', None)
|
||||
# though we'd probably source the whole credentials for these calls from the portfolio csp
|
||||
# data, as it would have to be where we store the creds for the at-at user within the portfolio tenant
|
||||
# credentials = self._get_credential_obj(environment.application.portfolio.csp_data.get_creds())
|
||||
credentials = self._get_credential_obj(self._root_creds)
|
||||
sub_client = self.sdk.subscription.SubscriptionClient(credentials)
|
||||
|
||||
display_name = f"{environment.application.name}_{environment.name}_{environment.id}" # proposed format
|
||||
management_group_id = "?" # management group id chained from environment
|
||||
parent_id = "?" # from environment.application
|
||||
|
||||
billing_profile_id = "?" # something chained from environment?
|
||||
sku_id = AZURE_SKU_ID
|
||||
# we want to set AT-AT as an owner here
|
||||
# we could potentially associate subscriptions with "management groups" per DOD component
|
||||
body = self.sdk.subscription.models.ModernSubscriptionCreationParameters(
|
||||
display_name,
|
||||
billing_profile_id,
|
||||
sku_id,
|
||||
# owner=<AdPrincipal: for AT-AT user>
|
||||
management_group = self._create_management_group(
|
||||
credentials, management_group_id, display_name, parent_id,
|
||||
)
|
||||
|
||||
# These 2 seem like something that might be worthwhile to allow tiebacks to
|
||||
# TOs filed for the environment
|
||||
billing_account_name = "?"
|
||||
invoice_section_name = "?"
|
||||
# We may also want to create billing sections in the enrollment account
|
||||
sub_creation_operation = sub_client.subscription_factory.create_subscription(
|
||||
billing_account_name, invoice_section_name, body
|
||||
)
|
||||
|
||||
# the resulting object from this process is a link to the new subscription
|
||||
# not a subscription model, so we'll have to unpack the ID
|
||||
new_sub = sub_creation_operation.result()
|
||||
|
||||
subscription_id = self._extract_subscription_id(new_sub.subscription_link)
|
||||
if subscription_id:
|
||||
return subscription_id
|
||||
else:
|
||||
# troublesome error, subscription should exist at this point
|
||||
# but we just don't have a valid ID
|
||||
pass
|
||||
return management_group
|
||||
|
||||
def create_atat_admin_user(
|
||||
self, auth_credentials: Dict, csp_environment_id: str
|
||||
@ -502,6 +485,83 @@ class AzureCloudProvider(CloudProviderInterface):
|
||||
"role_name": role_assignment_id,
|
||||
}
|
||||
|
||||
def _create_application(self, auth_credentials: Dict, application: Application):
|
||||
management_group_name = str(uuid4()) # can be anything, not just uuid
|
||||
display_name = application.name # Does this need to be unique?
|
||||
credentials = self._get_credential_obj(auth_credentials)
|
||||
parent_id = "?" # application.portfolio.csp_details.management_group_id
|
||||
|
||||
return self._create_management_group(
|
||||
credentials, management_group_name, display_name, parent_id,
|
||||
)
|
||||
|
||||
def _create_management_group(
|
||||
self, credentials, management_group_id, display_name, parent_id=None,
|
||||
):
|
||||
mgmgt_group_client = self.sdk.managementgroups.ManagementGroupsAPI(credentials)
|
||||
create_parent_grp_info = self.sdk.managementgroups.models.CreateParentGroupInfo(
|
||||
id=parent_id
|
||||
)
|
||||
create_mgmt_grp_details = self.sdk.managementgroups.models.CreateManagementGroupDetails(
|
||||
parent=create_parent_grp_info
|
||||
)
|
||||
mgmt_grp_create = self.sdk.managementgroups.models.CreateManagementGroupRequest(
|
||||
name=management_group_id,
|
||||
display_name=display_name,
|
||||
details=create_mgmt_grp_details,
|
||||
)
|
||||
create_request = mgmgt_group_client.management_groups.create_or_update(
|
||||
management_group_id, mgmt_grp_create
|
||||
)
|
||||
|
||||
# result is a synchronous wait, might need to do a poll instead to handle first mgmt group create
|
||||
# since we were told it could take 10+ minutes to complete, unless this handles that polling internally
|
||||
return create_request.result()
|
||||
|
||||
def _create_subscription(
|
||||
self,
|
||||
credentials,
|
||||
display_name,
|
||||
billing_profile_id,
|
||||
sku_id,
|
||||
management_group_id,
|
||||
billing_account_name,
|
||||
invoice_section_name,
|
||||
):
|
||||
sub_client = self.sdk.subscription.SubscriptionClient(credentials)
|
||||
|
||||
display_name = f"{environment.application.name}_{environment.name}_{environment.id}" # proposed format
|
||||
billing_profile_id = "?" # where do we source this?
|
||||
sku_id = AZURE_SKU_ID
|
||||
# These 2 seem like something that might be worthwhile to allow tiebacks to
|
||||
# TOs filed for the environment
|
||||
billing_account_name = "?" # from TO?
|
||||
invoice_section_name = "?" # from TO?
|
||||
|
||||
body = self.sdk.subscription.models.ModernSubscriptionCreationParameters(
|
||||
display_name=display_name,
|
||||
billing_profile_id=billing_profile_id,
|
||||
sku_id=sku_id,
|
||||
management_group_id=management_group_id,
|
||||
)
|
||||
|
||||
# We may also want to create billing sections in the enrollment account
|
||||
sub_creation_operation = sub_client.subscription_factory.create_subscription(
|
||||
billing_account_name, invoice_section_name, body
|
||||
)
|
||||
|
||||
# the resulting object from this process is a link to the new subscription
|
||||
# not a subscription model, so we'll have to unpack the ID
|
||||
new_sub = sub_creation_operation.result()
|
||||
|
||||
subscription_id = self._extract_subscription_id(new_sub.subscription_link)
|
||||
if subscription_id:
|
||||
return subscription_id
|
||||
else:
|
||||
# troublesome error, subscription should exist at this point
|
||||
# but we just don't have a valid ID
|
||||
pass
|
||||
|
||||
def _get_management_service_principal(self):
|
||||
# we really should be using graph.microsoft.com, but i'm getting
|
||||
# "expired token" errors for that
|
||||
|
@ -64,10 +64,12 @@ class TaskOrders(BaseDomainClass):
|
||||
db.session.commit()
|
||||
|
||||
@classmethod
|
||||
def sort(cls, task_orders: [TaskOrder]) -> [TaskOrder]:
|
||||
# Sorts a list of task orders on two keys: status (primary) and time_created (secondary)
|
||||
by_time_created = sorted(task_orders, key=lambda to: to.time_created)
|
||||
by_status = sorted(by_time_created, key=lambda to: SORT_ORDERING.get(to.status))
|
||||
def sort_by_status(cls, task_orders):
|
||||
by_status = {status.value: [] for status in SORT_ORDERING}
|
||||
|
||||
for task_order in task_orders:
|
||||
by_status[task_order.display_status].append(task_order)
|
||||
|
||||
return by_status
|
||||
|
||||
@classmethod
|
||||
|
@ -1,5 +1,5 @@
|
||||
from datetime import timedelta
|
||||
from enum import Enum
|
||||
from decimal import Decimal
|
||||
|
||||
from sqlalchemy import Column, DateTime, ForeignKey, String
|
||||
from sqlalchemy.ext.hybrid import hybrid_property
|
||||
@ -17,15 +17,16 @@ class Status(Enum):
|
||||
ACTIVE = "Active"
|
||||
UPCOMING = "Upcoming"
|
||||
EXPIRED = "Expired"
|
||||
UNSIGNED = "Not signed"
|
||||
UNSIGNED = "Unsigned"
|
||||
|
||||
|
||||
SORT_ORDERING = {
|
||||
status: order
|
||||
for (order, status) in enumerate(
|
||||
[Status.DRAFT, Status.ACTIVE, Status.UPCOMING, Status.EXPIRED, Status.UNSIGNED]
|
||||
)
|
||||
}
|
||||
SORT_ORDERING = [
|
||||
Status.ACTIVE,
|
||||
Status.DRAFT,
|
||||
Status.UPCOMING,
|
||||
Status.EXPIRED,
|
||||
Status.UNSIGNED,
|
||||
]
|
||||
|
||||
|
||||
class TaskOrder(Base, mixins.TimestampsMixin):
|
||||
@ -131,12 +132,11 @@ class TaskOrder(Base, mixins.TimestampsMixin):
|
||||
|
||||
@property
|
||||
def start_date(self):
|
||||
return min((c.start_date for c in self.clins), default=self.time_created.date())
|
||||
return min((c.start_date for c in self.clins), default=None)
|
||||
|
||||
@property
|
||||
def end_date(self):
|
||||
default_end_date = self.start_date + timedelta(days=1)
|
||||
return max((c.end_date for c in self.clins), default=default_end_date)
|
||||
return max((c.end_date for c in self.clins), default=None)
|
||||
|
||||
@property
|
||||
def days_to_expiration(self):
|
||||
@ -170,6 +170,11 @@ class TaskOrder(Base, mixins.TimestampsMixin):
|
||||
# Faked for display purposes
|
||||
return 50
|
||||
|
||||
@property
|
||||
def invoiced_funds(self):
|
||||
# TODO: implement this using reporting data from the CSP
|
||||
return self.total_obligated_funds * Decimal(0.75)
|
||||
|
||||
@property
|
||||
def display_status(self):
|
||||
return self.status.value
|
||||
|
@ -20,6 +20,7 @@ from atst.domain.permission_sets import PermissionSets
|
||||
from atst.utils.flash import formatted_flash as flash
|
||||
from atst.utils.localization import translate
|
||||
from atst.jobs import send_mail
|
||||
from atst.routes.errors import log_error
|
||||
|
||||
|
||||
def get_environments_obj_for_app(application):
|
||||
@ -234,7 +235,8 @@ def handle_update_member(application_id, application_role_id, form_data):
|
||||
|
||||
flash("application_member_updated", user_name=app_role.user_name)
|
||||
|
||||
except GeneralCSPException:
|
||||
except GeneralCSPException as exc:
|
||||
log_error(exc)
|
||||
flash(
|
||||
"application_member_update_error", user_name=app_role.user_name,
|
||||
)
|
||||
|
@ -6,7 +6,6 @@ from atst.domain.portfolios import Portfolios
|
||||
from atst.domain.task_orders import TaskOrders
|
||||
from atst.forms.task_order import SignatureForm
|
||||
from atst.models import Permissions
|
||||
from atst.models.task_order import Status as TaskOrderStatus
|
||||
|
||||
|
||||
@task_orders_bp.route("/task_orders/<task_order_id>/review")
|
||||
@ -28,14 +27,9 @@ def review_task_order(task_order_id):
|
||||
@user_can(Permissions.VIEW_PORTFOLIO_FUNDING, message="view portfolio funding")
|
||||
def portfolio_funding(portfolio_id):
|
||||
portfolio = Portfolios.get(g.current_user, portfolio_id)
|
||||
task_orders = TaskOrders.sort(portfolio.task_orders)
|
||||
label_colors = {
|
||||
TaskOrderStatus.DRAFT: "warning",
|
||||
TaskOrderStatus.ACTIVE: "success",
|
||||
TaskOrderStatus.UPCOMING: "info",
|
||||
TaskOrderStatus.EXPIRED: "error",
|
||||
TaskOrderStatus.UNSIGNED: "purple",
|
||||
}
|
||||
task_orders = TaskOrders.sort_by_status(portfolio.task_orders)
|
||||
to_count = len(portfolio.task_orders)
|
||||
# TODO: Get expended amount from the CSP
|
||||
return render_template(
|
||||
"task_orders/index.html", task_orders=task_orders, label_colors=label_colors
|
||||
"task_orders/index.html", task_orders=task_orders, to_count=to_count
|
||||
)
|
||||
|
@ -13,10 +13,10 @@ data:
|
||||
CDN_ORIGIN: https://azure.atat.code.mil
|
||||
CELERY_DEFAULT_QUEUE: celery-master
|
||||
CSP: azure
|
||||
DEBUG: 0
|
||||
DEBUG: "0"
|
||||
FLASK_ENV: master
|
||||
LOG_JSON: "true"
|
||||
MAIL_PORT: 587
|
||||
MAIL_PORT: "587"
|
||||
MAIL_SENDER: postmaster@atat.code.mil
|
||||
MAIL_SERVER: smtp.mailgun.org
|
||||
MAIL_TLS: "true"
|
||||
@ -24,7 +24,7 @@ data:
|
||||
PGAPPNAME: atst
|
||||
PGDATABASE: staging
|
||||
PGHOST: atat-db.postgres.database.azure.com
|
||||
PGPORT: 5432
|
||||
PGPORT: "5432"
|
||||
PGSSLMODE: verify-full
|
||||
PGSSLROOTCERT: /opt/atat/atst/ssl/pgsslrootcert.crt
|
||||
PGUSER: atat_master@atat-db
|
||||
|
@ -9,9 +9,9 @@ data:
|
||||
AZURE_TO_BUCKET_NAME: task-order-pdfs
|
||||
CAC_URL: https://auth-staging.atat.code.mil/login-redirect
|
||||
CELERY_DEFAULT_QUEUE: celery-master
|
||||
DEBUG: 0
|
||||
DEBUG: "0"
|
||||
DISABLE_CRL_CHECK: "true"
|
||||
MAIL_PORT: 587
|
||||
MAIL_PORT: "587"
|
||||
MAIL_SENDER: postmaster@atat.code.mil
|
||||
MAIL_SERVER: smtp.mailgun.org
|
||||
MAIL_TLS: "true"
|
||||
@ -19,7 +19,7 @@ data:
|
||||
PGAPPNAME: atst
|
||||
PGDATABASE: staging
|
||||
PGHOST: atat-db.postgres.database.azure.com
|
||||
PGPORT: 5432
|
||||
PGPORT: "5432"
|
||||
PGSSLMODE: verify-full
|
||||
PGSSLROOTCERT: /opt/atat/atst/ssl/pgsslrootcert.crt
|
||||
PGUSER: atat_master@atat-db
|
||||
|
@ -11,4 +11,10 @@ export default {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
collapse: function() {
|
||||
this.isVisible = false
|
||||
},
|
||||
},
|
||||
}
|
||||
|
16
js/components/accordion_list.js
Normal file
16
js/components/accordion_list.js
Normal file
@ -0,0 +1,16 @@
|
||||
import Accordion from './accordion'
|
||||
|
||||
export default {
|
||||
name: 'accordion-list',
|
||||
|
||||
components: {
|
||||
Accordion,
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleClick: function(e) {
|
||||
e.preventDefault()
|
||||
this.$children.forEach(el => el.collapse())
|
||||
},
|
||||
},
|
||||
}
|
@ -7,6 +7,8 @@ import Vue from 'vue/dist/vue'
|
||||
import VTooltip from 'v-tooltip'
|
||||
import stickybits from 'stickybits'
|
||||
|
||||
import Accordion from './components/accordion'
|
||||
import AccordionList from './components/accordion_list'
|
||||
import dodlogin from './components/dodlogin'
|
||||
import optionsinput from './components/options_input'
|
||||
import multicheckboxinput from './components/multi_checkbox_input'
|
||||
@ -29,7 +31,6 @@ import SemiCollapsibleText from './components/semi_collapsible_text'
|
||||
import ToForm from './components/forms/to_form'
|
||||
import ClinFields from './components/clin_fields'
|
||||
import PopDateRange from './components/pop_date_range'
|
||||
import Accordion from './components/accordion'
|
||||
import ToggleMenu from './components/toggle_menu'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
@ -42,6 +43,7 @@ const app = new Vue({
|
||||
el: '#app-root',
|
||||
components: {
|
||||
Accordion,
|
||||
AccordionList,
|
||||
dodlogin,
|
||||
toggler,
|
||||
optionsinput,
|
||||
|
@ -17,6 +17,7 @@ export default {
|
||||
methods: {
|
||||
toggle: function(e) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
this.isVisible = !this.isVisible
|
||||
},
|
||||
},
|
||||
|
@ -46,5 +46,20 @@
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&--empty {
|
||||
font-weight: $font-bold;
|
||||
color: $color-gray-dark;
|
||||
padding: $gap * 8;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
&-list {
|
||||
max-width: $max-panel-width;
|
||||
|
||||
&__collapse {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -127,21 +127,6 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.label {
|
||||
&--pending,
|
||||
&--started {
|
||||
background-color: $color-gold;
|
||||
}
|
||||
|
||||
&--active {
|
||||
background-color: $color-green;
|
||||
}
|
||||
|
||||
&--expired {
|
||||
background-color: $color-red;
|
||||
}
|
||||
}
|
||||
|
||||
.task-order-document-link {
|
||||
&__icon {
|
||||
padding-top: 0.5rem;
|
||||
|
@ -1,4 +1,5 @@
|
||||
{% from "components/accordion.html" import Accordion %}
|
||||
{% from "components/accordion_list.html" import AccordionList %}
|
||||
{% from "components/empty_state.html" import EmptyState %}
|
||||
{% from "components/sticky_cta.html" import StickyCTA %}
|
||||
{% from "components/icon.html" import Icon %}
|
||||
@ -32,7 +33,7 @@
|
||||
) }}
|
||||
|
||||
{% else %}
|
||||
<div class="usa-accordion">
|
||||
{% call AccordionList() %}
|
||||
{% for application in portfolio.applications|sort(attribute='name') %}
|
||||
{% set section_name = "application-{}".format(application.id) %}
|
||||
{% set title = "Environments ({})".format(application.environments|length) %}
|
||||
@ -76,7 +77,7 @@
|
||||
{% endcall %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
11
templates/components/accordion_list.html
Normal file
11
templates/components/accordion_list.html
Normal file
@ -0,0 +1,11 @@
|
||||
{% macro AccordionList() %}
|
||||
<accordion-list inline-template>
|
||||
<div class="accordion-list usa-accordion">
|
||||
<div class="action-group">
|
||||
<a v-on:click="handleClick($event)" class="accordion-list__collapse">Collapse All</a>
|
||||
</div>
|
||||
<!-- caller iterates over accordion vue components or Accordion jinja macros -->
|
||||
{{ caller() }}
|
||||
</div>
|
||||
</accordion-list>
|
||||
{% endmacro %}
|
@ -1,4 +1,5 @@
|
||||
{% from "components/accordion.html" import Accordion %}
|
||||
{% from "components/accordion_list.html" import AccordionList %}
|
||||
{% from "components/empty_state.html" import EmptyState %}
|
||||
{% from "components/icon.html" import Icon %}
|
||||
{% from "components/sticky_cta.html" import StickyCTA %}
|
||||
@ -13,38 +14,52 @@
|
||||
|
||||
|
||||
{% macro TaskOrderList(task_orders, status) %}
|
||||
{% set status = "All Task Orders" %}
|
||||
<div class="accordion usa-accordion">
|
||||
{% call Accordion(title=status, id=status, heading_tag="h4") %}
|
||||
{% for task_order in task_orders %}
|
||||
<div class="accordion__content--list-item">
|
||||
<h4><a href="{{ url_for('task_orders.review_task_order', task_order_id=task_order.id) }}">Task Order #{{ task_order.number }} {{ Icon("caret_right", classes="icon--tiny icon--primary" ) }}</a></h4>
|
||||
<div class="row">
|
||||
<div class="col col--grow">
|
||||
<h5>
|
||||
Current Period of Performance
|
||||
</h5>
|
||||
<p>
|
||||
{{ task_order.start_date | formattedDate(formatter="%b %d, %Y") }}
|
||||
-
|
||||
{{ task_order.end_date | formattedDate(formatter="%b %d, %Y") }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col col--grow">
|
||||
<h5>Total Value</h5>
|
||||
<p>{{ task_order.total_contract_amount | dollars }}</p>
|
||||
</div>
|
||||
<div class="col col--grow">
|
||||
<h5>Total Obligated</h5>
|
||||
<p>{{ task_order.total_obligated_funds | dollars }}</p>
|
||||
</div>
|
||||
<div class="col col--grow">
|
||||
<h5>Total Expended</h5>
|
||||
<p>$0</p>
|
||||
</div>
|
||||
<div class="accordion">
|
||||
{% call Accordion(title=("task_orders.status_list_title"|translate({'status': status})), id=status, heading_tag="h4") %}
|
||||
{% if task_orders|length > 0 %}
|
||||
{% for task_order in task_orders %}
|
||||
{% set to_number %}
|
||||
{% if task_order.number != "" %}
|
||||
Task Order #{{ task_order.number }}
|
||||
{% else %}
|
||||
New Task Order
|
||||
{% endif %}
|
||||
{% endset %}
|
||||
<div class="accordion__content--list-item">
|
||||
<h4><a href="{{ url_for('task_orders.review_task_order', task_order_id=task_order.id) }}">{{ to_number }} {{ Icon("caret_right", classes="icon--tiny icon--primary" ) }}</a></h4>
|
||||
{% if status != 'Expired' -%}
|
||||
<div class="row">
|
||||
<div class="col col--grow">
|
||||
<h5>
|
||||
Current Period of Performance
|
||||
</h5>
|
||||
<p>
|
||||
{{ task_order.start_date | formattedDate(formatter="%b %d, %Y") }}
|
||||
-
|
||||
{{ task_order.end_date | formattedDate(formatter="%b %d, %Y") }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col col--grow">
|
||||
<h5>Total Value</h5>
|
||||
<p>{{ task_order.total_contract_amount | dollars }}</p>
|
||||
</div>
|
||||
<div class="col col--grow">
|
||||
<h5>Total Obligated</h5>
|
||||
<p>{{ task_order.total_obligated_funds | dollars }}</p>
|
||||
</div>
|
||||
<div class="col col--grow">
|
||||
<h5>Total Expended</h5>
|
||||
<p>{{ task_order.invoiced_funds | dollars }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{%- endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="accordion__content--empty">
|
||||
{{ "task_orders.status_empty_state" | translate({ 'status': status }) }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endcall %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@ -62,8 +77,12 @@
|
||||
|
||||
<div class="portfolio-funding">
|
||||
|
||||
{% if task_orders %}
|
||||
{{ TaskOrderList(task_orders) }}
|
||||
{% if to_count > 0 %}
|
||||
{% call AccordionList() %}
|
||||
{% for status, to_list in task_orders.items() %}
|
||||
{{ TaskOrderList(to_list, status) }}
|
||||
{% endfor %}
|
||||
{% endcall %}
|
||||
{% else %}
|
||||
{{ EmptyState(
|
||||
header="task_orders.empty_state.header"|translate,
|
||||
|
@ -1,27 +1,81 @@
|
||||
import pytest
|
||||
from unittest.mock import Mock
|
||||
|
||||
from uuid import uuid4
|
||||
|
||||
from atst.domain.csp.cloud import AzureCloudProvider
|
||||
|
||||
from tests.mock_azure import mock_azure, AUTH_CREDENTIALS
|
||||
from tests.factories import EnvironmentFactory
|
||||
from tests.factories import EnvironmentFactory, ApplicationFactory
|
||||
|
||||
|
||||
def test_create_environment_succeeds(mock_azure: AzureCloudProvider):
|
||||
# TODO: Directly test create subscription, provide all args √
|
||||
# TODO: Test create environment (create management group with parent)
|
||||
# TODO: Test create application (create manageemnt group with parent)
|
||||
# Create reusable mock for mocking the management group calls for multiple services
|
||||
#
|
||||
|
||||
|
||||
def test_create_subscription_succeeds(mock_azure: AzureCloudProvider):
|
||||
environment = EnvironmentFactory.create()
|
||||
|
||||
subscription_id = str(uuid4())
|
||||
|
||||
credentials = mock_azure._get_credential_obj(AUTH_CREDENTIALS)
|
||||
display_name = "Test Subscription"
|
||||
billing_profile_id = str(uuid4())
|
||||
sku_id = str(uuid4())
|
||||
management_group_id = (
|
||||
environment.cloud_id # environment.csp_details.management_group_id?
|
||||
)
|
||||
billing_account_name = (
|
||||
"?" # environment.application.portfilio.csp_details.billing_account.name?
|
||||
)
|
||||
invoice_section_name = "?" # environment.name? or something specific to billing?
|
||||
|
||||
mock_azure.sdk.subscription.SubscriptionClient.return_value.subscription_factory.create_subscription.return_value.result.return_value.subscription_link = (
|
||||
f"subscriptions/{subscription_id}"
|
||||
)
|
||||
|
||||
result = mock_azure._create_subscription(
|
||||
credentials,
|
||||
display_name,
|
||||
billing_profile_id,
|
||||
sku_id,
|
||||
management_group_id,
|
||||
billing_account_name,
|
||||
invoice_section_name,
|
||||
)
|
||||
|
||||
assert result == subscription_id
|
||||
|
||||
|
||||
def mock_management_group_create(mock_azure, spec_dict):
|
||||
mock_azure.sdk.managementgroups.ManagementGroupsAPI.return_value.management_groups.create_or_update.return_value.result.return_value = Mock(
|
||||
**spec_dict
|
||||
)
|
||||
|
||||
|
||||
def test_create_environment_succeeds(mock_azure: AzureCloudProvider):
|
||||
environment = EnvironmentFactory.create()
|
||||
|
||||
mock_management_group_create(mock_azure, {"id": "Test Id"})
|
||||
|
||||
result = mock_azure.create_environment(
|
||||
AUTH_CREDENTIALS, environment.creator, environment
|
||||
)
|
||||
|
||||
assert result == subscription_id
|
||||
assert result.id == "Test Id"
|
||||
|
||||
|
||||
def test_create_application_succeeds(mock_azure: AzureCloudProvider):
|
||||
application = ApplicationFactory.create()
|
||||
|
||||
mock_management_group_create(mock_azure, {"id": "Test Id"})
|
||||
|
||||
result = mock_azure._create_application(AUTH_CREDENTIALS, application)
|
||||
|
||||
assert result.id == "Test Id"
|
||||
|
||||
|
||||
def test_create_atat_admin_user_succeeds(mock_azure: AzureCloudProvider):
|
||||
|
@ -3,78 +3,11 @@ from datetime import date, timedelta
|
||||
from decimal import Decimal
|
||||
|
||||
from atst.domain.task_orders import TaskOrders
|
||||
from atst.models import Attachment, TaskOrder
|
||||
from atst.models import Attachment
|
||||
from atst.models.task_order import TaskOrder, SORT_ORDERING, Status
|
||||
from tests.factories import TaskOrderFactory, CLINFactory, PortfolioFactory
|
||||
|
||||
|
||||
def test_task_order_sorting():
|
||||
"""
|
||||
Task orders should be listed first by status, and then by time_created.
|
||||
"""
|
||||
|
||||
today = date.today()
|
||||
yesterday = today - timedelta(days=1)
|
||||
future = today + timedelta(days=100)
|
||||
|
||||
task_orders = [
|
||||
# Draft
|
||||
TaskOrderFactory.create(pdf=None),
|
||||
TaskOrderFactory.create(pdf=None),
|
||||
TaskOrderFactory.create(pdf=None),
|
||||
# Active
|
||||
TaskOrderFactory.create(
|
||||
signed_at=yesterday,
|
||||
clins=[CLINFactory.create(start_date=yesterday, end_date=future)],
|
||||
),
|
||||
TaskOrderFactory.create(
|
||||
signed_at=yesterday,
|
||||
clins=[CLINFactory.create(start_date=yesterday, end_date=future)],
|
||||
),
|
||||
TaskOrderFactory.create(
|
||||
signed_at=yesterday,
|
||||
clins=[CLINFactory.create(start_date=yesterday, end_date=future)],
|
||||
),
|
||||
# Upcoming
|
||||
TaskOrderFactory.create(
|
||||
signed_at=yesterday,
|
||||
clins=[CLINFactory.create(start_date=future, end_date=future)],
|
||||
),
|
||||
TaskOrderFactory.create(
|
||||
signed_at=yesterday,
|
||||
clins=[CLINFactory.create(start_date=future, end_date=future)],
|
||||
),
|
||||
TaskOrderFactory.create(
|
||||
signed_at=yesterday,
|
||||
clins=[CLINFactory.create(start_date=future, end_date=future)],
|
||||
),
|
||||
# Expired
|
||||
TaskOrderFactory.create(
|
||||
signed_at=yesterday,
|
||||
clins=[CLINFactory.create(start_date=yesterday, end_date=yesterday)],
|
||||
),
|
||||
TaskOrderFactory.create(
|
||||
signed_at=yesterday,
|
||||
clins=[CLINFactory.create(start_date=yesterday, end_date=yesterday)],
|
||||
),
|
||||
TaskOrderFactory.create(
|
||||
signed_at=yesterday,
|
||||
clins=[CLINFactory.create(start_date=yesterday, end_date=yesterday)],
|
||||
),
|
||||
# Unsigned
|
||||
TaskOrderFactory.create(
|
||||
clins=[CLINFactory.create(start_date=today, end_date=today)]
|
||||
),
|
||||
TaskOrderFactory.create(
|
||||
clins=[CLINFactory.create(start_date=today, end_date=today)]
|
||||
),
|
||||
TaskOrderFactory.create(
|
||||
clins=[CLINFactory.create(start_date=today, end_date=today)]
|
||||
),
|
||||
]
|
||||
|
||||
assert TaskOrders.sort(task_orders) == task_orders
|
||||
|
||||
|
||||
def test_create_adds_clins():
|
||||
portfolio = PortfolioFactory.create()
|
||||
clins = [
|
||||
@ -177,3 +110,47 @@ def test_delete_task_order_with_clins(session):
|
||||
assert not session.query(
|
||||
session.query(TaskOrder).filter_by(id=task_order.id).exists()
|
||||
).scalar()
|
||||
|
||||
|
||||
def test_task_order_sort_by_status():
|
||||
today = date.today()
|
||||
yesterday = today - timedelta(days=1)
|
||||
future = today + timedelta(days=100)
|
||||
|
||||
initial_to_list = [
|
||||
# Draft
|
||||
TaskOrderFactory.create(pdf=None),
|
||||
TaskOrderFactory.create(pdf=None),
|
||||
TaskOrderFactory.create(pdf=None),
|
||||
# Active
|
||||
TaskOrderFactory.create(
|
||||
signed_at=yesterday,
|
||||
clins=[CLINFactory.create(start_date=yesterday, end_date=future)],
|
||||
),
|
||||
# Upcoming
|
||||
TaskOrderFactory.create(
|
||||
signed_at=yesterday,
|
||||
clins=[CLINFactory.create(start_date=future, end_date=future)],
|
||||
),
|
||||
# Expired
|
||||
TaskOrderFactory.create(
|
||||
signed_at=yesterday,
|
||||
clins=[CLINFactory.create(start_date=yesterday, end_date=yesterday)],
|
||||
),
|
||||
TaskOrderFactory.create(
|
||||
signed_at=yesterday,
|
||||
clins=[CLINFactory.create(start_date=yesterday, end_date=yesterday)],
|
||||
),
|
||||
# Unsigned
|
||||
TaskOrderFactory.create(
|
||||
clins=[CLINFactory.create(start_date=today, end_date=today)]
|
||||
),
|
||||
]
|
||||
|
||||
sorted_by_status = TaskOrders.sort_by_status(initial_to_list)
|
||||
assert len(sorted_by_status["Draft"]) == 3
|
||||
assert len(sorted_by_status["Active"]) == 1
|
||||
assert len(sorted_by_status["Upcoming"]) == 1
|
||||
assert len(sorted_by_status["Expired"]) == 2
|
||||
assert len(sorted_by_status["Unsigned"]) == 1
|
||||
assert list(sorted_by_status.keys()) == [status.value for status in SORT_ORDERING]
|
||||
|
@ -10,9 +10,9 @@ AZURE_CONFIG = {
|
||||
}
|
||||
|
||||
AUTH_CREDENTIALS = {
|
||||
"CLIENT_ID": AZURE_CONFIG["AZURE_CLIENT_ID"],
|
||||
"SECRET_KEY": AZURE_CONFIG["AZURE_SECRET_KEY"],
|
||||
"TENANT_ID": AZURE_CONFIG["AZURE_TENANT_ID"],
|
||||
"client_id": AZURE_CONFIG["AZURE_CLIENT_ID"],
|
||||
"secret_key": AZURE_CONFIG["AZURE_SECRET_KEY"],
|
||||
"tenant_id": AZURE_CONFIG["AZURE_TENANT_ID"],
|
||||
}
|
||||
|
||||
|
||||
@ -28,6 +28,12 @@ def mock_authorization():
|
||||
return Mock(spec=authorization)
|
||||
|
||||
|
||||
def mock_managementgroups():
|
||||
from azure.mgmt import managementgroups
|
||||
|
||||
return Mock(spec=managementgroups)
|
||||
|
||||
|
||||
def mock_graphrbac():
|
||||
import azure.graphrbac as graphrbac
|
||||
|
||||
@ -46,6 +52,7 @@ class MockAzureSDK(object):
|
||||
|
||||
self.subscription = mock_subscription()
|
||||
self.authorization = mock_authorization()
|
||||
self.managementgroups = mock_managementgroups()
|
||||
self.graphrbac = mock_graphrbac()
|
||||
self.credentials = mock_credentials()
|
||||
# may change to a JEDI cloud
|
||||
|
@ -12,6 +12,7 @@ from atst.domain.application_roles import ApplicationRoles
|
||||
from atst.domain.environment_roles import EnvironmentRoles
|
||||
from atst.domain.invitations import ApplicationInvitations
|
||||
from atst.domain.common import Paginator
|
||||
from atst.domain.csp.cloud import GeneralCSPException
|
||||
from atst.domain.permission_sets import PermissionSets
|
||||
from atst.models.application_role import Status as ApplicationRoleStatus
|
||||
from atst.models.environment_role import CSPRole, EnvironmentRole
|
||||
@ -748,3 +749,41 @@ def test_handle_update_member(set_g):
|
||||
assert len(application.roles) == 1
|
||||
assert len(app_role.environment_roles) == 1
|
||||
assert app_role.environment_roles[0].environment == env
|
||||
|
||||
|
||||
def test_handle_update_member_with_error(set_g, monkeypatch, mock_logger):
|
||||
exception = "An error occurred."
|
||||
|
||||
def _raise_csp_exception(*args, **kwargs):
|
||||
raise GeneralCSPException(exception)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"atst.domain.environments.Environments.update_env_role", _raise_csp_exception
|
||||
)
|
||||
|
||||
user = UserFactory.create()
|
||||
application = ApplicationFactory.create(
|
||||
environments=[{"name": "Naboo"}, {"name": "Endor"}]
|
||||
)
|
||||
(env, env_1) = application.environments
|
||||
app_role = ApplicationRoleFactory(application=application)
|
||||
set_g("current_user", application.portfolio.owner)
|
||||
set_g("portfolio", application.portfolio)
|
||||
set_g("application", application)
|
||||
|
||||
form_data = ImmutableMultiDict(
|
||||
{
|
||||
"environment_roles-0-environment_id": env.id,
|
||||
"environment_roles-0-role": "Basic Access",
|
||||
"environment_roles-0-environment_name": env.name,
|
||||
"environment_roles-1-environment_id": env_1.id,
|
||||
"environment_roles-1-role": NO_ACCESS,
|
||||
"environment_roles-1-environment_name": env_1.name,
|
||||
"perms_env_mgmt": True,
|
||||
"perms_team_mgmt": True,
|
||||
"perms_del_env": True,
|
||||
}
|
||||
)
|
||||
handle_update_member(application.id, app_role.id, form_data)
|
||||
|
||||
assert mock_logger.messages[-1] == exception
|
||||
|
@ -29,8 +29,10 @@ def task_order():
|
||||
user = UserFactory.create()
|
||||
portfolio = PortfolioFactory.create(owner=user)
|
||||
attachment = Attachment(filename="sample_attachment", object_name="sample")
|
||||
task_order = TaskOrderFactory.create(portfolio=portfolio)
|
||||
CLINFactory.create(task_order=task_order)
|
||||
|
||||
return TaskOrderFactory.create(portfolio=portfolio)
|
||||
return task_order
|
||||
|
||||
|
||||
def test_review_task_order_not_draft(client, user_session, task_order):
|
||||
|
@ -19,6 +19,16 @@ def build_pdf_form_data(filename="sample.pdf", object_name=None):
|
||||
def task_order():
|
||||
user = UserFactory.create()
|
||||
portfolio = PortfolioFactory.create(owner=user)
|
||||
task_order = TaskOrderFactory.create(portfolio=portfolio)
|
||||
CLINFactory.create(task_order=task_order)
|
||||
|
||||
return task_order
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def incomplete_to():
|
||||
user = UserFactory.create()
|
||||
portfolio = PortfolioFactory.create(owner=user)
|
||||
|
||||
return TaskOrderFactory.create(portfolio=portfolio)
|
||||
|
||||
@ -234,7 +244,7 @@ def test_task_orders_submit_form_step_three_add_clins_existing_to(
|
||||
},
|
||||
]
|
||||
TaskOrders.create_clins(task_order.id, clin_list)
|
||||
assert len(task_order.clins) == 2
|
||||
assert len(task_order.clins) == 3
|
||||
|
||||
user_session(task_order.portfolio.owner)
|
||||
form_data = {
|
||||
@ -267,11 +277,11 @@ def test_task_orders_form_step_four_review(client, user_session, completed_task_
|
||||
|
||||
|
||||
def test_task_orders_form_step_four_review_incomplete_to(
|
||||
client, user_session, task_order
|
||||
client, user_session, incomplete_to
|
||||
):
|
||||
user_session(task_order.portfolio.owner)
|
||||
user_session(incomplete_to.portfolio.owner)
|
||||
response = client.get(
|
||||
url_for("task_orders.form_step_four_review", task_order_id=task_order.id)
|
||||
url_for("task_orders.form_step_four_review", task_order_id=incomplete_to.id)
|
||||
)
|
||||
assert response.status_code == 404
|
||||
|
||||
@ -290,12 +300,13 @@ def test_task_orders_form_step_five_confirm_signature(
|
||||
|
||||
|
||||
def test_task_orders_form_step_five_confirm_signature_incomplete_to(
|
||||
client, user_session, task_order
|
||||
client, user_session, incomplete_to
|
||||
):
|
||||
user_session(task_order.portfolio.owner)
|
||||
user_session(incomplete_to.portfolio.owner)
|
||||
response = client.get(
|
||||
url_for(
|
||||
"task_orders.form_step_five_confirm_signature", task_order_id=task_order.id
|
||||
"task_orders.form_step_five_confirm_signature",
|
||||
task_order_id=incomplete_to.id,
|
||||
)
|
||||
)
|
||||
assert response.status_code == 404
|
||||
|
@ -40,6 +40,9 @@ class FakeLogger:
|
||||
def error(self, msg, *args, **kwargs):
|
||||
self._log("error", msg, *args, **kwargs)
|
||||
|
||||
def exception(self, msg, *args, **kwargs):
|
||||
self._log("exception", msg, *args, **kwargs)
|
||||
|
||||
def _log(self, _lvl, msg, *args, **kwargs):
|
||||
self.messages.append(msg)
|
||||
if "extra" in kwargs:
|
||||
|
@ -529,6 +529,8 @@ task_orders:
|
||||
team_title: Your team
|
||||
sign:
|
||||
digital_signature_description: I acknowledge that the uploaded task order contains the required KO signature.
|
||||
status_empty_state: 'This Portfolio has no {status} Task Orders.'
|
||||
status_list_title: '{status} Task Orders'
|
||||
JEDICLINType:
|
||||
JEDI_CLIN_1: 'IDIQ CLIN 0001 Unclassified IaaS/PaaS'
|
||||
JEDI_CLIN_2: 'IDIQ CLIN 0002 Classified IaaS/PaaS'
|
||||
|
Loading…
x
Reference in New Issue
Block a user