Compare commits

...

472 Commits

Author SHA1 Message Date
Nico Schottelius ecc9e6f734 [reverseDNS] add basic logic 2020-10-25 22:43:34 +01:00
Nico Schottelius 20c7c86703 restructure to move uncloudnetwork into core 2020-10-25 21:00:30 +01:00
Nico Schottelius 8959bc6ad5 various updates 2020-10-25 13:52:36 +01:00
Nico Schottelius 0cd8a3a787 ++update ungleich_provider 2020-10-11 22:36:01 +02:00
Nico Schottelius bbc7625550 phase in configuration - move address to base 2020-10-11 22:32:08 +02:00
Nico Schottelius fe4e200dc0 Begin phasing in the uncloudprovider 2020-10-11 17:45:25 +02:00
Nico Schottelius e03cdf214a update VAT importer 2020-10-08 19:54:04 +02:00
Nico Schottelius 50fd9e1f37 ++work 2020-10-07 00:54:56 +02:00
Nico Schottelius 2e74661702 Fix first test case / billing 2020-10-06 23:14:32 +02:00
Nico Schottelius c26ff253de One step furter to allow saving of orders w/o explicit recurringperiod 2020-10-06 19:21:37 +02:00
Nico Schottelius 9623a77907 Updating for products/recurring periods 2020-10-06 18:53:13 +02:00
Nico Schottelius c435639241 gitignore some tests 2020-10-06 16:13:03 +02:00
Nico Schottelius 992c7c551e Make recurring period a database model
- For easier handling (foreignkeys, many2many)
- For higher flexibility (users can define their own periods)
2020-10-06 15:46:22 +02:00
Nico Schottelius 58883765d7 [tests] back to 5 working tests! 2020-09-28 23:16:17 +02:00
Nico Schottelius 8d8c4d660c Can order a generic product now 2020-09-28 21:59:35 +02:00
Nico Schottelius c32499199a Add JSON support for product description 2020-09-28 21:34:24 +02:00
Nico Schottelius c6bacab35a Phasing out Product model
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-09-28 20:59:08 +02:00
Nico Schottelius 1aead50170 remove big mistake: orders from product
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-09-28 20:44:50 +02:00
Nico Schottelius d8a7964fed Continue to refactor for shifting logic into the order 2020-09-09 00:35:55 +02:00
Nico Schottelius 077c665c53 ++update 2020-09-03 17:16:18 +02:00
Nico Schottelius f7274fe967 Adding logic to order to find out whether its closed 2020-09-03 16:38:51 +02:00
Nico Schottelius 1c7d81762d begin splitting bill record creation function 2020-09-02 16:02:28 +02:00
Nico Schottelius 18f9a3848a Implement ending/replacing date logic 2020-08-27 22:00:54 +02:00
Nico Schottelius 9211894b23 implement basic logic for updating a recurring order
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-08-27 14:45:37 +02:00
Nico Schottelius b8b15704a3 begin testing bill sums
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-08-25 21:53:25 +02:00
Nico Schottelius ab412cb877 Test that creating products w/o correct billing address fails 2020-08-25 21:31:12 +02:00
Nico Schottelius 7b83efe995 [pay] make sample products more modular 2020-08-25 21:11:28 +02:00
Nico Schottelius 4d5ca58b2a [tests] cleanup old tests
Finally manage.py tests runs through
2020-08-25 20:40:33 +02:00
Nico Schottelius f693dd3d18 ++notes 2020-08-09 21:10:43 +02:00
Nico Schottelius 5ceaaf7c90 bill cleanup, note next step 2020-08-09 14:52:42 +02:00
Nico Schottelius 2b29e300dd [product] migrate orders to ManyToManyField 2020-08-09 14:44:29 +02:00
Nico Schottelius 8df1d8dc7c begin refactor product to user orders instead of single order
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-08-09 14:38:10 +02:00
Nico Schottelius ef02cb61fd Refine tests for bills, multiple bills 2020-08-09 12:34:25 +02:00
Nico Schottelius 70c450afc8 fix tests for Product()
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-08-09 11:44:22 +02:00
Nico Schottelius 0dd1093812 add sample products and improve testing for Product 2020-08-09 11:02:45 +02:00
Nico Schottelius 6a928a2b2a Fix tests for billing 2020-08-09 10:18:15 +02:00
Nico Schottelius 89519e48a9 Various updates 2020-08-09 10:14:49 +02:00
Nico Schottelius e169b8c1d1 Implement the whole billing logic
The major part has been written!
2020-08-09 10:14:31 +02:00
Nico Schottelius d7c0c40926 first bill generation works
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-08-09 00:37:27 +02:00
Nico Schottelius fd39526350 Improve billing address testing 2020-08-08 23:02:24 +02:00
Nico Schottelius 78d1de9031 Remove orderrecord
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-08-08 22:37:00 +02:00
Nico Schottelius db1a69561b pass first 2 bill tests
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-08-08 22:31:43 +02:00
Nico Schottelius 9bf0a99f6a uncloud pay cleanups
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-08-08 22:20:49 +02:00
Nico Schottelius c9be8cc50b orders only have 1 price
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-08-08 21:54:44 +02:00
Nico Schottelius 8da6a1e19c update models/new django
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-08-08 21:54:07 +02:00
Nico Schottelius ff8fdb76b1 ++ test 2020-08-08 19:30:25 +02:00
Nico Schottelius 9b00ef11fb ++stuff 2020-08-04 18:56:36 +02:00
Nico Schottelius d2bd6ba200 [test] update for uncloud_net 2020-08-04 12:41:15 +02:00
Nico Schottelius 165dacb7bf update to use new JSONFIELD
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-08-04 12:01:44 +02:00
Nico Schottelius ee79877a27 +migrations
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-08-04 11:40:34 +02:00
Nico Schottelius 2ce667e8c7 model changes
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-08-04 11:26:42 +02:00
Nico Schottelius e563780142 Add some sample VMs 2020-08-02 00:55:07 +02:00
Nico Schottelius 66233a1ce5 [doc] more cleanup 2020-08-01 23:44:10 +02:00
Nico Schottelius 9d5d8657cb [doc] Move install and co. into the main documentation 2020-08-01 23:42:36 +02:00
Nico Schottelius a091079677 in between commit
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-08-01 23:20:14 +02:00
Nico Schottelius c9a941e290 [vm] add disks inlined to VMs 2020-08-01 18:48:51 +02:00
Nico Schottelius f7b14bf507 cleanup migrations
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-08-01 18:38:38 +02:00
Nico Schottelius ed40b21d16 [doc] begin describe replacing orders 2020-08-01 18:31:27 +02:00
Nico Schottelius 880e4d046b [vm] cleanup 2020-08-01 18:30:40 +02:00
Nico Schottelius cbd5a08ae7 make price a real property of order
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-08-01 18:27:12 +02:00
Nico Schottelius 64780bfc6c [models] update / doc 2020-08-01 16:29:24 +02:00
Nico Schottelius 9c1b4ab275 [db] add migration for vmdiskproduct 2020-08-01 16:29:11 +02:00
Nico Schottelius 2771a7518a [doc] cleanup 2020-08-01 16:28:19 +02:00
Nico Schottelius 9b3493a661 [doc] integrate vpn into manual 2020-08-01 16:24:21 +02:00
Nico Schottelius 932ac06cea begin doc in views 2020-08-01 14:05:56 +02:00
Nico Schottelius 05a897db70 +whitespace 2020-08-01 14:05:50 +02:00
Nico Schottelius 011096f152 [bootstrap user] remove syntax errors 2020-08-01 14:05:36 +02:00
Nico Schottelius 55ba61e36b [urls] remove obsolete comment 2020-08-01 14:05:26 +02:00
Nico Schottelius 1265e23750 [doc] add bootstrap 2020-08-01 14:05:12 +02:00
Nico Schottelius bc0c77a393 add sql migration for opennebula
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-08-01 14:03:22 +02:00
Nico Schottelius bdba3bffe2 [opennebula] turn VM into a product 2020-08-01 14:02:54 +02:00
Nico Schottelius 3745a0e2b1 [doc] ignore generated files 2020-08-01 13:24:51 +02:00
Nico Schottelius 7ce28b0b60 Begin new uncloud manual 2020-08-01 13:07:44 +02:00
Nico Schottelius 662e706eab begin to fix bill view 2020-06-21 23:54:57 +02:00
Nico Schottelius 11de455d23 begin to add pdf view of bill into admin 2020-06-21 23:46:26 +02:00
Nico Schottelius 126d9da764 Use quantity instead of usage_count 2020-06-21 16:42:55 +02:00
Nico Schottelius 8a17ee6de5 Include BillRecords in the admin 2020-06-21 16:08:00 +02:00
Nico Schottelius 721472b416 Fix constraint to active = True 2020-06-21 14:45:05 +02:00
Nico Schottelius 424ceafbb8 Enhance make-admin to allow making superuser 2020-06-21 14:44:55 +02:00
Nico Schottelius 1e68539ed8 remove uuid primary key
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-06-21 14:35:12 +02:00
Nico Schottelius 3ef19610f3 add script to reset migrations
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-06-21 14:34:48 +02:00
Nico Schottelius 8decfe1b16 Phase in admin, remove uuid from bills 2020-06-21 13:46:54 +02:00
Nico Schottelius 662845128f ++ billing details 2020-06-20 23:47:26 +02:00
Nico Schottelius 95011c2058 Add readme for billing 2020-06-20 22:15:15 +02:00
Nico Schottelius a3f3ca8cf9 in the middle of restructering 2020-05-24 13:45:03 +02:00
Nico Schottelius 5d1eaaf0af Add new models backup - before major refactoring 2020-05-24 12:46:11 +02:00
Nico Schottelius bcd141730d convert recurring period into an integerfield
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-05-23 23:38:34 +02:00
Nico Schottelius 15535433e8 begin to change to day based differences
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-05-23 23:32:45 +02:00
Nico Schottelius 18b862c2e1 fix syntax errors 2020-05-23 23:08:59 +02:00
Nico Schottelius b8652c921e Merge branch 'master' of code.ungleich.ch:uncloud/uncloud 2020-05-23 21:33:04 +02:00
Nico Schottelius 8bbcc5df5f ++dev 2020-05-23 21:32:56 +02:00
Nico Schottelius 0202f80a37 in between pay commit 2020-05-23 21:23:06 +02:00
Nico Schottelius caedf874e4 [vpn] add tests 2020-05-20 21:00:08 +02:00
Nico Schottelius f17f9060b0 [config] add path to chrome 2020-05-17 23:34:13 +02:00
Nico Schottelius 04fac71a85 Merge branch 'master' of code.ungleich.ch:uncloud/uncloud
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-05-17 22:38:49 +02:00
Nico Schottelius ef76304bae + old stuff 2020-05-17 22:35:25 +02:00
Nico Schottelius ec447e0dc4 Add support for primary address in user.
Closes #35

