File: cc_phone_home.py

package info (click to toggle)
cloud-init 25.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 12,412 kB
  • sloc: python: 135,894; sh: 3,883; makefile: 141; javascript: 30; xml: 22
file content (142 lines) | stat: -rw-r--r-- 3,829 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# Copyright (C) 2011 Canonical Ltd.
# Copyright (C) 2012, 2013 Hewlett-Packard Development Company, L.P.
#
# Author: Scott Moser <scott.moser@canonical.com>
# Author: Juerg Haefliger <juerg.haefliger@hp.com>
#
# This file is part of cloud-init. See LICENSE file for license information.

"""Phone Home: Post data to url"""

import logging

from cloudinit import templater, url_helper, util
from cloudinit.cloud import Cloud
from cloudinit.config import Config
from cloudinit.config.schema import MetaSchema
from cloudinit.distros import ALL_DISTROS
from cloudinit.settings import PER_INSTANCE

POST_LIST_ALL = [
    "pub_key_rsa",
    "pub_key_ecdsa",
    "pub_key_ed25519",
    "instance_id",
    "hostname",
    "fqdn",
]

meta: MetaSchema = {
    "id": "cc_phone_home",
    "distros": [ALL_DISTROS],
    "frequency": PER_INSTANCE,
    "activate_by_schema_keys": ["phone_home"],
}

LOG = logging.getLogger(__name__)
# phone_home:
#  url: http://my.foo.bar/{{ v1.instance_id }}/
#  post: all
#  tries: 10
#
# phone_home:
#  url: http://my.foo.bar/{{ v1.instance_id }}/
#  post: [ pub_key_rsa, pub_key_ecdsa, instance_id, hostname,
#          fqdn ]
#


def handle(name: str, cfg: Config, cloud: Cloud, args: list) -> None:
    if args:
        ph_cfg = util.read_conf(args[0])
    else:
        if "phone_home" not in cfg:
            LOG.debug(
                "Skipping module named %s, "
                "no 'phone_home' configuration found",
                name,
            )
            return
        ph_cfg = cfg["phone_home"]

    if "url" not in ph_cfg:
        LOG.warning(
            "Skipping module named %s, "
            "no 'url' found in 'phone_home' configuration",
            name,
        )
        return

    url = ph_cfg["url"]
    post_list = ph_cfg.get("post", "all")
    tries = ph_cfg.get("tries")
    try:
        tries = int(tries)  # type: ignore
    except (ValueError, TypeError):
        tries = 10
        util.logexc(
            LOG,
            "Configuration entry 'tries' is not an integer, using %s instead",
            tries,
        )

    if post_list == "all":
        post_list = POST_LIST_ALL

    all_keys = {
        "instance_id": cloud.get_instance_id(),
        "hostname": cloud.get_hostname().hostname,
        "fqdn": cloud.get_hostname(fqdn=True).hostname,
    }

    pubkeys = {
        "pub_key_rsa": "/etc/ssh/ssh_host_rsa_key.pub",
        "pub_key_ecdsa": "/etc/ssh/ssh_host_ecdsa_key.pub",
        "pub_key_ed25519": "/etc/ssh/ssh_host_ed25519_key.pub",
    }

    for n, path in pubkeys.items():
        try:
            all_keys[n] = util.load_text_file(path)
        except Exception:
            util.logexc(
                LOG, "%s: failed to open, can not phone home that data!", path
            )

    submit_keys = {}
    for k in post_list:
        if k in all_keys:
            submit_keys[k] = all_keys[k]
        else:
            submit_keys[k] = None
            LOG.warning(
                "Requested key %s from 'post'"
                " configuration list not available",
                k,
            )

    # Get them read to be posted
    real_submit_keys = {}
    for k, v in submit_keys.items():
        if v is None:
            real_submit_keys[k] = "N/A"
        else:
            real_submit_keys[k] = str(v)

    # Incase the url is parameterized
    url_params = {
        "INSTANCE_ID": all_keys["instance_id"],
    }
    url = templater.render_string(url, url_params)
    try:
        url_helper.read_file_or_url(
            url,
            data=real_submit_keys,
            retries=tries - 1,
            sec_between=3,
            ssl_details=util.fetch_ssl_details(cloud.paths),
        )
    except Exception:
        util.logexc(
            LOG, "Failed to post phone home data to %s in %s tries", url, tries
        )