{"action":"created","comment":{"url":"https://api.github.com/repos/opsmop/opsmop/pulls/comments/240620619","pull_request_review_id":183689545,"id":240620619,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDI0MDYyMDYxOQ==","diff_hunk":"@@ -0,0 +1,47 @@\n+# Copyright 2018 Michael DeHaan LLC, <michael@michaeldehaan.net>\n+#\n+# Licensed under the Apache License, Version 2.0 (the \"License\");\n+# you may not use this file except in compliance with the License.\n+# You may obtain a copy of the License at\n+#\n+#    http://www.apache.org/licenses/LICENSE-2.0\n+#\n+# Unless required by applicable law or agreed to in writing, software\n+# distributed under the License is distributed on an \"AS IS\" BASIS,\n+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+# See the License for the specific language governing permissions and\n+# limitations under the License.\n+\n+from opsmop.providers.provider import Provider\n+from opsmop.core.errors import ValidationError\n+\n+GET_INTERFACES = \"cat /proc/net/dev\"\n+\n+\n+class Network(Provider):\n+\n+    def get_interface_config(self):\n+        raise NotImplementedError\n+\n+    def plan(self):\n+        valid_interface_names = self._get_valid_interfaces()\n+        for interface in self.resource.interfaces:\n+            # First validate that the requested interface exists and raise an error if not\n+            if interface.name in valid_interface_names:\n+                current_state = self.get_interface_config(interface.name)\n+                if current_state.get_fields_dict() and current_state.get_fields_dict() != interface.get_fields_dict():","path":"opsmop/providers/network/network.py","position":32,"original_position":32,"commit_id":"cc9e52821c009e5ec19bec4f2551e87124bf7e52","original_commit_id":"699ba10f676174fb34fbc2d62b6115916a31c95d","user":{"login":"graysonhead","id":6179496,"node_id":"MDQ6VXNlcjYxNzk0OTY=","avatar_url":"https://avatars3.githubusercontent.com/u/6179496?v=4","gravatar_id":"","url":"https://api.github.com/users/graysonhead","html_url":"https://github.com/graysonhead","followers_url":"https://api.github.com/users/graysonhead/followers","following_url":"https://api.github.com/users/graysonhead/following{/other_user}","gists_url":"https://api.github.com/users/graysonhead/gists{/gist_id}","starred_url":"https://api.github.com/users/graysonhead/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/graysonhead/subscriptions","organizations_url":"https://api.github.com/users/graysonhead/orgs","repos_url":"https://api.github.com/users/graysonhead/repos","events_url":"https://api.github.com/users/graysonhead/events{/privacy}","received_events_url":"https://api.github.com/users/graysonhead/received_events","type":"User","site_admin":false},"body":"This is a part I'm not quite satisfied with, and I think maybe a better mechanism may exist to do this already.\r\n\r\nRight now, this method iterates through a statically defined list of expected fields (which duplicates the fields on the instance itself) and adds them to a dict. I use this dict in Network.plan() to compare the parsed current state and the defined desired state.\r\n\r\nOriginally I had planned on comparing the Fields attribute of the classes directly, but unfortunately I had to come up with another way to populate those attributes when I am parsing from a file (since the sysconfig files don't necessarily define every parameter). As a result, I set the attributes directly when parsing the file, which means the fields attribute on my generated object is empty. So this is kind of a hack to make comparison between instances of Interface to work.\r\n\r\nI'm very open to a better way of doing this. And that might just consist of me figuring out how to add Fields after __init__ is called on the class.","created_at":"2018-12-11T14:01:24Z","updated_at":"2018-12-11T14:01:25Z","html_url":"https://github.com/opsmop/opsmop/pull/29#discussion_r240620619","pull_request_url":"https://api.github.com/repos/opsmop/opsmop/pulls/29","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/opsmop/opsmop/pulls/comments/240620619"},"html":{"href":"https://github.com/opsmop/opsmop/pull/29#discussion_r240620619"},"pull_request":{"href":"https://api.github.com/repos/opsmop/opsmop/pulls/29"}},"in_reply_to_id":240580293},"pull_request":{"url":"https://api.github.com/repos/opsmop/opsmop/pulls/29","id":237559433,"node_id":"MDExOlB1bGxSZXF1ZXN0MjM3NTU5NDMz","html_url":"https://github.com/opsmop/opsmop/pull/29","diff_url":"https://github.com/opsmop/opsmop/pull/29.diff","patch_url":"https://github.com/opsmop/opsmop/pull/29.patch","issue_url":"https://api.github.com/repos/opsmop/opsmop/issues/29","number":29,"state":"open","locked":false,"title":"Network sysconfig module","user":{"login":"graysonhead","id":6179496,"node_id":"MDQ6VXNlcjYxNzk0OTY=","avatar_url":"https://avatars3.githubusercontent.com/u/6179496?v=4","gravatar_id":"","url":"https://api.github.com/users/graysonhead","html_url":"https://github.com/graysonhead","followers_url":"https://api.github.com/users/graysonhead/followers","following_url":"https://api.github.com/users/graysonhead/following{/other_user}","gists_url":"https://api.github.com/users/graysonhead/gists{/gist_id}","starred_url":"https://api.github.com/users/graysonhead/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/graysonhead/subscriptions","organizations_url":"https://api.github.com/users/graysonhead/orgs","repos_url":"https://api.github.com/users/graysonhead/repos","events_url":"https://api.github.com/users/graysonhead/events{/privacy}","received_events_url":"https://api.github.com/users/graysonhead/received_events","type":"User","site_admin":false},"body":"Here is a mostly feature-complete version of the sysconfig provider and associated types (for physical interfaces only, right now.) A few things to point out:\r\n\r\nIn regards to signals, I wanted to make sure I'm using them sanely. I'm dynamically generating the signal name based on the sub-resource that needs an operation, I.E.:\r\n```\r\n--------------------------\r\n1. HelloRole => Interfaces\r\n\r\n    parameters:\r\n        | auto_reload: True\r\n        | method: sysconfig\r\n    planning...\r\n        # cat /proc/net/dev\r\n        | Inter-|   Receive                                                |  Transmit\r\n        |  face |bytes    packets errs drop fifo frame compressed multicast|bytes    packets errs drop fifo colls carrier compressed\r\n        |   ens9:    2934      16  890    0    0   890          0         0      530       8    0    0    0     0       0          0\r\n        |   eth0: 15318332    3259    0    7    0     0          0         0   484770    2753    0    0    0     0       0          0\r\n        |     lo:    1740      20    0    0    0     0          0         0     1740      20    0    0    0     0       0          0\r\n        = ok, rc=0\r\n        # cat /etc/sysconfig/network-scripts/ifcfg-ens9\r\n        | DEVICE=ens9\r\n        | IPADDR=10.1.1.123\r\n        | NETMASK=255.255.255.0\r\n        | METRIC=2\r\n        | IPV4_FAILURE_FATAL=NO\r\n        | ONBOOT=YES\r\n        | BOOTPROTO=static\r\n        | MTU=2300\r\n        = ok, rc=0\r\n    do: update_interface_ens9\r\n        | New Interface Config:\r\n        | DEVICE=ens9\r\n        | IPADDR=10.1.1.143\r\n        | NETMASK=255.255.255.0\r\n        | METRIC=2\r\n        | IPV4_FAILURE_FATAL=NO\r\n        | ONBOOT=YES\r\n        | BOOTPROTO=static\r\n        | MTU=2300\r\n    do: reload_service\r\n        # systemctl restart network\r\n        | (no output)\r\n        = ok, rc=0\r\n    ok\r\n\r\n---------\r\ncomplete!\r\n\r\n```\r\n\r\nThis seemed like I was using signals correctly at the time, but wanted to make sure this was a good way to address sub-resource planning.\r\n\r\nI created a class in the interface type module to classify interface types as abstract (as we mentioned in the forum thread):\r\n\r\n```\r\nclass InterfaceAbstract(Type):\r\n    is_abstract = True\r\n```\r\nSo we could check for that in whatever component validates resources to ensure people aren't sticking it directly where a normal resource should go. If you don't like that convention, it can be easily modified, but I figure it needs to live outside of the interface type module either way (I just couldn't figure out an obvious place to stick it). \r\n\r\nAlso, if you can point me to where the validation change should be made to raise a ValidationError when people try to return an abstract resource I would be more than happy to take a crack at that validation step.\r\n\r\nExample usage is like so: (I'll add a more complete example to the docs assuming you don't request any changes).\r\n\r\n```\r\nfrom opsmop.core.easy import *\r\nfrom opsmop.types.interfaces import Interfaces, Interface\r\n\r\nclass NetworkRole(Role):\r\n\r\n    def set_variables(self):\r\n        return dict(program='OpsMop')\r\n\r\n    def set_resources(self):\r\n        ifaces = Interfaces(method='sysconfig', auto_reload=True)\r\n        ifaces.add(Interface(name='ens9',\r\n                             mtu=2300,\r\n                             ipv4_address='10.1.1.143',\r\n                             ipv4_netmask='255.255.255.0',\r\n                             metric=2,\r\n                             on_boot=True,\r\n                             boot_proto='static',\r\n                             ))\r\n        return Resources(\r\n            ifaces\r\n        )\r\n\r\n\r\n# -----------------\r\n\r\nclass NetTest(Policy):\r\n\r\n    def set_variables(self):\r\n        return dict()\r\n\r\n    def set_roles(self):\r\n        return Roles(NetworkRole())\r\n\r\n\r\ndef main():\r\n    return NetTest()\r\n```\r\n\r\nI want to nail down the structure before I add the rest of the params and abstract types (although it would probably be sufficient for a large portion of use-cases as is). Let me know what you think.","created_at":"2018-12-11T05:58:08Z","updated_at":"2018-12-11T14:01:25Z","closed_at":null,"merged_at":null,"merge_commit_sha":"0167c67200142e393bc1d15e3a6a841c0c80bdbe","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/opsmop/opsmop/pulls/29/commits","review_comments_url":"https://api.github.com/repos/opsmop/opsmop/pulls/29/comments","review_comment_url":"https://api.github.com/repos/opsmop/opsmop/pulls/comments{/number}","comments_url":"https://api.github.com/repos/opsmop/opsmop/issues/29/comments","statuses_url":"https://api.github.com/repos/opsmop/opsmop/statuses/cc9e52821c009e5ec19bec4f2551e87124bf7e52","head":{"label":"graysonhead:network-sysconfig-module","ref":"network-sysconfig-module","sha":"cc9e52821c009e5ec19bec4f2551e87124bf7e52","user":{"login":"graysonhead","id":6179496,"node_id":"MDQ6VXNlcjYxNzk0OTY=","avatar_url":"https://avatars3.githubusercontent.com/u/6179496?v=4","gravatar_id":"","url":"https://api.github.com/users/graysonhead","html_url":"https://github.com/graysonhead","followers_url":"https://api.github.com/users/graysonhead/followers","following_url":"https://api.github.com/users/graysonhead/following{/other_user}","gists_url":"https://api.github.com/users/graysonhead/gists{/gist_id}","starred_url":"https://api.github.com/users/graysonhead/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/graysonhead/subscriptions","organizations_url":"https://api.github.com/users/graysonhead/orgs","repos_url":"https://api.github.com/users/graysonhead/repos","events_url":"https://api.github.com/users/graysonhead/events{/privacy}","received_events_url":"https://api.github.com/users/graysonhead/received_events","type":"User","site_admin":false},"repo":{"id":159605828,"node_id":"MDEwOlJlcG9zaXRvcnkxNTk2MDU4Mjg=","name":"opsmop","full_name":"graysonhead/opsmop","private":false,"owner":{"login":"graysonhead","id":6179496,"node_id":"MDQ6VXNlcjYxNzk0OTY=","avatar_url":"https://avatars3.githubusercontent.com/u/6179496?v=4","gravatar_id":"","url":"https://api.github.com/users/graysonhead","html_url":"https://github.com/graysonhead","followers_url":"https://api.github.com/users/graysonhead/followers","following_url":"https://api.github.com/users/graysonhead/following{/other_user}","gists_url":"https://api.github.com/users/graysonhead/gists{/gist_id}","starred_url":"https://api.github.com/users/graysonhead/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/graysonhead/subscriptions","organizations_url":"https://api.github.com/users/graysonhead/orgs","repos_url":"https://api.github.com/users/graysonhead/repos","events_url":"https://api.github.com/users/graysonhead/events{/privacy}","received_events_url":"https://api.github.com/users/graysonhead/received_events","type":"User","site_admin":false},"html_url":"https://github.com/graysonhead/opsmop","description":"Next generation config management. Language-preview phase. PRs open Dec 1. ","fork":true,"url":"https://api.github.com/repos/graysonhead/opsmop","forks_url":"https://api.github.com/repos/graysonhead/opsmop/forks","keys_url":"https://api.github.com/repos/graysonhead/opsmop/keys{/key_id}","collaborators_url":"https://api.github.com/repos/graysonhead/opsmop/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/graysonhead/opsmop/teams","hooks_url":"https://api.github.com/repos/graysonhead/opsmop/hooks","issue_events_url":"https://api.github.com/repos/graysonhead/opsmop/issues/events{/number}","events_url":"https://api.github.com/repos/graysonhead/opsmop/events","assignees_url":"https://api.github.com/repos/graysonhead/opsmop/assignees{/user}","branches_url":"https://api.github.com/repos/graysonhead/opsmop/branches{/branch}","tags_url":"https://api.github.com/repos/graysonhead/opsmop/tags","blobs_url":"https://api.github.com/repos/graysonhead/opsmop/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/graysonhead/opsmop/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/graysonhead/opsmop/git/refs{/sha}","trees_url":"https://api.github.com/repos/graysonhead/opsmop/git/trees{/sha}","statuses_url":"https://api.github.com/repos/graysonhead/opsmop/statuses/{sha}","languages_url":"https://api.github.com/repos/graysonhead/opsmop/languages","stargazers_url":"https://api.github.com/repos/graysonhead/opsmop/stargazers","contributors_url":"https://api.github.com/repos/graysonhead/opsmop/contributors","subscribers_url":"https://api.github.com/repos/graysonhead/opsmop/subscribers","subscription_url":"https://api.github.com/repos/graysonhead/opsmop/subscription","commits_url":"https://api.github.com/repos/graysonhead/opsmop/commits{/sha}","git_commits_url":"https://api.github.com/repos/graysonhead/opsmop/git/commits{/sha}","comments_url":"https://api.github.com/repos/graysonhead/opsmop/comments{/number}","issue_comment_url":"https://api.github.com/repos/graysonhead/opsmop/issues/comments{/number}","contents_url":"https://api.github.com/repos/graysonhead/opsmop/contents/{+path}","compare_url":"https://api.github.com/repos/graysonhead/opsmop/compare/{base}...{head}","merges_url":"https://api.github.com/repos/graysonhead/opsmop/merges","archive_url":"https://api.github.com/repos/graysonhead/opsmop/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/graysonhead/opsmop/downloads","issues_url":"https://api.github.com/repos/graysonhead/opsmop/issues{/number}","pulls_url":"https://api.github.com/repos/graysonhead/opsmop/pulls{/number}","milestones_url":"https://api.github.com/repos/graysonhead/opsmop/milestones{/number}","notifications_url":"https://api.github.com/repos/graysonhead/opsmop/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/graysonhead/opsmop/labels{/name}","releases_url":"https://api.github.com/repos/graysonhead/opsmop/releases{/id}","deployments_url":"https://api.github.com/repos/graysonhead/opsmop/deployments","created_at":"2018-11-29T04:07:37Z","updated_at":"2018-12-11T05:43:08Z","pushed_at":"2018-12-11T05:44:34Z","git_url":"git://github.com/graysonhead/opsmop.git","ssh_url":"git@github.com:graysonhead/opsmop.git","clone_url":"https://github.com/graysonhead/opsmop.git","svn_url":"https://github.com/graysonhead/opsmop","homepage":"https://opsmop.io/","size":389,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"opsmop:master","ref":"master","sha":"3b623ec8e2f0ae8bca544c4c4c68d9d3c70950f3","user":{"login":"opsmop","id":45145059,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ1MTQ1MDU5","avatar_url":"https://avatars1.githubusercontent.com/u/45145059?v=4","gravatar_id":"","url":"https://api.github.com/users/opsmop","html_url":"https://github.com/opsmop","followers_url":"https://api.github.com/users/opsmop/followers","following_url":"https://api.github.com/users/opsmop/following{/other_user}","gists_url":"https://api.github.com/users/opsmop/gists{/gist_id}","starred_url":"https://api.github.com/users/opsmop/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/opsmop/subscriptions","organizations_url":"https://api.github.com/users/opsmop/orgs","repos_url":"https://api.github.com/users/opsmop/repos","events_url":"https://api.github.com/users/opsmop/events{/privacy}","received_events_url":"https://api.github.com/users/opsmop/received_events","type":"Organization","site_admin":false},"repo":{"id":156804512,"node_id":"MDEwOlJlcG9zaXRvcnkxNTY4MDQ1MTI=","name":"opsmop","full_name":"opsmop/opsmop","private":false,"owner":{"login":"opsmop","id":45145059,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ1MTQ1MDU5","avatar_url":"https://avatars1.githubusercontent.com/u/45145059?v=4","gravatar_id":"","url":"https://api.github.com/users/opsmop","html_url":"https://github.com/opsmop","followers_url":"https://api.github.com/users/opsmop/followers","following_url":"https://api.github.com/users/opsmop/following{/other_user}","gists_url":"https://api.github.com/users/opsmop/gists{/gist_id}","starred_url":"https://api.github.com/users/opsmop/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/opsmop/subscriptions","organizations_url":"https://api.github.com/users/opsmop/orgs","repos_url":"https://api.github.com/users/opsmop/repos","events_url":"https://api.github.com/users/opsmop/events{/privacy}","received_events_url":"https://api.github.com/users/opsmop/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/opsmop/opsmop","description":"Next generation config management and app deployment","fork":false,"url":"https://api.github.com/repos/opsmop/opsmop","forks_url":"https://api.github.com/repos/opsmop/opsmop/forks","keys_url":"https://api.github.com/repos/opsmop/opsmop/keys{/key_id}","collaborators_url":"https://api.github.com/repos/opsmop/opsmop/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/opsmop/opsmop/teams","hooks_url":"https://api.github.com/repos/opsmop/opsmop/hooks","issue_events_url":"https://api.github.com/repos/opsmop/opsmop/issues/events{/number}","events_url":"https://api.github.com/repos/opsmop/opsmop/events","assignees_url":"https://api.github.com/repos/opsmop/opsmop/assignees{/user}","branches_url":"https://api.github.com/repos/opsmop/opsmop/branches{/branch}","tags_url":"https://api.github.com/repos/opsmop/opsmop/tags","blobs_url":"https://api.github.com/repos/opsmop/opsmop/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/opsmop/opsmop/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/opsmop/opsmop/git/refs{/sha}","trees_url":"https://api.github.com/repos/opsmop/opsmop/git/trees{/sha}","statuses_url":"https://api.github.com/repos/opsmop/opsmop/statuses/{sha}","languages_url":"https://api.github.com/repos/opsmop/opsmop/languages","stargazers_url":"https://api.github.com/repos/opsmop/opsmop/stargazers","contributors_url":"https://api.github.com/repos/opsmop/opsmop/contributors","subscribers_url":"https://api.github.com/repos/opsmop/opsmop/subscribers","subscription_url":"https://api.github.com/repos/opsmop/opsmop/subscription","commits_url":"https://api.github.com/repos/opsmop/opsmop/commits{/sha}","git_commits_url":"https://api.github.com/repos/opsmop/opsmop/git/commits{/sha}","comments_url":"https://api.github.com/repos/opsmop/opsmop/comments{/number}","issue_comment_url":"https://api.github.com/repos/opsmop/opsmop/issues/comments{/number}","contents_url":"https://api.github.com/repos/opsmop/opsmop/contents/{+path}","compare_url":"https://api.github.com/repos/opsmop/opsmop/compare/{base}...{head}","merges_url":"https://api.github.com/repos/opsmop/opsmop/merges","archive_url":"https://api.github.com/repos/opsmop/opsmop/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/opsmop/opsmop/downloads","issues_url":"https://api.github.com/repos/opsmop/opsmop/issues{/number}","pulls_url":"https://api.github.com/repos/opsmop/opsmop/pulls{/number}","milestones_url":"https://api.github.com/repos/opsmop/opsmop/milestones{/number}","notifications_url":"https://api.github.com/repos/opsmop/opsmop/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/opsmop/opsmop/labels{/name}","releases_url":"https://api.github.com/repos/opsmop/opsmop/releases{/id}","deployments_url":"https://api.github.com/repos/opsmop/opsmop/deployments","created_at":"2018-11-09T03:22:50Z","updated_at":"2018-12-11T07:18:22Z","pushed_at":"2018-12-11T05:58:09Z","git_url":"git://github.com/opsmop/opsmop.git","ssh_url":"git@github.com:opsmop/opsmop.git","clone_url":"https://github.com/opsmop/opsmop.git","svn_url":"https://github.com/opsmop/opsmop","homepage":"https://opsmop.io/","size":454,"stargazers_count":131,"watchers_count":131,"language":"Python","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":14,"mirror_url":null,"archived":false,"open_issues_count":17,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":14,"open_issues":17,"watchers":131,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/opsmop/opsmop/pulls/29"},"html":{"href":"https://github.com/opsmop/opsmop/pull/29"},"issue":{"href":"https://api.github.com/repos/opsmop/opsmop/issues/29"},"comments":{"href":"https://api.github.com/repos/opsmop/opsmop/issues/29/comments"},"review_comments":{"href":"https://api.github.com/repos/opsmop/opsmop/pulls/29/comments"},"review_comment":{"href":"https://api.github.com/repos/opsmop/opsmop/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/opsmop/opsmop/pulls/29/commits"},"statuses":{"href":"https://api.github.com/repos/opsmop/opsmop/statuses/cc9e52821c009e5ec19bec4f2551e87124bf7e52"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"graysonhead"}}