Fixes #35
2020-05-10 21:47:44 +02:00
Nico Schottelius ca2065a94d gitignore for local settings 2020-05-10 14:37:20 +02:00
Nico Schottelius dc7a465a8c Fix exception if first order does not have billing address 2020-05-10 14:22:09 +02:00
fnux 65440ab2ef Add depends_on relation on orders 2020-05-08 16:47:32 +02:00
fnux d794b24c86 Make VM order-able again 2020-05-08 16:31:33 +02:00
fnux df059fb00d Speed-up CI with pre-built image, add resources directory 2020-05-08 12:15:40 +02:00
fnux 67af7b5465 Add missing billnico migration 2020-05-08 11:43:01 +02:00
fnux beb5bd7ee4 Fix existing uncloud_pay tests 2020-05-08 11:33:59 +02:00
fnux 9574d69f4c Move VAT rate CSV out of archive/ 2020-05-08 11:23:09 +02:00
fnux 74e2168529 Fix floating-point issue on bills (Fix #31) 2020-05-08 11:13:11 +02:00
fnux 444d6ded28 Move AMOUNT_* and COUNTRIES to uncloud_pay/init.py 2020-05-08 10:56:03 +02:00
fnux cbba1f4169 Add admin bill generation endpoint 2020-05-08 10:42:04 +02:00
fnux d47c94ba84 Implement non-destructive order updates 2020-05-08 10:07:44 +02:00
fnux 89e853b490 Add order termination logic 2020-05-08 09:31:46 +02:00
Nico Schottelius 1b97fc8fc7 Merge branch 'master' of code.ungleich.ch:uncloud/uncloud 2020-05-07 20:22:49 +02:00
Nico Schottelius 41a2f18453 In between commit 2020-05-07 20:22:42 +02:00
fnux a086d46136 Allow bill download from admin bill endpoint 2020-05-07 15:45:04 +02:00
fnux ae2bad5754 Generate bill PDFs from /my/bill 2020-05-07 15:38:49 +02:00
fnux 3874165189 Fix bill generation 2020-05-07 14:24:04 +02:00
fnux 56d98cbb55 Implement Orders/Bills permissions, unpaid bill views 2020-05-07 13:12:38 +02:00
fnux 718abab9d2 Add make-admin command to uncloud_auth 2020-05-07 12:45:06 +02:00
fnux 268e08c4db Adapt README for SQLite 2020-05-07 12:31:59 +02:00
fnux b8ac99acb6 On more small commit to fix README formatting 2020-05-07 12:25:05 +02:00
fnux 221d98af4b Inline CI badges 2020-05-07 12:24:17 +02:00
fnux ebd4e6fa1b Add fancy CI badges to README 2020-05-07 12:23:17 +02:00
fnux b512d42058 Add devel environment setup instructions 2020-05-07 12:21:49 +02:00
fnux 1245c191c0 Adapt CI to new structure 2020-05-07 12:13:48 +02:00
fnux 95d43f002f Move django-based uncloud to top-level 2020-05-07 12:12:35 +02:00
fnux 0560063326 Add description field to Orders 2020-05-07 12:08:18 +02:00
fnux db3c29d17e Fix admin order creation 2020-05-07 12:05:26 +02:00
fnux 892b2b6f13 Revert "Disable vat validator to get project back running"
This reverts commit 1cf20a2cb6.
2020-05-07 12:03:28 +02:00
Nico Schottelius aa8ade4730 Add readme about identifiers 2020-05-05 16:01:47 +02:00
Nico Schottelius 594f1a9b69 +hacks 2020-05-05 15:19:50 +02:00
Nico Schottelius e3b28354fe ++notes 2020-05-05 15:19:04 +02:00
Nico Schottelius 99a18232aa VMs now properly set their pricing 2020-05-02 23:44:20 +02:00
Nico Schottelius c835c874d5 [BREAKING] make Order a stand-alone version
I think that while the idea of an Orderrecord is good, we might get
away / have a simpler implementation if we only use orders and
reference them where needed.

I saved the previous Order model for easy rollback, if my assumption
is wrong.
2020-05-02 22:48:05 +02:00
Nico Schottelius 028f1ebe6e Entry point /beta/vm/ works for creating VM + order 2020-05-02 22:03:34 +02:00
Nico Schottelius 9ef5309b91 +db migrations for pay/vm
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-05-02 21:21:29 +02:00
Nico Schottelius 4097c2ce13 BillingAddress: make mget_preferred_address a classmethod 2020-05-02 21:20:14 +02:00
Nico Schottelius 736fe27493 Add issues.org as a shortcut for registering issues 2020-05-02 20:45:19 +02:00
Nico Schottelius 7d708cfbb6 Fix empty vat number bug 2020-05-02 20:42:09 +02:00
Nico Schottelius 927fb20671 Versionise API and cleanups 2020-05-02 20:31:36 +02:00
Nico Schottelius eea654a9f8 Phase in new beta/vm view for creating vms + orders + bills 2020-05-02 19:15:48 +02:00
Nico Schottelius 2cda6441f4 Refactor secret / local settings handling 2020-05-02 00:16:29 +02:00
Nico Schottelius 62d9ccbbef [vpn] begin to introduce save() method
The save() and delete() method will create/manage the orders
2020-04-27 18:25:44 +02:00
Nico Schottelius 1cf20a2cb6 Disable vat validator to get project back running 2020-04-27 18:25:27 +02:00
fnux 94932edebe Add user admin endpoint, import from LDAP 2020-04-18 15:11:52 +02:00
fnux a15952862a Make VM order-able again 2020-04-18 15:11:46 +02:00
fnux cec4263621 Merge branch 'vat-handling' into 'master'
VAT support handling

See merge request uncloud/uncloud!7
2020-04-18 11:57:16 +02:00
fnux f61b91dab2 Catch any exception from VIES VAT check 2020-04-18 11:51:13 +02:00
fnux b3afad5d5d Compute VAT rate and amount on bill generation 2020-04-18 11:43:55 +02:00
fnux 3a03717b12 Split bills between orders of the same billing address 2020-04-18 11:21:11 +02:00
fnux db9ff5d18b Display allr elevant values on Bill serializer/page 2020-04-18 10:43:23 +02:00
fnux a49fe6ff51 Properly wire billing addresses to uncloud_service 2020-04-18 10:40:11 +02:00
fnux dd0c1cba94 Remove legacy ungleich_service migrations 2020-04-18 10:39:57 +02:00
fnux c0e12884e1 Sync migrations - again! 2020-04-18 09:38:12 +02:00
fnux 9bbe3b3b56 Adapt uncloud_pay tests to support billing addresses 2020-04-18 09:26:34 +02:00
fnux e6eba7542b Minor fixes, DB sync after rebase 2020-04-18 09:26:21 +02:00
fnux 0522927c50 Start wiring BillingAddresses to bills & orders 2020-04-18 09:13:52 +02:00
fnux 3fa1d5753e Minimal VAT validation on billing address registration 2020-04-18 09:13:04 +02:00
fnux c6ca94800e Add BillingAddress structure to users 2020-04-18 09:13:04 +02:00
fnux ad187c02da Import VAT rates "importer" from dynamicweb 2020-04-18 09:13:04 +02:00
fnux 7afb3f8793 Merge branch 'uncloud-product-activation' into 'master'
Handle product activation on payment

See merge request uncloud/uncloud!6
2020-04-18 09:08:32 +02:00
fnux 86775af4c8 Fix product activation tests after rebase 2020-04-18 09:02:33 +02:00
fnux 83d2cd465d Sync migrations after rebase 2020-04-18 08:42:50 +02:00
fnux b6c976b722 Commit autp-generated migrations (missing from master?) 2020-04-18 08:36:41 +02:00
fnux d1e993140c Add simple product activation test 2020-04-18 08:36:41 +02:00
fnux c57780fb4d Add naive GenericServiceProduct 2020-04-18 08:36:41 +02:00
fnux 83a0ca0e4e Adapt billing tests to product activation structure 2020-04-18 08:35:22 +02:00
fnux 5d5bf486b5 Initial product activation implementation 2020-04-18 08:34:41 +02:00
fnux 839bd5f8a9 Merge branch 'vm-ordering-disk' into 'master'
Wire disk images to VM creation/ordering, make Order records transparents

See merge request uncloud/uncloud!4
2020-04-18 08:32:07 +02:00
fnux aa0702faba Add chromium to path on CI environment 2020-04-13 12:06:03 +02:00
fnux 9a57153c4e Commit missing migrations 2020-04-13 12:02:49 +02:00
fnux f5897ed4b1 Adapt recurring price of VM and Matrix to new scheme 2020-04-13 12:00:59 +02:00
fnux 14f59430bb Restore Order.add_record, used by uncloud_pay tests 2020-04-13 11:54:41 +02:00
fnux 05f8f1f6c0 Fix dependency issue in CI job 2020-04-13 11:40:19 +02:00
fnux 1a58508f8b Rename ungleich_service into uncloud_service 2020-04-13 11:39:49 +02:00
fnux e67bd03997 Migration fix after rebase 2020-04-13 11:19:18 +02:00
fnux a4cc4304f9 Adapt managed service to create VMDiskProduct 2020-04-13 11:19:18 +02:00
fnux d3b7470294 Wire disk images to VM creation/ordering 2020-04-13 11:19:18 +02:00
fnux a7e9f3c09d Move Order.add_record to save hook in abstract Product 2020-04-13 10:42:44 +02:00
Nico Schottelius ff133e81b7 [vpn] update to show reservations, create wireguard config 2020-04-12 22:55:22 +02:00
Nico Schottelius 85b4d70592 [vpn] make a vpn creat-able!
[15:40] line:~% http -a nicoschottelius:$(pass ungleich.ch/nico.schottelius@ungleich.ch)  http://localhost:8000/net/vpn/ network_size=48  wireguard_public_key=$(wg genkey | wg pubkey)
HTTP/1.1 201 Created
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 206
Content-Type: application/json
Date: Sun, 12 Apr 2020 13:40:26 GMT
Server: WSGIServer/0.2 CPython/3.7.3
Vary: Accept
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

{
    "extra_data": null,
    "network": "2a0a:e5c1:203::",
    "order": null,
    "owner": 30,
    "status": "PENDING",
    "uuid": "8f977a8f-e06a-4346-94ae-8f525df58b7b",
    "wireguard_public_key": "JvCuUTZHm9unasJkGsLKN0Bf/hu6ZSIv7dnIGPyJ6xA="
}
2020-04-12 15:40:39 +02:00
Nico Schottelius b55254b9b1 Merge branch 'master' of code.ungleich.ch:uncloud/uncloud 2020-04-12 14:28:39 +02:00
Nico Schottelius bc033a9087 Merge branch 'master' of code.ungleich.ch:uncloud/uncloud 2020-04-12 11:36:51 +02:00
Nico Schottelius 05f38d157e add a discounter function to the product model 2020-04-12 11:35:37 +02:00
Nico Schottelius bab59b1879 Merge branch 'master' of code.ungleich.ch:uncloud/uncloud 2020-04-11 21:37:50 +02:00
Nico Schottelius 50b8b7a5f6 in-between commit 2020-04-11 21:37:36 +02:00
nico14571 f1bba63f6f Merge branch 'yearly-billing' into 'master'
Implement yearly billing, general billing tests

See merge request uncloud/uncloud!5
2020-04-11 21:37:13 +02:00
fnux 276c7e9901 Set VM order starting date on creation 2020-04-09 14:52:56 +02:00
fnux 3a37343a73 Set default value for vpnnetworkreservation, rebuild migrations 2020-04-09 14:28:46 +02:00
fnux 3588ae88f9 Merge branch 'master' into HEAD 2020-04-09 14:10:01 +02:00
Nico Schottelius 9431f11284 ++notes 2020-04-09 12:09:38 +02:00
Nico Schottelius e64c2b8ddb Merge branch 'master' of code.ungleich.ch:uncloud/uncloud 2020-04-09 12:08:17 +02:00
Nico Schottelius d9473e8f33 ++ doc 2020-04-09 12:08:11 +02:00
fnux cb3346303b Fix typo in migration dependencies for uncloud_pay 2020-04-09 12:06:05 +02:00
Nico Schottelius 7d892daff9 [db] stay on psql+socket 2020-04-09 11:59:49 +02:00
Nico Schottelius 08b9886ce3 Remove sample secret key in secrets_sample
No need to worry, this was just a testing key
2020-04-09 11:59:25 +02:00
nico14571 d6bdf5c991 Merge branch 'sync-old-meowpay-patches' into 'master'
Revamped payment pipeline (imported from nico/mew-cloud)

See merge request uncloud/uncloud!3
2020-04-09 11:31:49 +02:00
fnux cc7056c87c Remove old Stripe settings from secrets_sample.py 2020-04-08 17:55:48 +02:00
fnux a8b81b074b Remove user view from uncloud_pay 2020-04-08 17:40:44 +02:00
fnux 89c705f7d2 Set one payment method as primary, allow updates 2020-04-08 17:22:53 +02:00
fnux f2a797874a Merge branch 'master' into stripe-js 2020-04-08 17:08:09 +02:00
Nico Schottelius d3f2a3e071 in between commit
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-04-08 16:24:39 +02:00
Nico Schottelius 3d2f8574d3 [db] use tcp -> support ssh 2020-04-08 13:09:17 +02:00
Nico Schottelius 1838eaf7dd Merge branch 'master' of code.ungleich.ch:uncloud/uncloud 2020-04-08 12:07:11 +02:00
Nico Schottelius 8986835c7e Add readme for postgresql support 2020-04-08 12:03:18 +02:00
Nico Schottelius 938f0a3390 update to work on different computer
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-04-07 19:45:16 +02:00
Nico Schottelius 913e992a48 [vpn] fix urls 2020-04-06 22:30:01 +02:00
Nico Schottelius 096f7e05c0 [migration] new models for uncloud_net 2020-04-06 22:29:41 +02:00
Nico Schottelius 06c4a5643c [doc] move readme to subdir 2020-04-06 22:08:29 +02:00
Nico Schottelius 198aaea48a Remove unused ldaptest 2020-04-06 22:06:48 +02:00
Nico Schottelius d537e9e2f0 [doc] add new readme's 2020-04-06 22:06:34 +02:00
Nico Schottelius 5d084a5716 phase in vpn
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-04-03 19:27:49 +02:00
Nico Schottelius 8fb3ad7fe8 inline all pictures 2020-04-03 18:51:09 +02:00
Nico Schottelius c44faa7a73 Begin to include bill output 2020-04-03 18:41:17 +02:00
Nico Schottelius fa0ca2d9c1 Merge remote-tracking branch 'meowpaylocal/master' 2020-04-02 19:31:57 +02:00
Nico Schottelius 7a6c8739f6 Rename / prepare for merge with uncloud repo 2020-04-02 19:31:03 +02:00
Nico Schottelius 833d570472 sync .gitignore 2020-04-02 19:30:47 +02:00
Nico Schottelius 3cf3439f1c Move all files to _etc_based 2020-04-02 19:29:08 +02:00
Nico Schottelius 23203ff418 vmsnapshot progress 2020-03-22 20:55:11 +01:00
Nico Schottelius 9961ca0446 add new migrations
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-03-22 18:59:59 +01:00
Nico Schottelius 105142f76a Add template for creating VMs 2020-03-22 18:52:31 +01:00
Nico Schottelius 08fe3e689e Add debug to opennebula, create VM disks from opennebula correctly 2020-03-22 17:30:55 +01:00
Nico Schottelius 10c5257f90 Introduce "extra_data" jsonfield 2020-03-21 11:59:04 +01:00
Nico Schottelius a32f7522b5 Relate VM to disks and snapshots 2020-03-18 15:43:01 +01:00
Nico Schottelius 4b4cbbf009 Also list snapshots for a VM 2020-03-18 15:19:06 +01:00
Nico Schottelius c6a9bd4363 Make balance a user attribute + decimalfield 2020-03-18 14:53:26 +01:00
Nico Schottelius cd01f62fde Move user view to uncloud_auth 2020-03-18 14:36:40 +01:00
Nico Schottelius 2f1aee8181 Can create a VMSnapshot w/ order (bugs to be removed) 2020-03-17 19:53:14 +01:00
Nico Schottelius 6a382fab23 [vmhost] add used_ram_in_gb 2020-03-17 19:07:00 +01:00
Nico Schottelius b9473c1803 ++ fix opennebula migration 2020-03-17 16:03:41 +01:00
Nico Schottelius cc2efa5c14 Remove old opennebula view, remove vmid field 2020-03-17 15:40:08 +01:00
Nico Schottelius 5d840de55c [opennebula] refresh formula, cleanup vm import/migration to uncloud 2020-03-17 15:39:24 +01:00
Nico Schottelius 9f4b927c74 Introduce mirations to ungleich_service to make tests work 2020-03-17 14:50:28 +01:00
Nico Schottelius 55bd42fe64 List all VMs for admins 2020-03-17 14:50:14 +01:00
Nico Schottelius 8634d667d5 update requirements for graphing 2020-03-17 14:49:59 +01:00
Nico Schottelius ac7ea86668 rename opennebula commands 2020-03-17 14:49:49 +01:00
Nico Schottelius 8356404fe4 ++ product readme 2020-03-17 14:49:36 +01:00
Nico Schottelius 723d2a99cc add django…extensions to support "graph_models" 2020-03-17 13:30:48 +01:00
Nico Schottelius 8f4e7cca1b add migrations to ungleich_service so tests don't fail
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-03-17 12:46:02 +01:00
ahmadbilalkhalid b15a12dc71 Missing import for DCLVMProductSerializer 2020-03-13 14:22:49 +05:00
fnux 923102af24 Fix DCLVMProductSerializer import following rebase 2020-03-09 13:17:40 +01:00
fnux f4ebbb79ce Add test coverage parsing to CI 2020-03-09 12:25:12 +01:00
fnux 122bc586b4 Disable 'old' broken/not-yet-implemented ungleich_vm tests
To make CI happy on merge requests from now on :-)
2020-03-09 12:25:12 +01:00
fnux ae6548e168 Read database host and user from environment (used by CI pipeline) 2020-03-09 12:25:12 +01:00
fnux 0e4068cea8 Add minimal CI running django tests 2020-03-09 12:25:12 +01:00
fnux 623d3ae5c4 Fix various billing issues discovered by testing 2020-03-09 12:25:12 +01:00
fnux fe0e6d98bf Add simple tests for billing 2020-03-09 12:25:11 +01:00
fnux 41e35c1af0 Add missing migration and dependency to run tests 2020-03-09 12:25:11 +01:00
fnux 948391ab2e Dump test placeholder for uncloud_pay 2020-03-09 12:25:11 +01:00
fnux d089d06264 Initial yearly billing implementation 2020-03-09 12:25:11 +01:00
fnux c086dbd357 Rebuild paymentmethod/stripe migrations from master 2020-03-09 12:25:11 +01:00
fnux 545727afe7 Move STRIPE_PUBLIC_KEY to secrets (i.e. local configuration) 2020-03-09 12:25:11 +01:00
fnux 7bbc729b87 Fix duplicates in payment method creation 2020-03-09 12:25:11 +01:00
fnux b10cae472e Fix migration dependencies after rebase 2020-03-09 12:25:11 +01:00
fnux 952cf8fd13 Remove unused empty migration 2020-03-09 12:25:11 +01:00
fnux 7e278228bd Fix payment update updates 2020-03-09 12:25:11 +01:00
fnux 31507c0f1a Fix error in stripe get_customer_id_for 2020-03-09 12:25:11 +01:00
fnux 7e58a8ace2 Fix generate-bills, remove debug print in charge method 2020-03-09 12:25:11 +01:00
fnux a4fa0def4b Fix dumb logic errors/typo from last commit 2020-03-09 12:25:11 +01:00
fnux 80fe28588e Revamp stripe error handling 2020-03-09 12:25:11 +01:00
fnux 4e658d2d77 Remove legacy credit card support 2020-03-09 12:25:11 +01:00
fnux 5161a74354 Add STRIPE_PUBLIC_KEY setting 2020-03-09 12:25:11 +01:00
fnux 0e62ccff3b Cleanup/reorder uncloud_pay views 2020-03-09 12:25:11 +01:00
fnux bf83b750de Replace legacy Stripe Charge API by Payment{setup, intent} 2020-03-09 12:24:14 +01:00
Nico Schottelius 47148454f6 s/_/-/ for bill id 2020-03-06 11:11:16 +01:00
Nico Schottelius 0032c272e7 Merge branch 'bill-id' of code.ungleich.ch:nico/meow-pay 2020-03-06 11:10:47 +01:00
Nico Schottelius 263125048d Begin to introduce a DCL alike view for VMs 2020-03-06 11:10:20 +01:00
fnux 658262c599 Add human readable reference to bills 2020-03-06 09:39:41 +01:00
fnux c41b55573a Fix order link in BillRecordSerializer 2020-03-06 09:32:25 +01:00
fnux 4016c28c5f Fix generate-bills 2020-03-06 09:17:31 +01:00
Nico Schottelius aa8336b7e4 VM: def __str__ 2020-03-05 23:55:33 +01:00
Nico Schottelius 0e6a6afd88 [opennebula] Fix fields/serializers 2020-03-05 23:18:07 +01:00
Nico Schottelius efbe1c0596 Merge commands into the "vm" command 2020-03-05 17:52:01 +01:00
fnux 6c7f0e98b3 Rebuild paymentmethod/stripe migrations from master 2020-03-05 16:24:45 +01:00
Nico Schottelius b8c2f80e45 [vmhost] add available_ram_in_gb and available_cores 2020-03-05 15:06:34 +01:00
Nico Schottelius 139aca6a61 Remove vms field from vmhost
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-03-05 14:58:45 +01:00
Nico Schottelius 2a73f0e767 [migration] make vm name optional, use storage class choices
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-03-05 14:22:56 +01:00
Nico Schottelius 66e224e926 [storage] move choices to uncloud_storage 2020-03-05 14:21:10 +01:00
Nico Schottelius 10f09c7115 add an old client hack (just for reference) 2020-03-05 14:15:33 +01:00
Nico Schottelius ec7a2a3c3a Correct pricing for VMProduct 2020-03-05 14:00:14 +01:00
fnux b07df26eb2 Move STRIPE_PUBLIC_KEY to secrets (i.e. local configuration) 2020-03-05 11:51:08 +01:00
fnux b958cc77ea Fix duplicates in payment method creation 2020-03-05 11:45:37 +01:00
fnux e9b6a6f277 Fix migration dependencies after rebase 2020-03-05 11:43:07 +01:00
fnux 546667d117 Remove unused empty migration 2020-03-05 11:39:48 +01:00
fnux b88dfa4bfe Fix payment update updates 2020-03-05 11:39:48 +01:00
fnux d6ee806467 Fix error in stripe get_customer_id_for 2020-03-05 11:39:48 +01:00
fnux a41184d83d Fix generate-bills, remove debug print in charge method 2020-03-05 11:39:48 +01:00
fnux 2f70418f4d Fix dumb logic errors/typo from last commit 2020-03-05 11:39:48 +01:00
fnux 21e1a3d220 Revamp stripe error handling 2020-03-05 11:39:48 +01:00
fnux 4cc19e1e6e Remove legacy credit card support 2020-03-05 11:39:48 +01:00
fnux 08bf7cd320 Add STRIPE_PUBLIC_KEY setting 2020-03-05 11:39:48 +01:00
fnux 7e9f2ea561 Cleanup/reorder uncloud_pay views 2020-03-05 11:39:48 +01:00
fnux 929211162d Replace legacy Stripe Charge API by Payment{setup, intent} 2020-03-05 11:39:48 +01:00
Nico Schottelius cf17373b3f Fix ahmed introduced migrations
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-03-05 11:35:00 +01:00
Nico Schottelius 14a4fa8cc1 Merge remote-tracking branch 'ahmed/migrate-one-to-regular-vm' 2020-03-05 11:34:15 +01:00
Nico Schottelius 4fc1c36ae9 fix incorrect migrations from fnux-stable branch
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-03-05 11:17:30 +01:00
fnux 371c5ccf00 Merge remote-tracking branch 'origin/fnux-hacks' 2020-03-04 12:12:22 +01:00
fnux faca104459 Fix stripe import in uncloud_pay.models 2020-03-04 11:05:21 +01:00
fnux 9e8149135b Move bill generation logic to Bill class, initial work for prepaid 2020-03-04 10:55:12 +01:00
ahmadbilalkhalid 02b287eff8 small cleaning 2020-03-04 14:44:41 +05:00
fnux 9aabc66e57 Pay: move some model-related methods from helpers to models
Otherwise we end up in circular dependency hell
2020-03-04 09:39:18 +01:00
ahmadbilalkhalid a662b1fe29 Make migrate-one-vm-to-regular command idempotent 2020-03-04 13:25:46 +05:00
ahmadbilalkhalid 88c10e2e4a improve readability 2020-03-03 23:53:45 +05:00
ahmadbilalkhalid fea0568bb9 init commit 2020-03-03 23:46:39 +05:00
fnux e0cb6ac670 Allow for charging customers 2020-03-03 18:16:25 +01:00
Nico Schottelius ebc9238845 recreate all migrations
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-03-03 17:50:52 +01:00
fnux 94a39ed81d Properly wire stripe card to payment methods 2020-03-03 16:56:42 +01:00
fnux 5c2d2a5b94 Document relations for Orders and Managed Services 2020-03-03 13:14:51 +01:00
Nico Schottelius e9ef2acb06 Add readme for objects 2020-03-03 12:15:05 +01:00
fnux 3846e49395 Fix migration issue introduced in previous merge 2020-03-03 11:40:37 +01:00
fnux a849e642dd Merge remote-tracking branch 'origin/master' into fnux-hacks 2020-03-03 11:36:08 +01:00
fnux 28407bf3e3 Quickly document OrderRecord class 2020-03-03 11:34:47 +01:00
Nico Schottelius ea00e81b1e Move all stripe stuff to stripe.py 2020-03-03 11:31:32 +01:00
fnux 53baf0d9f3 Fix typo in BillRecord 2020-03-03 11:29:57 +01:00
fnux 11e22f5001 Consistently use one_time_price instead of setup_fee 2020-03-03 11:27:35 +01:00
Nico Schottelius e176ad0817 Remove second stripe key definition 2020-03-03 11:26:16 +01:00
Nico Schottelius a50095f873 Merge remote-tracking branch 'origin/fnux-stable'
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-03-03 11:23:29 +01:00
fnux a40da40169 Add recurring_count to bills 2020-03-03 11:15:48 +01:00
fnux 2eaaad49db Handle setup fee in bills 2020-03-03 10:59:21 +01:00
fnux 9fdf66ed74 Fix MatrixService ordering 2020-03-03 10:51:16 +01:00
fnux b31aa72f84 Allow to select billing period when registering VM 2020-03-03 10:14:56 +01:00
fnux 5559d600c7 Move things around for readability in uncloud_pay models and serializer 2020-03-03 09:13:04 +01:00
fnux 4e51670a90 Expand recurring period billing logic for DD/MM/hh/month 2020-03-03 08:53:19 +01:00
fnux 4ad737ed90 Initial stripe playground 2020-03-02 22:29:50 +01:00
fnux c651c4ddaa Cleanup a bit BillRecord 2020-03-02 16:41:49 +01:00
ahmadbilalkhalid 531bfa1768 actual thing name is replaced by pseudo names 2020-03-02 19:20:12 +05:00
ahmadbilalkhalid 750d8c8cbf Use fictional hostname for VMHost 2020-03-02 17:42:54 +05:00
ahmadbilalkhalid 0c3e6d10ae Indentation/Spacing fixes 2020-03-02 17:20:30 +05:00
ahmadbilalkhalid afdba3d7d9 Remove duplicate code 2020-03-02 17:17:30 +05:00
ahmadbilalkhalid 52e74c22cc Merge branch 'nico/meow-pay-master' into HEAD 2020-03-02 17:05:23 +05:00
ahmadbilalkhalid 6c9c63e0da Add sample clean() for model + Add tests for uncloud_vm 2020-03-02 16:54:36 +05:00
fnux 9e9018060e Wire order records to bills, fix user balance 2020-03-02 10:46:04 +01:00
fnux 9e253d497b Wrap VM creation in database transaction 2020-03-02 09:30:51 +01:00
fnux 81bd54116a Add records to orders 2020-03-02 09:25:03 +01:00
fnux 8e41b894c0 Add OrderRecord model 2020-03-02 08:09:42 +01:00
Ahmed Bilal 3228b91038 Merge branch 'master' into 'master'
Merge nico/meow-pay into ahmedbilal/meow-pay

See merge request ahmedbilal/meow-pay!7
2020-03-02 07:17:04 +01:00
Ahmed Bilal 028fd6789f ++cleanup
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-03-02 07:17:04 +01:00
fnux 4f25086a63 Only generate bill if no overlap 2020-03-01 15:47:27 +01:00
fnux be2b0a8855 Fix a few errors on preview billing rework
Another WIP commit to sync with laptop, do not forget to rebase!
2020-03-01 12:23:04 +01:00
Nico Schottelius 4115eed2a8 +migration
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-29 17:58:10 +01:00
Nico Schottelius 5c33bc5c02 support creating disks
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-29 17:57:57 +01:00
Nico Schottelius 6a38e4e0a4 add url for importing disk image
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-29 17:00:13 +01:00
Nico Schottelius bcbd6f6f83 Introduce disk->image relationship
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-29 16:45:52 +01:00
fnux e319d1d151 WIP revamped bill logic 2020-02-29 09:08:55 +01:00
fnux af1265003e Define custom fields and serializer for MatrixServiceProduct 2020-02-28 16:26:45 +01:00
fnux eaa483e018 Commit forgottem uncloud_vm migrations 2020-02-28 15:08:45 +01:00
fnux 181005ad6c Cleanup VMProduct serializer, add name field to VMProduct 2020-02-28 15:08:00 +01:00
fnux b3bbfafa04 Introduce custom ProductViewSet preventing customer from updating
products
2020-02-28 14:57:45 +01:00
fnux 33cc2b2111 Add uncloud_storage template app 2020-02-28 14:48:01 +01:00
fnux 3b87a47430 Add initial ungleich_service app with MatrixServiceProduct shell 2020-02-28 14:46:33 +01:00
fnux b5a242f176 Merge branch 'master' into fnux-hacks 2020-02-28 14:06:29 +01:00
fnux 1cb1de4876 Add (broken) charge method to payment method endpoint 2020-02-28 11:10:31 +01:00
fnux c0512e54b0 Add handle-overdue-bills 2020-02-28 10:18:24 +01:00
fnux e12575e1de Commit forgotten migration on Orders (Float->Decimal) 2020-02-28 09:59:13 +01:00
fnux adb57c55ca Revamp generate-bills logic to avoid overlapping 2020-02-28 09:58:01 +01:00
Nico Schottelius 89215e47b6 phase in mac 2020-02-28 09:34:29 +01:00
fnux 37ed126bc1 Create payment on strip charging 2020-02-28 09:26:18 +01:00
fnux 4bed53c8a8 Wire charge-negative-balance to payment methods 2020-02-28 09:10:36 +01:00
fnux 059791e2f2 Add initial generate-bills and charge-negative-balance uncloud-pay
commands
2020-02-28 08:59:32 +01:00
fnux ef5e7e8035 Quickly wire vm creation to orders 2020-02-28 07:26:34 +01:00
fnux b1649a6228 Remove product resolution from /order endpoint 2020-02-28 07:25:56 +01:00
fnux 0e28e50bac Revert "Commit WIP changes for /order, if needed at any point"
This reverts commit 83794a1781a1b84506100b39a6997882c654b4f3.
2020-02-28 07:25:18 +01:00
fnux 38d3a3a5d3 Commit WIP changes for /order, if needed at any point 2020-02-28 07:25:18 +01:00
fnux 809a55e1dd Wire VMProduct creation to order 2020-02-28 07:25:18 +01:00
fnux b2fe5014d8 Make recurring_period an Enum, VMProduct a Product, initial wire for
order
2020-02-28 07:25:05 +01:00
fnux 1dd3324275 Wiring initial user balance 2020-02-28 07:24:51 +01:00
fnux 36fcff5149 Add initial structure for payment methods 2020-02-28 07:24:51 +01:00
Nico Schottelius 288a65f219 ++update
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-27 15:29:15 +01:00
Nico Schottelius bd6008462d add template for uncloud_net
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-27 15:29:05 +01:00
Nico Schottelius 70a4fe4d90 order: serialize all fields 2020-02-27 12:45:54 +01:00
Nico Schottelius e89fb45f9c Merge remote-tracking branch 'origin/fnux-hacks' 2020-02-27 12:43:24 +01:00
fnux 1ff5702ce3 Expose Order model 2020-02-27 12:42:24 +01:00
Nico Schottelius b722f30ea5 ++doc 2020-02-27 12:42:09 +01:00
fnux f5eadd6ddb Move user view to uncloud_pay 2020-02-27 12:38:04 +01:00
Nico Schottelius 1445acb77a Merge remote-tracking branch 'origin/fnux-hacks' 2020-02-27 12:36:40 +01:00
Nico Schottelius 7bf4f2adb2 --debug
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-27 12:36:33 +01:00
Nico Schottelius a9aac39486 Create a vmsnapshot + associated order 2020-02-27 12:31:20 +01:00
fnux 225f20c91b Fix typo in payment source model 2020-02-27 12:21:52 +01:00
fnux b9b605f407 Add ADMIN endpoints for bills and payments 2020-02-27 12:21:25 +01:00
fnux f358acca05 Fix payment creation 2020-02-27 12:11:13 +01:00
Nico Schottelius 41a5eae879 cleanup views/vmsnapshot 2020-02-27 12:09:29 +01:00
Nico Schottelius 657dfc541e Merge remote-tracking branch 'origin/fnux-hacks'
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-27 12:06:02 +01:00
Nico Schottelius fd648ade65 ++cleanup
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-27 12:02:41 +01:00
fnux 06ab21c577 Fix python errors on latest hack commits => make runserver happy again 2020-02-27 11:59:28 +01:00
Nico Schottelius 11d629bb51 [uncloud_api] completely remove it 2020-02-27 11:42:42 +01:00
Nico Schottelius aa59b05a2d cleanup urls 2020-02-27 11:40:36 +01:00
Nico Schottelius a58a361254 Move snapshot to _pay and _vm 2020-02-27 11:36:50 +01:00
Nico Schottelius 1ca247148c [uncloud_pay] add "prototype" 2020-02-27 11:21:38 +01:00
ahmadbilalkhalid 033b1e846a Merge branch 'nico/meow-pay-master' into HEAD 2020-02-27 14:35:52 +05:00
Nico Schottelius c0bf4d96c4 ++ debian/devuan notes 2020-02-26 21:13:30 +01:00
Ahmed Bilal 0a6fa031d3 Merge branch 'master' into 'master'
Merge nico/meow-pay into ahmedbilal/meow-pay

See merge request ahmedbilal/meow-pay!5
2020-02-26 11:31:18 +01:00
Ahmed Bilal 0c7ca1147a fix migrations the ugly way
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-26 11:31:17 +01:00
Nico Schottelius df851bee08 Merge branch 'master' of code.ungleich.ch:nico/meow-pay 2020-02-26 11:16:46 +01:00
Nico Schottelius 0b60765e2b in between commit 2020-02-26 11:16:42 +01:00
Nico Schottelius bd3d21faa9 add thoughts for health checking 2020-02-25 22:04:04 +01:00
Nico Schottelius c7ded96658 vmhosts, restructure urls, etc. 2020-02-25 22:01:55 +01:00
Nico Schottelius d4b170f813 phase in vmhost
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-25 20:53:12 +01:00
Nico Schottelius 446c13b77c fix/simplify syncvm 2020-02-25 19:23:39 +01:00
Nico Schottelius defe36bfb6 Merge branch 'master' of code.ungleich.ch:nico/meow-pay 2020-02-25 18:28:19 +01:00
Nico Schottelius e790063d5a Merge remote-tracking branch 'ahmed/master' into ahmed_merge
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-25 18:19:55 +01:00
Nico Schottelius cc3d2f2d42 in-between-commit
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-25 18:15:22 +01:00
ahmadbilalkhalid d658b9635d Replace (vmid,uuid) with id in VM model + Add last_host and graphics in VM model + Fixed retrieve view in uncloud.opennebula 2020-02-25 21:03:20 +05:00
Nico Schottelius 7d1c8df84d ++ postgres requirement 2020-02-25 14:20:03 +01:00
Nico Schottelius cc9e5905eb update
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-25 14:12:23 +01:00
ahmadbilalkhalid c7252cde53 Introduced local settings in meow-pay/uncloud django app 2020-02-25 13:09:54 +05:00
ahmadbilalkhalid a72bc142a6 Fixed issues in opennebula/views.py + syncvm now behaves correctly and print users which are not in ldap as per their email address 2020-02-25 11:50:49 +05:00
ahmadbilalkhalid 739bd72526 Migration fixed + opennebula/views.py fixed 2020-02-23 23:00:42 +05:00
Nico Schottelius 15b0fe3dc9 fix migrations the ugly way
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-23 18:11:14 +01:00
Nico Schottelius 50df7050d6 vmhost: add status field 2020-02-23 17:46:30 +01:00
Nico Schottelius 734c406245 Extend uncloud VM models 2020-02-23 17:43:06 +01:00
ahmadbilalkhalid b3e505d37c migration fix 2020-02-23 21:34:22 +05:00
Ahmed Bilal 98f84f1cf5 Merge branch 'master' into 'master'
Merge nico/meow-pay into ahmedbilal/meow-pay

See merge request ahmedbilal/meow-pay!4
2020-02-23 17:23:57 +01:00
Nico Schottelius 8c6e4eee00 -- merge conflict 2020-02-23 17:20:28 +01:00
Nico Schottelius 2900844f63 Merge remote-tracking branch 'ahmed/master' 2020-02-23 17:17:08 +01:00
Nico Schottelius 46921c43ad update ldap, update syncvm 2020-02-23 17:11:05 +01:00
ahmadbilalkhalid fa4d7a1d70 opennebula_hacks added i.e create one user and chown of vm 2020-02-23 21:00:18 +05:00
Nico Schottelius edbfb7964e [ldap] bind with admin to get attributes 2020-02-23 16:52:30 +01:00
ahmadbilalkhalid e4f2f446f5 Merge branch 'nico/meow-pay-master' into HEAD 2020-02-23 20:25:35 +05:00
Nico Schottelius e2b5b5d102 opennebula -> router 2020-02-23 15:33:26 +01:00
ahmadbilalkhalid 7b09f0a373 abk-hacks added 2020-02-23 19:18:51 +05:00
Nico Schottelius cee45b5227 -typo 2020-02-23 15:09:58 +01:00
Nico Schottelius 94633d6cc8 move uncloud a layer up
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-23 14:07:37 +01:00
Nico Schottelius 1d1ae6fb3e Force uniqueness on uuid 2020-02-23 11:59:09 +01:00
Nico Schottelius f8c29aa1d6 add uuid() to opennebula VM 2020-02-23 11:55:57 +01:00
Nico Schottelius fc4ec7b0f8 update readme + api 2020-02-23 11:42:15 +01:00
Nico Schottelius f588691f0d [opennebula] add json, add helper functions 2020-02-23 11:42:03 +01:00
Nico Schottelius 581865460b Mess with migrations 2020-02-23 11:41:51 +01:00
Nico Schottelius 7f821b4d5a add readme 2020-02-23 10:31:28 +01:00
Nico Schottelius ce0da4b827 + bracket 2020-02-23 09:44:55 +01:00
Nico Schottelius de06b9ee22 Merge branch 'master' of code.ungleich.ch:nico/meow-pay 2020-02-23 09:18:27 +01:00
Nico Schottelius 26449d3159 ++snapshot ideas 2020-02-23 09:18:16 +01:00
ahmadbilalkhalid 71a764ce1e Move vm/{detail,list} under opennebula/vm/{detail,list} and make it admin accessible only + Created vm/list that list currently authenticated user's VMs 2020-02-22 15:49:00 +05:00
ahmadbilalkhalid 5f28e9630c Remove unneccessary requirements from {repo_root}/requirements.txt + uncloud/secret_sample.py minor changes 2020-02-22 11:36:18 +05:00
Ahmed Bilal 2aa22803f4 Merge branch 'master' into 'master'
Merge nico/meow-pay into ahmedbilal/meow-pay

See merge request ahmedbilal/meow-pay!2
2020-02-22 07:32:52 +01:00
Ahmed Bilal dc34c0ecd4 Merge nico/meow-pay into ahmedbilal/meow-pay 2020-02-22 07:32:52 +01:00
Nico Schottelius 4f4a4be839 good night commit - introducing status 2020-02-22 00:50:06 +01:00
Nico Schottelius b1bb6bc314 Make products available via getattr 2020-02-22 00:22:42 +01:00
Nico Schottelius b67f41cc35 Merge remote-tracking branch 'ahmed/master' 2020-02-21 22:04:26 +01:00
Nico Schottelius dc5092be71 Add sample secrets 2020-02-21 21:41:51 +01:00
Nico Schottelius 4df7c761d3 ++stuff 2020-02-21 20:51:04 +01:00
ahmadbilalkhalid d61a7e670f opennebula vm sync/query application added 2020-02-21 20:33:37 +05:00
ahmadbilalkhalid 5de973b204 Merge branch 'nico/meow-pay-master' into HEAD 2020-02-21 20:06:21 +05:00
Nico Schottelius 0708a1e1fd add requirements.txt 2020-02-21 15:05:17 +01:00
Nico Schottelius a5695ffa48 two more related user problems
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-21 11:43:17 +01:00
Nico Schottelius 6ba224638a fix migrations / custom user late introduce
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-21 11:42:54 +01:00
Nico Schottelius 2cda4dd57b [auth] add customer user model
Best practice

See

https://docs.djangoproject.com/en/3.0/topics/auth/customizing/#using-a-custom-user-model-when-starting-a-project
2020-02-21 11:32:41 +01:00
Nico Schottelius c456355059 begin to introduce product
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-21 10:41:22 +01:00
Nico Schottelius 118c66799c ++views/permissions 2020-02-20 19:38:43 +01:00
Nico Schottelius f8182e00e8 import secrets 2020-02-20 19:38:30 +01:00
Nico Schottelius 9fd445e947 add ldap support + tutorial example 2020-02-20 18:58:07 +01:00
Nico Schottelius 254429db55 .gitignore & more 2020-02-20 16:55:01 +01:00
Nico Schottelius e472d20ae0 hacking uncloud v202002
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-20 16:52:50 +01:00
Nico Schottelius 315aaded41 Focus on creating a VPN as a first test case 2020-02-20 16:05:58 +01:00
Nico Schottelius 13292db39e +old notes 2020-02-20 11:57:03 +01:00
Nico Schottelius 0a1ccadda2 +ldaptest 2020-02-20 11:56:47 +01:00
Nico Schottelius 8160d01471 Merge remote-tracking branch 'origin/master' 2020-02-20 11:51:14 +01:00
ahmadbilalkhalid 8c353f277c is_order_valid added in helper.py 2020-02-20 15:23:15 +05:00
ahmadbilalkhalid bb18f6b0e9 Flask-RESTful added in requirements.txt 2020-02-20 14:08:39 +05:00
ahmadbilalkhalid 00b35e0567 cleaned requirements.txt 2020-02-20 14:04:53 +05:00
ahmadbilalkhalid 9c7d458eec use code from ungleich-common 2020-02-20 13:57:32 +05:00
Nico Schottelius 074efffaa7 ++ hack 2020-02-20 09:44:30 +01:00
ahmadbilalkhalid cee92f2e99 A lot of code moved to ungleich-common 2020-02-20 00:12:11 +05:00
ahmadbilalkhalid ce709c3b6f Add certificates option for etcd 2020-02-19 14:44:19 +05:00
ahmadbilalkhalid 519279ce6f Update README.md 2020-02-19 13:13:39 +05:00
ahmadbilalkhalid 7b9a970307 Update README.md 2020-02-19 13:12:46 +05:00
ahmadbilalkhalid 5f1f451bc2 Added installation and getting started instructions in README.md 2020-02-19 13:12:07 +05:00
ahmadbilalkhalid e37592bdc6 README.md updated and reorganized, Improved error handling for configparser and ldap manager, requirements.txt added 2020-02-19 11:59:54 +05:00
ahmadbilalkhalid 347843cb24 Sample config file added + uncloud dependency removed 2020-02-19 10:22:15 +05:00
Nico Schottelius aa9548e753 +gitignore
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2020-02-15 11:15:26 +01:00
Nico Schottelius c1f384fb9a so many notes&hacks! 2020-02-15 09:38:33 +01:00
Nico Schottelius b38c9b6060 Ad capability to add and list hosts 2020-02-09 19:27:24 +01:00
ahmadbilalkhalid 200a7672f2 make value_in_json=True 2020-01-27 14:55:26 +05:00
ahmadbilalkhalid 1a76d2b5f3 Many more changes 2020-01-27 13:40:57 +05:00
ahmadbilalkhalid da54a59ca2 initial commit 2020-01-20 12:30:12 +05:00
313 changed files with 15792 additions and 37 deletions

11
.gitignore vendored
View File

@ -1,6 +1,12 @@
.idea
.vscode
.idea/
.vscode/
__pycache__/
pay.conf
log.txt
test.py
STRIPE
venv/
uncloud/docs/build
logs.txt
@ -18,3 +24,4 @@ venv/
dist/
*.iso
*.sqlite3

View File

@ -1,8 +1,18 @@
image: python:3
stages:
- lint
- test
before_script:
- python setup.py install
python_tests:
script:
- python -m unittest -v test/test_mac_local.py
run-tests:
stage: test
image: code.ungleich.ch:5050/uncloud/uncloud/uncloud-ci:latest
services:
- postgres:latest
variables:
DATABASE_HOST: postgres
DATABASE_USER: postgres
POSTGRES_HOST_AUTH_METHOD: trust
coverage: /^TOTAL.+?(\d+\%)$/
script:
- pip install -r requirements.txt
- coverage run --source='.' ./manage.py test
- coverage report

View File

@ -1,3 +1,62 @@
# ucloud
# Uncloud
Checkout https://ungleich.ch/ucloud/ for the documentation of ucloud.
Cloud management platform, the ungleich way.
[![pipeline status](https://code.ungleich.ch/uncloud/uncloud/badges/master/pipeline.svg)](https://code.ungleich.ch/uncloud/uncloud/commits/master)
[![coverage report](https://code.ungleich.ch/uncloud/uncloud/badges/master/coverage.svg)](https://code.ungleich.ch/uncloud/uncloud/commits/master)
## Useful commands
* `./manage.py import-vat-rates path/to/csv`
* `./manage.py make-admin username`
## Development setup
Install system dependencies:
* On Fedora, you will need the following packages: `python3-virtualenv python3-devel openldap-devel gcc chromium`
NOTE: you will need to configure a LDAP server and credentials for authentication. See `uncloud/settings.py`.
```
# Initialize virtualenv.
» virtualenv .venv
Using base prefix '/usr'
New python executable in /home/fnux/Workspace/ungleich/uncloud/uncloud/.venv/bin/python3
Also creating executable in /home/fnux/Workspace/ungleich/uncloud/uncloud/.venv/bin/python
Installing setuptools, pip, wheel...
done.
# Enter virtualenv.
» source .venv/bin/activate
# Install dependencies.
» pip install -r requirements.txt
[...]
# Run migrations.
» ./manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, opennebula, sessions, uncloud_auth, uncloud_net, uncloud_pay, uncloud_service, uncloud_vm
Running migrations:
[...]
# Run webserver.
» ./manage.py runserver
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
May 07, 2020 - 10:17:08
Django version 3.0.6, using settings 'uncloud.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
```
### Note on PGSQL
If you want to use Postgres:
* Install on configure PGSQL on your base system.
* OR use a container! `podman run --rm -p 5432:5432 -e POSTGRES_HOST_AUTH_METHOD=trust -it postgres:latest`

6
archive/issues.org Normal file
View File

@ -0,0 +1,6 @@
* Intro
This file lists issues that should be handled, are small and likely
not yet high prio.
* Issues
** TODO Register prefered address in User model
** TODO Allow to specify different recurring periods

View File

@ -0,0 +1,55 @@
"""
investigate into a simple python function that maps an ldap user to a vat percentage. Basically you need to
lookup the customer address, check if she is a business/registered tax number and if not apply the local
vat
"""
import iso3166
import datetime
from csv import DictReader
def get_vat(street_address, city, postal_code, country, vat_number=None):
vat = {
'Austria': [
{'period': '1984-01-01/', 'rate': 0.2},
{'period': '1976-01-01/1984-01-01', 'rate': 0.18},
{'period': '1973-01-01/1976-01-01', 'rate': 0.16},
]
}
return iso3166.countries.get(country)
# return iso3166.countries_by_name[country]
def main():
# vat = get_vat(
# street_address='82 Nasheman-e-Iqbal near Wapda Town',
# city='Lahore',
# postal_code=53700,
# country='Pakistan',
# )
# print(vat)
vat_rates = {}
with open('vat_rates.csv', newline='') as csvfile:
reader = DictReader(csvfile)
for row in reader:
territory_codes = row['territory_codes'].split('\n')
for code in territory_codes:
if code not in vat_rates:
vat_rates[code] = {}
start_date = row['start_date']
stop_data = row['stop_date']
time_period = f'{start_date}|{stop_data}'
r = row.copy()
del r['start_date']
del r['stop_date']
del r['territory_codes']
vat_rates[code][time_period] = r
print(vat_rates)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,46 @@
import importlib
import sys
import os
from os.path import join as join_path
from xmlrpc.client import ServerProxy as RPCClient
root = os.path.dirname(os.getcwd())
sys.path.append(join_path(root, 'uncloud'))
secrets = importlib.import_module('uncloud.secrets')
class OpenNebula:
def __init__(self, url, session_string):
self.session_string = session_string
self.client = RPCClient(secrets.OPENNEBULA_URL)
def create_user(self, username, password, authentication_driver='', group_id=None):
# https://docs.opennebula.org/5.10/integration/system_interfaces/api.html#one-user-allocate
if group_id is None:
group_id = []
return self.client.one.user.allocate(
self.session_string,
username,
password,
authentication_driver,
group_id
)
def chmod(self, vm_id, user_id=-1, group_id=-1):
# https://docs.opennebula.org/5.10/integration/system_interfaces/api.html#one-vm-chown
return self.client.one.vm.chown(self.session_string, vm_id, user_id, group_id)
one = OpenNebula(secrets.OPENNEBULA_URL, secrets.OPENNEBULA_USER_PASS)
# Create User in OpenNebula
# success, response, *_ = one.create_user(username='meow12345', password='hello_world')
# print(success, response)
# Change owner of a VM
# success, response, *_ = one.chmod(vm_id=25589, user_id=706)
# print(success, response)

View File

@ -0,0 +1,18 @@
#!/bin/sh
dbhost=$1; shift
ssh -L5432:localhost:5432 "$dbhost" &
python manage.py "$@"
# command only needs to be active while manage command is running
# -T no pseudo terminal
# alternatively: commands output shell code
# ssh uncloud@dbhost "python manage.py --hostname xxx ..."

View File

@ -0,0 +1,51 @@
# uncloud-pay
The generic product/payment system.
## Installation
```shell script
pip3 install -r requirements.txt
```
## Getting Started
```shell script
python ucloud_pay.py
```
## Usage
#### 1. Adding of products
```shell script
http --json http://[::]:5000/product/add username=your_username_here password=your_password_here specs:=@ipv6-only-vm.json
```
#### 2. Listing of products
```shell script
http --json http://[::]:5000/product/list
```
#### 3. Registering user's payment method (credit card for now using Stripe)
```shell script
http --json http://[::]:5000/user/register_payment card_number=4111111111111111 cvc=123 expiry_year=2020 expiry_month=8 card_holder_name="The test user" username=your_username_here password=your_password_here line1="your_billing_address" city="your_city" country="your_country"
```
#### 4. Ordering products
First of all, user have to buy the membership first.
```shell script
http --json http://[::]:5000/product/order username=your_username_here password=your_password_here product_id=membership pay=True
```
```shell script
http --json http://[::]:5000/product/order username=your_username_here password=your_password_here product_id=ipv6-only-vm cpu=1 ram=1 os-disk-space=10 os=alpine pay=True
```
#### 5. Listing users orders
```shell script
http --json POST http://[::]:5000/order/list username=your_username_here password=your_password_here
```

View File

@ -0,0 +1,21 @@
import os
from ungleich_common.ldap.ldap_manager import LdapManager
from ungleich_common.std.configparser import StrictConfigParser
from ungleich_common.etcd.etcd_wrapper import EtcdWrapper
config_file = os.environ.get('meow-pay-config-file', default='pay.conf')
config = StrictConfigParser(allow_no_value=True)
config.read(config_file)
etcd_client = EtcdWrapper(
host=config.get('etcd', 'host'), port=config.get('etcd', 'port'),
ca_cert=config.get('etcd', 'ca_cert'), cert_key=config.get('etcd', 'cert_key'),
cert_cert=config.get('etcd', 'cert_cert')
)
ldap_manager = LdapManager(
server=config.get('ldap', 'server'), admin_dn=config.get('ldap', 'admin_dn'),
admin_password=config.get('ldap', 'admin_password')
)

View File

@ -0,0 +1,213 @@
from flask import Flask, request
from flask_restful import Resource, Api
import etcd3
import json
import logging
from functools import wraps
from ldaptest import is_valid_ldap_user
def authenticate(func):
@wraps(func)
def wrapper(*args, **kwargs):
if not getattr(func, 'authenticated', True):
return func(*args, **kwargs)
# pass in username/password !
acct = basic_authentication() # custom account lookup function
if acct:
return func(*args, **kwargs)
flask_restful.abort(401)
return wrapper
def readable_errors(func):
@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except etcd3.exceptions.ConnectionFailedError as e:
raise UncloudException('Cannot connect to etcd: is etcd running and reachable? {}'.format(e))
except etcd3.exceptions.ConnectionTimeoutError as e:
raise UncloudException('etcd connection timeout. {}'.format(e))
return wrapper
class DB(object):
def __init__(self, config, prefix="/"):
self.config = config
# Root for everything
self.base_prefix= '/nicohack'
# Can be set from outside
self.prefix = prefix
self.connect()
@readable_errors
def connect(self):
self._db_clients = []
for endpoint in self.config.etcd_hosts:
client = etcd3.client(host=endpoint, **self.config.etcd_args)
self._db_clients.append(client)
def realkey(self, key):
return "{}{}/{}".format(self.base_prefix,
self.prefix,
key)
@readable_errors
def get(self, key, as_json=False, **kwargs):
value, _ = self._db_clients[0].get(self.realkey(key), **kwargs)
if as_json:
value = json.loads(value)
return value
@readable_errors
def set(self, key, value, as_json=False, **kwargs):
if as_json:
value = json.dumps(value)
# FIXME: iterate over clients in case of failure ?
return self._db_clients[0].put(self.realkey(key), value, **kwargs)
class Membership(Resource):
def __init__(self, config):
self.config = config
def get(self):
data = request.get_json(silent=True) or {}
print("{} {}".format(data, config))
return {'message': 'Order successful' }, 200
def post(self):
data = request.get_json(silent=True) or {}
print("{} {}".format(data, config))
return {'message': 'Order 2x successful' }, 200
class Order(Resource):
def __init__(self, config):
self.config = config
@staticmethod
def post():
data = request.get_json(silent=True) or {}
print("{} {}".format(data, config))
class Product(Resource):
def __init__(self, config):
self.config = config
self.products = []
self.products.append(
{ "name": "membership-free",
"description": """
This membership gives you access to the API and includes a VPN
with 1 IPv6 address.
See https://redmine.ungleich.ch/issues/7747?
""",
"uuid": "a3883466-0012-4d01-80ff-cbf7469957af",
"recurring": True,
"recurring_time_frame": "per_year",
"features": [
{ "name": "membership",
"price_one_time": 0,
"price_recurring": 0
}
]
}
)
self.products.append(
{ "name": "membership-standard",
"description": """
This membership gives you access to the API and includes an IPv6-VPN with
one IPv6 address ("Road warrior")
See https://redmine.ungleich.ch/issues/7747?
""",
"uuid": "1d85296b-0863-4dd6-a543-a6d5a4fbe4a6",
"recurring": True,
"recurring_time_frame": "per_month",
"features": [
{ "name": "membership",
"price_one_time": 0,
"price_recurring": 5
}
]
}
)
self.products.append(
{ "name": "membership-premium",
"description": """
This membership gives you access to the API and includes an
IPv6-VPN with a /48 IPv6 network.
See https://redmine.ungleich.ch/issues/7747?
""",
"uuid": "bfd63fd2-d227-436f-a8b8-600de74dd6ce",
"recurring": True,
"recurring_time_frame": "per_month",
"features": [
{ "name": "membership",
"price_one_time": 0,
"price_recurring": 5
}
]
}
)
self.products.append(
{ "name": "ipv6-vpn-with-/48",
"description": """
An IPv6 VPN with a /48 network included.
""",
"uuid": "fe5753f8-6fe1-4dc4-9b73-7b803de4c597",
"recurring": True,
"recurring_time_frame": "per_year",
"features": [
{ "name": "vpn",
"price_one_time": 0,
"price_recurring": 120
}
]
}
)
@staticmethod
def post():
data = request.get_json(silent=True) or {}
print("{} {}".format(data, config))
def get(self):
data = request.get_json(silent=True) or {}
print("{} {}".format(data, config))
return self.products
if __name__ == '__main__':
app = Flask(__name__)
config = {}
config['etcd_url']="https://etcd1.ungleich.ch"
config['ldap_url']="ldaps://ldap1.ungleich.ch"
api = Api(app)
api.add_resource(Order, '/orders', resource_class_args=( config, ))
api.add_resource(Product, '/products', resource_class_args=( config, ))
api.add_resource(Membership, '/membership', resource_class_args=( config, ))
app.run(host='::', port=5000, debug=True)

View File

@ -0,0 +1,87 @@
import logging
import parsedatetime
from datetime import datetime
from stripe_utils import StripeUtils
def get_plan_id_from_product(product):
plan_id = 'ucloud-v1-'
plan_id += product['name'].strip().replace(' ', '-')
return plan_id
def get_pricing(price_in_chf_cents, product_type, recurring_period):
if product_type == 'recurring':
return 'CHF {}/{}'.format(price_in_chf_cents/100, recurring_period)
elif product_type == 'one-time':
return 'CHF {} (One time charge)'.format(price_in_chf_cents/100)
def get_user_friendly_product(product_dict):
uf_product = {
'name': product_dict['name'],
'description': product_dict['description'],
'product_id': product_dict['usable-id'],
'pricing': get_pricing(
product_dict['price'], product_dict['type'], product_dict['recurring_period']
)
}
if product_dict['type'] == 'recurring':
uf_product['minimum_subscription_period'] = product_dict['minimum_subscription_period']
return uf_product
def get_token(card_number, cvc, exp_month, exp_year):
stripe_utils = StripeUtils()
token_response = stripe_utils.get_token_from_card(
card_number, cvc, exp_month, exp_year
)
if token_response['response_object']:
return token_response['response_object'].id
else:
return None
def resolve_product(usable_id, etcd_client):
products = etcd_client.get_prefix('/v1/products/', value_in_json=True)
for p in products:
if p.value['usable-id'] == usable_id:
return p.value
return None
def calculate_charges(specification, data):
logging.debug('Calculating charges for specs:{} and data:{}'.format(specification, data))
one_time_charge = 0
recurring_charge = 0
for feature_name, feature_detail in specification['features'].items():
if feature_detail['constant']:
data[feature_name] = 1
if feature_detail['unit']['type'] != 'str':
one_time_charge += feature_detail['one_time_fee']
recurring_charge += (
feature_detail['price_per_unit_per_period'] * data[feature_name] /
feature_detail['unit']['value']
)
return one_time_charge, recurring_charge
def is_order_valid(order_timestamp, renewal_period):
"""
Sample Code Usage
>> current_datetime, status = cal.parse('Now')
>> current_datetime = datetime(*current_datetime[:6])
>> print('Is order valid: ', is_order_valid(current_datetime, '1 month'))
>> True
"""
cal = parsedatetime.Calendar()
renewal_datetime, status = cal.parse(renewal_period)
renewal_datetime = datetime(*renewal_datetime[:6])
return order_timestamp <= renewal_datetime

View File

@ -0,0 +1,28 @@
{
"usable-id": "ipv6-only-django-hosting",
"active": true,
"name": "IPv6 Only Django Hosting",
"description": "Host your Django application on our shiny IPv6 Only VM",
"recurring_period": "month",
"quantity": "inf",
"features": {
"cpu": {
"unit": {"value": 1, "type":"int"},
"price_per_unit_per_period": 3,
"one_time_fee": 0,
"constant": false
},
"ram": {
"unit": {"value": 1, "type":"int"},
"price_per_unit_per_period": 4,
"one_time_fee": 0,
"constant": false
},
"os-disk-space": {
"unit": {"value": 10, "type":"int"},
"one_time_fee": 0,
"price_per_unit_per_period": 3.5,
"constant": false
}
}
}

View File

@ -0,0 +1,34 @@
{
"usable-id": "ipv6-only-vm",
"active": true,
"name": "IPv6 Only VM",
"description": "IPv6 Only VM are accessible to only those having IPv6 for themselves",
"recurring_period": "month",
"quantity": "inf",
"features": {
"cpu": {
"unit": {"value": 1, "type":"int"},
"price_per_unit_per_period": 3,
"one_time_fee": 0,
"constant": false
},
"ram": {
"unit": {"value": 1, "type":"int"},
"price_per_unit_per_period": 4,
"one_time_fee": 0,
"constant": false
},
"os-disk-space": {
"unit": {"value": 10, "type":"int"},
"one_time_fee": 0,
"price_per_unit_per_period": 4,
"constant": false
},
"os": {
"unit": {"value": 1, "type":"str"},
"one_time_fee": 0,
"price_per_unit_per_period": 0,
"constant": false
}
}
}

View File

@ -0,0 +1,16 @@
{
"usable-id": "ipv6-only-vpn",
"active": true,
"name": "IPv6 Only VPN",
"description": "IPv6 VPN enable you to access IPv6 only websites and more",
"recurring_period": "month",
"quantity": "inf",
"features": {
"vpn": {
"unit": {"value": 1, "type": "int"},
"price_per_unit_per_period": 10,
"one_time_fee": 0,
"constant": true
}
}
}

View File

@ -0,0 +1,16 @@
{
"usable-id": "ipv6-box",
"active": true,
"name": "IPv6 Box",
"description": "A ready-to-go IPv6 Box: it creates a VPN to ungleich and distributes IPv6 addresses to all your computers.",
"recurring_period": "eternity",
"quantity": 4,
"features": {
"ipv6-box": {
"unit": {"value": 1, "type":"int"},
"price_per_unit_per_period": 0,
"one_time_fee": 250,
"constant": true
}
}
}

View File

@ -0,0 +1,17 @@
{
"usable-id": "membership",
"active": true,
"name": "Membership",
"description": "Membership to use uncloud-pay",
"recurring_period": "month",
"quantity": "inf",
"features": {
"membership": {
"unit": {"value": 1, "type":"int"},
"price_per_unit_per_period": 5,
"one_time_fee": 0,
"constant": true
}
},
"max_per_user": "1"
}

View File

@ -0,0 +1,7 @@
stripe
flask
Flask-RESTful
git+https://code.ungleich.ch/ahmedbilal/ungleich-common/#egg=ungleich-common-etcd&subdirectory=etcd
git+https://code.ungleich.ch/ahmedbilal/ungleich-common/#egg=ungleich-common-ldap&subdirectory=ldap
git+https://code.ungleich.ch/ahmedbilal/ungleich-common/#egg=ungleich-common-std&subdirectory=std
git+https://code.ungleich.ch/ahmedbilal/ungleich-common/#egg=ungleich-common-schemas&subdirectory=schemas

View File

@ -0,0 +1,17 @@
[etcd]
host = 127.0.0.1
port = 2379
ca_cert
cert_cert
cert_key
[stripe]
private_key=stripe_private_key
[app]
port = 5000
[ldap]
server = ldap_server_url
admin_dn = ldap_admin_dn
admin_password = ldap_admin_password

View File

@ -0,0 +1,136 @@
import logging
import config
import json
import math
from config import ldap_manager, etcd_client
from helper import resolve_product
from ungleich_common.schemas.schemas import BaseSchema, Field, ValidationException
class AddProductSchema(BaseSchema):
def __init__(self, data):
super().__init__()
self.add_schema(UserCredentialSchema, data)
self.specs = Field('specs', dict, **self.get(data, 'specs'))
self.update = Field('update', bool, **self.get(data, 'update', return_default=True, default=False))
def validation(self):
user = self.objects['user']
user = json.loads(user.entry_to_json())
uid, ou, *dc = user['dn'].replace('ou=', '').replace('dc=', '').replace('uid=', '').split(',')
if ou != config.config.get('ldap', 'internal_user_ou', fallback='users'):
raise ValidationException('You do not have access to create product.')
product = resolve_product(self.specs.value['usable-id'], etcd_client)
if product:
self.objects['product'] = product
class AddressSchema(BaseSchema):
def __init__(self, data):
super().__init__()
self.line1 = Field('line1', str, **self.get(data, 'line1'))
self.line2 = Field('line2', str, **self.get(data, 'line2', return_default=True))
self.city = Field('city', str, **self.get(data, 'city'))
self.country = Field('country', str, **self.get(data, 'country'))
self.state = Field('state', str, **self.get(data, 'state', return_default=True))
self.postal_code = Field('postal_code', str, **self.get(data, 'postal_code', return_default=True))
class UserRegisterPaymentSchema(BaseSchema):
def __init__(self, data):
super().__init__()
self.add_schema(UserCredentialSchema, data)
self.add_schema(AddressSchema, data, under_field_name='address')
self.card_number = Field('card_number', str, **self.get(data, 'card_number'))
self.cvc = Field('cvc', str, **self.get(data, 'cvc'))
self.expiry_year = Field('expiry_year', int, **self.get(data, 'expiry_year'))
self.expiry_month = Field('expiry_month', int, **self.get(data, 'expiry_month'))
self.card_holder_name = Field('card_holder_name', str, **self.get(data, 'card_holder_name'))
class UserCredentialSchema(BaseSchema):
def __init__(self, data):
super().__init__()
self.username = Field('username', str, **self.get(data, 'username'))
self.password = Field('password', str, **self.get(data, 'password'))
def validation(self):
try:
entry = ldap_manager.is_password_valid(self.username.value, self.password.value, query_key='uid')
except ValueError:
raise ValidationException('No user with \'{}\' username found. You can create account at '
'https://account.ungleich.ch'.format(self.username.value))
except Exception:
raise ValidationException('Invalid username/password.')
else:
self.objects['user'] = entry
class ProductOrderSchema(BaseSchema):
def __init__(self, data):
super().__init__()
self.product_id = Field(
'product_id', str, **self.get(data, 'product_id'), validators=[self.product_id_validation]
)
self.pay_consent = Field('pay', bool, **self.get(data, 'pay', return_default=True, default=False))
self.add_schema(UserCredentialSchema, data)
def product_id_validation(self):
product = resolve_product(self.product_id.value, etcd_client)
if product:
product['quantity'] = float(product['quantity'])
self.product_id.value = product['uuid']
self.objects['product'] = product
logging.debug('Got product {}'.format(product))
if not product['active']:
raise ValidationException('Product is not active at the moment.')
if product['quantity'] <= 0:
raise ValidationException('Out of stock.')
else:
raise ValidationException('No such product exists.')
def validation(self):
username = self.objects['user'].uid
customer_previous_orders = etcd_client.get_prefix('/v1/user/{}'.format(username), value_in_json=True)
customer_previous_orders = [o.value for o in customer_previous_orders]
membership = next(filter(lambda o: o['product'] == 'membership', customer_previous_orders), None)
if membership is None and self.objects['product']['usable-id'] != 'membership':
raise ValidationException('Please buy membership first to use this facility')
max_quantity_user_can_order = float(self.objects['product'].get('max_per_user', math.inf))
previous_order_of_same_product = [
o for o in customer_previous_orders if o['product'] == self.objects['product']['usable-id']
]
if len(previous_order_of_same_product) >= max_quantity_user_can_order:
raise ValidationException(
'You cannot buy {} more than {} times'.format(
self.objects['product']['name'], int(max_quantity_user_can_order)
)
)
class OrderListSchema(BaseSchema):
def __init__(self, data):
super().__init__()
self.add_schema(UserCredentialSchema, data)
def make_return_message(err, status_code=200):
logging.debug('message: {}'.format(str(err)))
return {'message': str(err)}, status_code
def create_schema(specification, data):
fields = {}
for feature_name, feature_detail in specification['features'].items():
if not feature_detail['constant']:
fields[feature_name] = Field(
feature_name, eval(feature_detail['unit']['type']), **BaseSchema.get(data, feature_name)
)
return type('{}Schema'.format(specification['name']), (BaseSchema,), fields)

View File

@ -0,0 +1,7 @@
import stripe_utils
import os
if __name__ == '__main__':
s = stripe_utils.StripeUtils(os.environ['STRIPE_PRIVATE_KEY'])
print(s.get_stripe_customer_from_email('coder.purple+2002@gmail.com'))

View File

@ -0,0 +1,491 @@
import re
import stripe
import stripe.error
import logging
from config import etcd_client as client, config as config
stripe.api_key = config.get('stripe', 'private_key')
def handle_stripe_error(f):
def handle_problems(*args, **kwargs):
response = {
'paid': False,
'response_object': None,
'error': None
}
common_message = "Currently it's not possible to make payments."
try:
response_object = f(*args, **kwargs)
response = {
'response_object': response_object,
'error': None
}
return response
except stripe.error.CardError as e:
# Since it's a decline, stripe.error.CardError will be caught
body = e.json_body
err = body['error']
response.update({'error': err['message']})
logging.error(str(e))
return response
except stripe.error.RateLimitError:
response.update(
{'error': "Too many requests made to the API too quickly"})
return response
except stripe.error.InvalidRequestError as e:
logging.error(str(e))
response.update({'error': "Invalid parameters"})
return response
except stripe.error.AuthenticationError as e:
# Authentication with Stripe's API failed
# (maybe you changed API keys recently)
logging.error(str(e))
response.update({'error': common_message})
return response
except stripe.error.APIConnectionError as e:
logging.error(str(e))
response.update({'error': common_message})
return response
except stripe.error.StripeError as e:
# maybe send email
logging.error(str(e))
response.update({'error': common_message})
return response
except Exception as e:
# maybe send email
logging.error(str(e))
response.update({'error': common_message})
return response
return handle_problems
class StripeUtils(object):
CURRENCY = 'chf'
INTERVAL = 'month'
SUCCEEDED_STATUS = 'succeeded'
STRIPE_PLAN_ALREADY_EXISTS = 'Plan already exists'
STRIPE_NO_SUCH_PLAN = 'No such plan'
PLAN_EXISTS_ERROR_MSG = 'Plan {} exists already.\nCreating a local StripePlan now.'
PLAN_DOES_NOT_EXIST_ERROR_MSG = 'Plan {} does not exist.'
def __init__(self, private_key):
self.stripe = stripe
stripe.api_key = private_key
@handle_stripe_error
def card_exists(self, customer, cc_number, exp_month, exp_year, cvc):
token_obj = stripe.Token.create(
card={
'number': cc_number,
'exp_month': exp_month,
'exp_year': exp_year,
'cvc': cvc,
},
)
cards = stripe.Customer.list_sources(
customer,
limit=20,
object='card'
)
for card in cards.data:
if (card.fingerprint == token_obj.card.fingerprint and
int(card.exp_month) == int(exp_month) and int(card.exp_year) == int(exp_year)):
return True
return False
@staticmethod
def get_stripe_customer_from_email(email):
customer = stripe.Customer.list(limit=1, email=email)
return customer.data[0] if len(customer.data) == 1 else None
@staticmethod
def update_customer_token(customer, token):
customer.source = token
customer.save()
@handle_stripe_error
def get_token_from_card(self, cc_number, cvc, expiry_month, expiry_year):
token_obj = stripe.Token.create(
card={
'number': cc_number,
'exp_month': expiry_month,
'exp_year': expiry_year,
'cvc': cvc,
},
)
return token_obj
@handle_stripe_error
def associate_customer_card(self, stripe_customer_id, token,
set_as_default=False):
customer = stripe.Customer.retrieve(stripe_customer_id)
card = customer.sources.create(source=token)
if set_as_default:
customer.default_source = card.id
customer.save()
return True
@handle_stripe_error
def dissociate_customer_card(self, stripe_customer_id, card_id):
customer = stripe.Customer.retrieve(stripe_customer_id)
card = customer.sources.retrieve(card_id)
card.delete()
@handle_stripe_error
def update_customer_card(self, customer_id, token):
customer = stripe.Customer.retrieve(customer_id)
current_card_token = customer.default_source
customer.sources.retrieve(current_card_token).delete()
customer.source = token
customer.save()
credit_card_raw_data = customer.sources.data.pop()
new_card_data = {
'last4': credit_card_raw_data.last4,
'brand': credit_card_raw_data.brand
}
return new_card_data
@handle_stripe_error
def get_card_details(self, customer_id):
customer = stripe.Customer.retrieve(customer_id)
credit_card_raw_data = customer.sources.data.pop()
card_details = {
'last4': credit_card_raw_data.last4,
'brand': credit_card_raw_data.brand,
'exp_month': credit_card_raw_data.exp_month,
'exp_year': credit_card_raw_data.exp_year,
'fingerprint': credit_card_raw_data.fingerprint,
'card_id': credit_card_raw_data.id
}
return card_details
@handle_stripe_error
def get_all_invoices(self, customer_id, created_gt):
return_list = []
has_more_invoices = True
starting_after = False
while has_more_invoices:
if starting_after:
invoices = stripe.Invoice.list(
limit=10, customer=customer_id, created={'gt': created_gt},
starting_after=starting_after
)
else:
invoices = stripe.Invoice.list(
limit=10, customer=customer_id, created={'gt': created_gt}
)
has_more_invoices = invoices.has_more
for invoice in invoices.data:
sub_ids = []
for line in invoice.lines.data:
if line.type == 'subscription':
sub_ids.append(line.id)
elif line.type == 'invoiceitem':
sub_ids.append(line.subscription)
else:
sub_ids.append('')
invoice_details = {
'created': invoice.created,
'receipt_number': invoice.receipt_number,
'invoice_number': invoice.number,
'paid_at': invoice.status_transitions.paid_at if invoice.paid else 0,
'period_start': invoice.period_start,
'period_end': invoice.period_end,
'billing_reason': invoice.billing_reason,
'discount': invoice.discount.coupon.amount_off if invoice.discount else 0,
'total': invoice.total,
# to see how many line items we have in this invoice and
# then later check if we have more than 1
'lines_data_count': len(invoice.lines.data) if invoice.lines.data is not None else 0,
'invoice_id': invoice.id,
'lines_meta_data_csv': ','.join(
[line.metadata.VM_ID if hasattr(line.metadata, 'VM_ID') else '' for line in invoice.lines.data]
),
'subscription_ids_csv': ','.join(sub_ids),
'line_items': invoice.lines.data
}
starting_after = invoice.id
return_list.append(invoice_details)
return return_list
@handle_stripe_error
def get_cards_details_from_token(self, token):
stripe_token = stripe.Token.retrieve(token)
card_details = {
'last4': stripe_token.card.last4,
'brand': stripe_token.card.brand,
'exp_month': stripe_token.card.exp_month,
'exp_year': stripe_token.card.exp_year,
'fingerprint': stripe_token.card.fingerprint,
'card_id': stripe_token.card.id
}
return card_details
def check_customer(self, stripe_cus_api_id, user, token):
try:
customer = stripe.Customer.retrieve(stripe_cus_api_id)
except stripe.error.InvalidRequestError:
customer = self.create_customer(token, user.email, user.name)
user.stripecustomer.stripe_id = customer.get(
'response_object').get('id')
user.stripecustomer.save()
if type(customer) is dict:
customer = customer['response_object']
return customer
@handle_stripe_error
def get_customer(self, stripe_api_cus_id):
customer = stripe.Customer.retrieve(stripe_api_cus_id)
# data = customer.get('response_object')
return customer
@handle_stripe_error
def create_customer(self, token, email, name=None, address=None):
if name is None or name.strip() == "":
name = email
customer = self.stripe.Customer.create(
source=token,
description=name,
email=email,
address=address
)
return customer
@handle_stripe_error
def make_charge(self, amount=None, customer=None):
_amount = float(amount)
amount = int(_amount * 100) # stripe amount unit, in cents
charge = self.stripe.Charge.create(
amount=amount, # in cents
currency=self.CURRENCY,
customer=customer
)
return charge
@staticmethod
def _get_all_stripe_plans():
all_stripe_plans = client.get("/v1/stripe_plans")
all_stripe_plans_set = set()
if all_stripe_plans:
all_stripe_plans_obj = all_stripe_plans.value
if all_stripe_plans_obj and len(all_stripe_plans_obj['plans']) > 0:
all_stripe_plans_set = set(all_stripe_plans_obj["plans"])
return all_stripe_plans_set
@staticmethod
def _save_all_stripe_plans(stripe_plans):
client.put("/v1/stripe_plans", {"plans": list(stripe_plans)})
@handle_stripe_error
def get_or_create_stripe_plan(self, product_name, amount, stripe_plan_id,
interval=INTERVAL):
"""
This function checks if a StripePlan with the given
stripe_plan_id already exists. If it exists then the function
returns this object otherwise it creates a new StripePlan and
returns the new object.
:param amount: The amount in CHF cents
:param product_name: The name of the Stripe plan (product) to be created.
:param stripe_plan_id: The id of the Stripe plan to be
created. Use get_stripe_plan_id_string function to
obtain the name of the plan to be created
:param interval: The interval for subscription {month, year}. Defaults
to month if not provided
:return: The StripePlan object if it exists else creates a
Plan object in Stripe and a local StripePlan and
returns it. Returns None in case of Stripe error
"""
_amount = float(amount)
amount = int(_amount * 100) # stripe amount unit, in cents
all_stripe_plans = self._get_all_stripe_plans()
if stripe_plan_id in all_stripe_plans:
logging.debug("{} plan exists in db.".format(stripe_plan_id))
else:
logging.debug(("{} plan DOES NOT exist in db. "
"Creating").format(stripe_plan_id))
try:
plan_obj = self.stripe.Plan.retrieve(id=stripe_plan_id)
logging.debug("{} plan exists in Stripe".format(stripe_plan_id))
all_stripe_plans.add(stripe_plan_id)
except stripe.error.InvalidRequestError as e:
if "No such plan" in str(e):
logging.debug("Plan {} does not exist in Stripe, Creating")
plan_obj = self.stripe.Plan.create(
amount=amount,
product={'name': product_name},
interval=interval,
currency=self.CURRENCY,
id=stripe_plan_id)
logging.debug(self.PLAN_EXISTS_ERROR_MSG.format(stripe_plan_id))
all_stripe_plans.add(stripe_plan_id)
self._save_all_stripe_plans(all_stripe_plans)
return stripe_plan_id
@handle_stripe_error
def delete_stripe_plan(self, stripe_plan_id):
"""
Deletes the Plan in Stripe and also deletes the local db copy
of the plan if it exists
:param stripe_plan_id: The stripe plan id that needs to be
deleted
:return: True if the plan was deleted successfully from
Stripe, False otherwise.
"""
return_value = False
try:
plan = self.stripe.Plan.retrieve(stripe_plan_id)
plan.delete()
return_value = True
all_stripe_plans = self._get_all_stripe_plans()
all_stripe_plans.remove(stripe_plan_id)
self._save_all_stripe_plans(all_stripe_plans)
except stripe.error.InvalidRequestError as e:
if self.STRIPE_NO_SUCH_PLAN in str(e):
logging.debug(
self.PLAN_DOES_NOT_EXIST_ERROR_MSG.format(stripe_plan_id))
return return_value
@handle_stripe_error
def subscribe_customer_to_plan(self, customer, plans, trial_end=None):
"""
Subscribes the given customer to the list of given plans
:param customer: The stripe customer identifier
:param plans: A list of stripe plans.
:param trial_end: An integer representing when the Stripe subscription
is supposed to end
Ref: https://stripe.com/docs/api/python#create_subscription-items
e.g.
plans = [
{
"plan": "dcl-v1-cpu-2-ram-5gb-ssd-10gb",
},
]
:return: The subscription StripeObject
"""
subscription_result = self.stripe.Subscription.create(
customer=customer, items=plans, trial_end=trial_end
)
return subscription_result
@handle_stripe_error
def set_subscription_metadata(self, subscription_id, metadata):
subscription = stripe.Subscription.retrieve(subscription_id)
subscription.metadata = metadata
subscription.save()
@handle_stripe_error
def unsubscribe_customer(self, subscription_id):
"""
Cancels a given subscription
:param subscription_id: The Stripe subscription id string
:return:
"""
sub = stripe.Subscription.retrieve(subscription_id)
return sub.delete()
@handle_stripe_error
def make_payment(self, customer, amount, token):
charge = self.stripe.Charge.create(
amount=amount, # in cents
currency=self.CURRENCY,
customer=customer
)
return charge
@staticmethod
def get_stripe_plan_id(cpu, ram, ssd, version, app='dcl', hdd=None,
price=None):
"""
Returns the Stripe plan id string of the form
`dcl-v1-cpu-2-ram-5gb-ssd-10gb` based on the input parameters
:param cpu: The number of cores
:param ram: The size of the RAM in GB
:param ssd: The size of ssd storage in GB
:param hdd: The size of hdd storage in GB
:param version: The version of the Stripe plans
:param app: The application to which the stripe plan belongs
to. By default it is 'dcl'
:param price: The price for this plan
:return: A string of the form `dcl-v1-cpu-2-ram-5gb-ssd-10gb`
"""
dcl_plan_string = 'cpu-{cpu}-ram-{ram}gb-ssd-{ssd}gb'.format(cpu=cpu,
ram=ram,
ssd=ssd)
if hdd is not None:
dcl_plan_string = '{dcl_plan_string}-hdd-{hdd}gb'.format(
dcl_plan_string=dcl_plan_string, hdd=hdd)
stripe_plan_id_string = '{app}-v{version}-{plan}'.format(
app=app,
version=version,
plan=dcl_plan_string
)
if price is not None:
stripe_plan_id_string_with_price = '{}-{}chf'.format(
stripe_plan_id_string,
round(price, 2)
)
return stripe_plan_id_string_with_price
else:
return stripe_plan_id_string
@staticmethod
def get_vm_config_from_stripe_id(stripe_id):
"""
Given a string like "dcl-v1-cpu-2-ram-5gb-ssd-10gb" return different
configuration params as a dict
:param stripe_id|str
:return: dict
"""
pattern = re.compile(r'^dcl-v(\d+)-cpu-(\d+)-ram-(\d+\.?\d*)gb-ssd-(\d+)gb-?(\d*\.?\d*)(chf)?$')
match_res = pattern.match(stripe_id)
if match_res is not None:
price = None
try:
price = match_res.group(5)
except IndexError:
logging.debug("Did not find price in {}".format(stripe_id))
return {
'version': match_res.group(1),
'cores': match_res.group(2),
'ram': match_res.group(3),
'ssd': match_res.group(4),
'price': price
}
@staticmethod
def get_stripe_plan_name(cpu, memory, disk_size, price):
"""
Returns the Stripe plan name
:return:
"""
return "{cpu} Cores, {memory} GB RAM, {disk_size} GB SSD, " \
"{price} CHF".format(
cpu=cpu,
memory=memory,
disk_size=disk_size,
price=round(price, 2)
)
@handle_stripe_error
def set_subscription_meta_data(self, subscription_id, meta_data):
"""
Adds VM metadata to a subscription
:param subscription_id: Stripe identifier for the subscription
:param meta_data: A dict of meta data to be added
:return:
"""
subscription = stripe.Subscription.retrieve(subscription_id)
subscription.metadata = meta_data
subscription.save()

View File

@ -0,0 +1,338 @@
import logging
from datetime import datetime
from uuid import uuid4
from flask import Flask, request
from flask_restful import Resource, Api
from werkzeug.exceptions import HTTPException
from config import etcd_client as client, config as config
from stripe_utils import StripeUtils
from schemas import (
make_return_message, ValidationException, UserRegisterPaymentSchema,
AddProductSchema, ProductOrderSchema, OrderListSchema, create_schema
)
from helper import get_plan_id_from_product, calculate_charges
class ListProducts(Resource):
@staticmethod
def get():
products = client.get_prefix('/v1/products/')
products = [
product
for product in [p.value for p in products]
if product['active']
]
prod_dict = {}
for p in products:
prod_dict[p['usable-id']] = {
'name': p['name'],
'description': p['description'],
}
logger.debug('Products = {}'.format(prod_dict))
return prod_dict, 200
class AddProduct(Resource):
@staticmethod
def post():
data = request.get_json(silent=True) or {}
try:
logger.debug('Got data: {}'.format(str(data)))
validator = AddProductSchema(data)
validator.is_valid()
except ValidationException as err:
return make_return_message(err, 400)
else:
cleaned_values = validator.get_cleaned_values()
previous_product = cleaned_values.get('product', None)
if previous_product:
if not cleaned_values['update']:
return make_return_message('Product already exists. Pass --update to update the product.')
else:
product_uuid = previous_product.pop('uuid')
else:
product_uuid = uuid4().hex
product_value = cleaned_values['specs']
product_key = '/v1/products/{}'.format(product_uuid)
product_value['uuid'] = product_uuid
logger.debug('Adding product data: {}'.format(str(product_value)))
client.put(product_key, product_value)
if not previous_product:
return make_return_message('Product created.')
else:
return make_return_message('Product updated.')
################################################################################
# Nico-ok-marker
class UserRegisterPayment(Resource):
@staticmethod
def post():
data = request.get_json(silent=True) or {}
try:
logger.debug('Got data: {}'.format(str(data)))
validator = UserRegisterPaymentSchema(data)
validator.is_valid()
except ValidationException as err:
return make_return_message(err, 400)
else:
cleaned_values = validator.get_cleaned_values()
last4 = data['card_number'].strip()[-4:]
stripe_utils = StripeUtils()
# Does customer already exist ?
stripe_customer = stripe_utils.get_stripe_customer_from_email(cleaned_values['user']['mail'])
# Does customer already exist ?
if stripe_customer is not None:
logger.debug('Customer {}-{} exists already'.format(
cleaned_values['username'], cleaned_values['user']['mail'])
)
# Check if the card already exists
ce_response = stripe_utils.card_exists(
stripe_customer.id, cc_number=data['card_number'],
exp_month=int(data['expiry_month']),
exp_year=int(data['expiry_year']),
cvc=data['cvc'])
if ce_response['response_object']:
message = 'The given card ending in {} exists already.'.format(last4)
return make_return_message(message, 400)
elif ce_response['response_object'] is False:
# Associate card with user
logger.debug('Adding card ending in {}'.format(last4))
token_response = stripe_utils.get_token_from_card(
data['card_number'], data['cvc'], data['expiry_month'],
data['expiry_year']
)
if token_response['response_object']:
logger.debug('Token {}'.format(token_response['response_object'].id))
resp = stripe_utils.associate_customer_card(
stripe_customer.id, token_response['response_object'].id
)
if resp['response_object']:
return make_return_message(
'Card ending in {} registered as your payment source'.format(last4)
)
else:
return make_return_message('Error with payment gateway. Contact support', 400)
else:
return make_return_message('Error: {}'.format(ce_response['error']), 400)
else:
# Stripe customer does not exist, create a new one
logger.debug(
'Customer {} does not exist, creating new'.format(cleaned_values['user']['mail'])
)
token_response = stripe_utils.get_token_from_card(
cleaned_values['card_number'], cleaned_values['cvc'],
cleaned_values['expiry_month'], cleaned_values['expiry_year']
)
if token_response['response_object']:
logger.debug('Token {}'.format(token_response['response_object'].id))
# Create stripe customer
stripe_customer_resp = stripe_utils.create_customer(
name=cleaned_values['card_holder_name'],
token=token_response['response_object'].id,
email=cleaned_values['user']['mail'],
address=cleaned_values['address']
)
stripe_customer = stripe_customer_resp['response_object']
if stripe_customer:
logger.debug('Created stripe customer {}'.format(stripe_customer.id))
return make_return_message(
'Card ending in {} registered as your payment source'.format(last4)
)
else:
return make_return_message('Error with card. Contact support', 400)
else:
return make_return_message('Error with payment gateway. Contact support', 400)
class ProductOrder(Resource):
@staticmethod
def post():
data = request.get_json(silent=True) or {}
try:
validator = ProductOrderSchema(data)
validator.is_valid()
except ValidationException as err:
return make_return_message(err, 400)
else:
cleaned_values = validator.get_cleaned_values()
stripe_utils = StripeUtils()
product = cleaned_values['product']
# Check the user has a payment source added
stripe_customer = stripe_utils.get_stripe_customer_from_email(cleaned_values['user']['mail'])
if not stripe_customer or len(stripe_customer.sources) == 0:
return make_return_message('Please register your payment method first.', 400)
try:
product_schema = create_schema(product, data)
product_schema = product_schema()
product_schema.is_valid()
except ValidationException as err:
return make_return_message(err, 400)
else:
transformed_data = product_schema.get_cleaned_values()
logger.debug('Tranformed data: {}'.format(transformed_data))
one_time_charge, recurring_charge = calculate_charges(product, transformed_data)
recurring_charge = int(recurring_charge)
if not cleaned_values['pay']:
return make_return_message(
'You would be charged {} CHF one time and {} CHF every {}. '
'Add --pay to command to order.'.format(
one_time_charge, recurring_charge, product['recurring_period']
)
)
with client.client.lock('product-order') as _:
# Initiate a one-time/subscription based on product type
if recurring_charge > 0:
logger.debug('Product {} is recurring payment'.format(product['name']))
plan_id = get_plan_id_from_product(product)
res = stripe_utils.get_or_create_stripe_plan(
product_name=product['name'],
stripe_plan_id=plan_id, amount=recurring_charge,
interval=product['recurring_period'],
)
if res['response_object']:
logger.debug('Obtained plan {}'.format(plan_id))
subscription_res = stripe_utils.subscribe_customer_to_plan(
stripe_customer.id,
[{'plan': plan_id}]
)
subscription_obj = subscription_res['response_object']
if subscription_obj is None or subscription_obj.status != 'active':
return make_return_message(
'Error subscribing to plan. Detail: {}'.format(subscription_res['error']), 400
)
else:
order_obj = {
'order-id': uuid4().hex,
'ordered-at': datetime.now().isoformat(),
'product': product['usable-id'],
'one-time-price': one_time_charge,
'recurring-price': recurring_charge,
'recurring-period': product['recurring_period']
}
client.put(
'/v1/user/{}/orders/{}'.format(
cleaned_values['username'], order_obj['order-id']
), order_obj
)
product['quantity'] -= 1
client.put('/v1/products/{}'.format(product['uuid']), product)
return {
'message': 'Order Successful.',
**order_obj
}
else:
logger.error('Could not create plan {}'.format(plan_id))
return make_return_message('Something wrong happened. Contact administrator', 400)
elif recurring_charge == 0 and one_time_charge > 0:
logger.debug('Product {} is one-time payment'.format(product['name']))
charge_response = stripe_utils.make_charge(
amount=one_time_charge,
customer=stripe_customer.id
)
stripe_onetime_charge = charge_response.get('response_object')
# Check if the payment was approved
if not stripe_onetime_charge:
msg = charge_response.get('error')
return make_return_message('Error subscribing to plan. Details: {}'.format(msg), 400)
order_obj = {
'order-id': uuid4().hex,
'ordered-at': datetime.now().isoformat(),
'product': product['usable-id'],
'one-time-price': one_time_charge,
}
client.put(
'/v1/user/{}/orders/{}'.format(cleaned_values['username'], order_obj['order-id']),
order_obj
)
product['quantity'] -= 1
client.put('/v1/products/{}'.format(product['uuid']), product)
return {'message': 'Order successful', **order_obj}, 200
class OrderList(Resource):
@staticmethod
def post():
data = request.get_json(silent=True) or {}
try:
validator = OrderListSchema(data)
validator.is_valid()
except ValidationException as err:
return make_return_message(err, 400)
else:
cleaned_values = validator.get_cleaned_values()
orders = client.get_prefix('/v1/user/{}/orders'.format(cleaned_values['username']))
orders_dict = {
order.value['order-id']: {
**order.value
}
for order in orders
}
logger.debug('Orders = {}'.format(orders_dict))
return {'orders': orders_dict}, 200
if __name__ == '__main__':
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
log_formater = logging.Formatter('[%(filename)s:%(lineno)d] %(message)s')
stream_logger = logging.StreamHandler()
stream_logger.setFormatter(log_formater)
# file_logger = logging.FileHandler('log.txt')
# file_logger.setLevel(logging.DEBUG)
# file_logger.setFormatter(log_formater)
logger.addHandler(stream_logger)
# logger.addHandler(file_logger)
app = Flask(__name__)
api = Api(app)
api.add_resource(ListProducts, '/product/list')
api.add_resource(AddProduct, '/product/add')
api.add_resource(ProductOrder, '/product/order')
api.add_resource(UserRegisterPayment, '/user/register_payment')
api.add_resource(OrderList, '/order/list')
app.run(host='::', port=config.get('app', 'port', fallback=5000), debug=True)
@app.errorhandler(Exception)
def handle_exception(e):
app.logger.error(e)
# pass through HTTP errors
if isinstance(e, HTTPException):
return e
# now you're handling non-HTTP exceptions only
return {'message': 'Server Error'}, 500

View File

@ -0,0 +1,11 @@
## TODO 2020-02-22
* ~~move the current rest api to /opennebula~~
* ~~make the /opennebula api only accessible by an admin account~~
* ~~create a new filtered api on /vm/list that~~
* ~~a) requires authentication~~
* ~~b) only shows the VMs of the current user~~
* ~~the new api should not contain all details, but: cpus (as read by the vcpu field), ram, ips, disks~~
* ~~also make a (random) uuid the primary key for VMs - everything in this uncloud hack will use uuids as the id~~
* ~~still expose the opennebula id as opennebula_id~~
* ~~note put all secrets/configs into uncloud.secrets - I added a sample file into the repo~~

View File

@ -0,0 +1,102 @@
* snapshot feature
** product: vm-snapshot
** flow
*** list all my VMs
**** get the uuid of the VM I want to take a snapshot of
*** request a snapshot
```
vmuuid=$(http nicocustomer
http -a nicocustomer:xxx http://uncloud.ch/vm/create_snapshot uuid=
password=...
```
** backend realisation
*** list snapshots
- have them in the DB
- create an entry on create
*** creating snapshots
- vm sync / fsync?
- rbd snapshot
- host/cluster mapping?
- need image(s)
* steps
** DONE authenticate via ldap
CLOSED: [2020-02-20 Thu 19:05]
** DONE Make classes / views require authentication
CLOSED: [2020-02-20 Thu 19:05]
** TODO register credit card
*** TODO find out what saving with us
*** Info
**** should not be fully saved in the DB
**** model needs to be a bit different
* Decide where to save sensitive data
** stripe access key, etc.
* python requirements (nicohack202002)
django djangorestframework django-auth-ldap stripe
* os package requirements (alpine)
openldap-dev
* VPN case
** put on /orders with uuid
** register cc
* CC
** TODO check whether we can register or not at stripe
* membership
** required for "smaller" / "shorter" products
* TODO Membership missing
* Flows to be implemented - see https://redmine.ungleich.ch/issues/7609
** Membership
*** 5 CHF
** Django Hosting
*** One time payment 35 CHF
*** Monthly payment depends on VM size
*** Parameters: same as IPv6 only VM
** IPv6 VPN
*** Parameters: none
*** Is for free if the customer has an active VM
** IPv6 only VM
*** Parameters: cores, ram, os_disk_size, OS
* Django rest framework
** viewset: .list and .create
** view: .get .post
* TODO register CC
* DONE list products
CLOSED: [2020-02-24 Mon 20:15]
* An ungleich account - can be registered for free on
https://account.ungleich.ch
* httpie installed (provides the http command)
## Get a membership
## Registering a payment method
To be able to pay for the membership, you will need to register a
credit card or apply for payment on bill (TO BE IMPLEMENTED).
### Register credit card
```
http POST https://api.ungleich.ch/membership \
username=nico password=yourpassword \
cc_number=.. \
cc_
```
### Request payment via bill
## Create the membership
```
http POST https://api.ungleich.ch/membership username=nico password=yourpassword
```
## List available products

View File

@ -0,0 +1,6 @@
* TODO register CC
* TODO list products
* ahmed
** schemas
*** field: is_valid? - used by schemas
*** definition of a "schema"

View File

@ -0,0 +1,4 @@
db.sqlite3
uncloud/secrets.py
debug.log
uncloud/local_settings.py

View File

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

View File

@ -0,0 +1,2 @@
class UncloudException(Exception):
pass

View File

@ -0,0 +1,23 @@
import argparse
import etcd3
from uncloud.common.etcd_wrapper import Etcd3Wrapper
arg_parser = argparse.ArgumentParser('client', add_help=False)
arg_parser.add_argument('--dump-etcd-contents-prefix', help="Dump contents below the given prefix")
def dump_etcd_contents(prefix):
etcd = Etcd3Wrapper()
for k,v in etcd.get_prefix_raw(prefix):
k = k.decode('utf-8')
v = v.decode('utf-8')
print("{} = {}".format(k,v))
# print("{} = {}".format(k,v))
# for k,v in etcd.get_prefix(prefix):
#
print("done")
def main(arguments):
if 'dump_etcd_contents_prefix' in arguments:
dump_etcd_contents(prefix=arguments['dump_etcd_contents_prefix'])

View File

@ -24,6 +24,7 @@ import etcd3
import json
import logging
import datetime
import re
from functools import wraps
from uncloud import UncloudException
@ -108,6 +109,17 @@ class DB(object):
return self._db_clients[0].put(self.realkey(key), value, **kwargs)
@readable_errors
def list_and_filter(self, key, filter_key=None, filter_regexp=None):
for k,v in self.get_prefix(key, as_json=True):
if filter_key and filter_regexp:
if filter_key in v:
if re.match(filter_regexp, v[filter_key]):
yield v
else:
yield v
@readable_errors
def increment(self, key, **kwargs):

Some files were not shown because too many files have changed in this diff Show More