From ca22cba8e41247701338142fa2057343e2a517fb Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Tue, 4 Jun 2019 18:09:50 +0200 Subject: [PATCH] Allow to specify the probed cloud stacks with -c cloud[,cloud...] --- agent/azure.c | 22 ++++--- agent/cloud-agent.8 | 24 +++++++- agent/cloudinit.c | 62 ++++---------------- agent/main.c | 140 ++++++++++++++++++++++++++++++++++++++------ agent/main.h | 32 ++++++++-- agent/opennebula.c | 7 ++- agent/openstack.c | 8 ++- 7 files changed, 210 insertions(+), 85 deletions(-) diff --git a/agent/azure.c b/agent/azure.c index 990d973..c880f9d 100644 --- a/agent/azure.c +++ b/agent/azure.c @@ -63,16 +63,22 @@ azure(struct system_config *sc) { int ret = -1; - sc->sc_stack = "azure"; + if (sc->sc_state == STATE_INIT) { + free(sc->sc_username); + if ((sc->sc_username = strdup("azure-user")) == NULL) { + log_warnx("failed to set default user"); + return (-1); + } - /* Apply defaults */ - free(sc->sc_username); - if ((sc->sc_username = strdup("azure-user")) == NULL) { - log_warnx("failed to set default user"); - goto fail; + /* Apply defaults */ + sc->sc_ovfenv = "/var/db/azure-ovf-env.xml"; + sc->sc_priv = &az_config; + sc->sc_state = STATE_DHCP; + return (-1); } - sc->sc_ovfenv = "/var/db/azure-ovf-env.xml"; - sc->sc_priv = &az_config; + + /* Don't try other endpoints */ + sc->sc_state = STATE_DONE; if (azure_getovfenv(sc) != 0) { log_warnx("failed to get ovf-env.xml"); diff --git a/agent/cloud-agent.8 b/agent/cloud-agent.8 index b6990f3..5f01cf0 100644 --- a/agent/cloud-agent.8 +++ b/agent/cloud-agent.8 @@ -23,6 +23,7 @@ .Sh SYNOPSIS .Nm cloud-agent .Op Fl nuv +.Op Fl c Ar cloud Ns Op , Ns Ar cloud Ns ... .Op Fl p Ar length .Op Fl r Ar rootdisk .Op Fl t Ar timeout @@ -32,10 +33,31 @@ The .Nm program manages the OpenBSD provisioning and VM interaction in cloud -environments, including Microsoft Azure and Amazon AWS. +environments. .Pp The options are as follows: .Bl -tag -width Ds +.It Fl c Ar cloud Ns Op , Ns Ar cloud Ns ... +Probe a list of cloud stacks for provisioning in the specified order. +If this option is not specified, +.Nm +tries to detect the environment and possible cloud stacks automatically. +Supported +.Ar cloud +stacks are: +.Pp +.Bl -tag -width opennebula -offset indent -compact +.It Ic azure +Microsoft Azure +.It Ic cloudinit +Generic cloud-init +.It Ic ec2 +Amazon AWS EC2 +.It Ic opennebula +OpenNebula +.It Ic openstack +OpenStack +.El .It Fl p Ar length Generate and set a random password for the default user. The password will be written in its plain form into the diff --git a/agent/cloudinit.c b/agent/cloudinit.c index 75b28e7..29582b4 100644 --- a/agent/cloudinit.c +++ b/agent/cloudinit.c @@ -31,65 +31,29 @@ static int cloudinit_fetch(struct system_config *); -int -tryendpoint(struct system_config *sc, - int (fetch)(struct system_config *), - int (next)(struct system_config *)) -{ - int errfd = -1, ret; - - free(sc->sc_endpoint); - sc->sc_endpoint = NULL; - - switch (sc->sc_dhcpendpoint) { - case 0: - sc->sc_dhcpendpoint = 1; - if (dhcp_getendpoint(sc) == -1) - return tryendpoint(sc, fetch, next); - break; - case 1: - sc->sc_dhcpendpoint = 2; - if ((sc->sc_endpoint = strdup(DEFAULT_ENDPOINT)) == NULL) { - log_warnx("failed to set defaults"); - return (-1); - } - break; - default: - if (next == NULL) - return (-1); - sc->sc_dhcpendpoint = 0; - return (*next)(sc); - } - - errfd = disable_output(sc, STDERR_FILENO); - ret = (*fetch)(sc); - enable_output(sc, STDERR_FILENO, errfd); - if (ret != 0) - return tryendpoint(sc, fetch, next); - - return (0); -} - int ec2(struct system_config *sc) { - free(sc->sc_username); - if ((sc->sc_username = strdup("ec2-user")) == NULL) { - log_warnx("failed to set default user"); + if (sc->sc_state == STATE_INIT) { + free(sc->sc_username); + if ((sc->sc_username = strdup("ec2-user")) == NULL) { + log_warnx("failed to set default user"); + return (-1); + } + sc->sc_state = STATE_169; return (-1); } - - sc->sc_stack = "ec2"; - sc->sc_dhcpendpoint = 1; - return tryendpoint(sc, cloudinit_fetch, NULL); + return cloudinit_fetch(sc); } int cloudinit(struct system_config *sc) { - sc->sc_stack = "cloudinit"; - sc->sc_dhcpendpoint = 0; - return tryendpoint(sc, cloudinit_fetch, NULL); + if (sc->sc_state == STATE_INIT) { + sc->sc_state = STATE_DHCP; + return (-1); + } + return cloudinit_fetch(sc); } static int diff --git a/agent/main.c b/agent/main.c index 05ae3dd..ed44b3a 100644 --- a/agent/main.c +++ b/agent/main.c @@ -56,6 +56,7 @@ static void agent_unconfigure(void); static char *metadata_parse(char *, size_t, enum strtype); static int agent_timeout; +static char *cloudnames[] = CLOUDNAMES; int shell(const char *arg, ...) @@ -970,7 +971,8 @@ metadata(struct system_config *sc, const char *path, enum strtype type) g = http_get(&sc->sc_addr, 1, sc->sc_endpoint, 80, path, NULL, 0, NULL); if (g != NULL) - log_debug("%s: HTTP %d %s", __func__, g->code, path); + log_debug("%s: HTTP %d http://%s%s", __func__, g->code, + sc->sc_endpoint, path); if (g != NULL && g->code == 200 && g->bodypartsz > 0) str = metadata_parse(g->bodypart, g->bodypartsz, type); @@ -1190,20 +1192,118 @@ pwgen(size_t len, char **hash) return (password); } +static void +cloud_add(enum cloudname name, struct clouds *clouds) +{ + struct cloud *cloud; + + TAILQ_FOREACH(cloud, clouds, cloud_entry) { + if (cloud->cloud_name == name) + fatalx("cloud %s defined twice", + cloudnames[name]); + } + + if ((cloud = calloc(1, sizeof(*cloud))) == NULL) + fatal("%s: calloc", __func__); + + cloud->cloud_name = name; + switch (name) { + case AZURE: + cloud->fetch = azure; + break; + case CLOUDINIT: + cloud->fetch = cloudinit; + break; + case EC2: + cloud->fetch = ec2; + break; + case OPENNEBULA: + cloud->fetch = opennebula; + break; + case OPENSTACK: + cloud->fetch = openstack; + break; + } + + TAILQ_INSERT_TAIL(clouds, cloud, cloud_entry); +} + +static int +trycloud(struct system_config *sc, struct cloud *cloud) +{ + int errfd = -1, ret = -1; + + free(sc->sc_endpoint); + sc->sc_endpoint = NULL; + + switch (sc->sc_state) { + case STATE_INIT: + if ((cloud = TAILQ_FIRST(sc->sc_clouds)) == NULL) + return (0); + sc->sc_stack = cloudnames[cloud->cloud_name]; + log_debug("%s: %s", __func__, sc->sc_stack); + TAILQ_REMOVE(sc->sc_clouds, cloud, cloud_entry); + break; + case STATE_DHCP: + sc->sc_state = STATE_169; + if (dhcp_getendpoint(sc) == -1) + return trycloud(sc, cloud); + break; + case STATE_169: + sc->sc_state = STATE_DONE; + if ((sc->sc_endpoint = strdup(DEFAULT_ENDPOINT)) == NULL) { + log_warnx("failed to set defaults"); + goto done; + } + break; + case STATE_DONE: + sc->sc_state = STATE_INIT; + ret = trycloud(sc, NULL); + goto done; + } + + errfd = disable_output(sc, STDERR_FILENO); + ret = (*cloud->fetch)(sc); + enable_output(sc, STDERR_FILENO, errfd); + if (ret != 0) + return trycloud(sc, cloud); + + done: + free(cloud); + return (0); +} + int main(int argc, char *const *argv) { struct system_config *sc; - int verbose = 0, dryrun = 0, unconfigure = 0; + int verbose = 0, dryrun = 0, unconfigure = 0, sub; int genpw = 0, ch, ret, timeout = CONNECT_TIMEOUT; const char *error = NULL, *rootdisk = NULL; - char *args, *username = NULL; + char *args, *username = NULL, *options, *value; + struct clouds clouds; + + /* log to stderr */ + log_init(1, LOG_DAEMON); if ((args = get_args(argc, argv)) == NULL) fatalx("failed to save args"); - while ((ch = getopt(argc, argv, "np:r:t:U:uv")) != -1) { + TAILQ_INIT(&clouds); + + while ((ch = getopt(argc, argv, "c:np:r:t:U:uv")) != -1) { switch (ch) { + case 'c': + options = optarg; + while (*options) { + if ((sub = getsubopt(&options, + cloudnames, &value)) == -1) + fatalx("invalid cloud stack"); + else if (value != NULL) + fatalx("unexpected value"); + cloud_add(sub, &clouds); + } + break; case 'n': dryrun = 1; break; @@ -1238,8 +1338,6 @@ main(int argc, char *const *argv) argv += optind; argc -= optind; - /* log to stderr */ - log_init(1, LOG_DAEMON); log_setverbose(verbose); if (unconfigure) { @@ -1262,24 +1360,30 @@ main(int argc, char *const *argv) if (rootdisk != NULL && growdisk(sc) == -1) fatalx("failed to grow %s", rootdisk); + sc->sc_clouds = &clouds; sc->sc_args = args; if (username != NULL) { free(sc->sc_username); sc->sc_username = username; } - /* - * XXX Detect cloud with help from hostctl and sysctl - * XXX in addition to the interface name. - */ - if (opennebula(sc) == 0) - ret = 0; - else if (strcmp("hvn0", sc->sc_interface) == 0) - ret = azure(sc); - else if (strcmp("xnf0", sc->sc_interface) == 0) - ret = ec2(sc); - else - ret = openstack(sc); + if (TAILQ_EMPTY(&clouds)) { + /* + * XXX Auto-detect cloud with help from hostctl and + * XXX sysctl in addition to the interface name. + */ + if (strcmp("hvn0", sc->sc_interface) == 0) { + cloud_add(AZURE, &clouds); + } else if (strcmp("xnf0", sc->sc_interface) == 0) { + cloud_add(OPENNEBULA, &clouds); + cloud_add(EC2, &clouds); + } else { + cloud_add(OPENNEBULA, &clouds); + cloud_add(OPENSTACK, &clouds); + cloud_add(CLOUDINIT, &clouds); + } + } + ret = trycloud(sc, NULL); if (genpw) { if (sc->sc_password_hash != NULL) diff --git a/agent/main.h b/agent/main.h index 93b5f92..b54b789 100644 --- a/agent/main.h +++ b/agent/main.h @@ -29,12 +29,30 @@ #define DEFAULT_ENDPOINT "169.254.169.254" #define CONNECT_TIMEOUT 10 /* in seconds */ +enum cloudname { + AZURE, + CLOUDINIT, + EC2, + OPENNEBULA, + OPENSTACK +}; +#define CLOUDNAMES { \ + "azure", "cloudinit", "ec2", "opennebula", "openstack", NULL \ +} + enum strtype { WORD, LINE, TEXT }; +enum state { + STATE_INIT, + STATE_DHCP, + STATE_169, + STATE_DONE +}; + struct ssh_pubkey { char *ssh_keyval; char *ssh_keyfp; @@ -66,6 +84,14 @@ struct net_addr { }; TAILQ_HEAD(net_addrs, net_addr); +struct system_config; +struct cloud { + enum cloudname cloud_name; + int (*fetch)(struct system_config *); + TAILQ_ENTRY(cloud) cloud_entry; +}; +TAILQ_HEAD(clouds, cloud); + struct system_config { const char *sc_stack; char *sc_args; @@ -77,7 +103,8 @@ struct system_config { char *sc_pubkey; char *sc_userdata; char *sc_endpoint; - int sc_dhcpendpoint; + enum state sc_state; + struct clouds *sc_clouds; char *sc_instance; int sc_timeout; @@ -125,9 +152,6 @@ int azure(struct system_config *); /* cloudinit.c */ int ec2(struct system_config *); int cloudinit(struct system_config *); -int tryendpoint(struct system_config *, - int (fetch)(struct system_config *), - int (next)(struct system_config *)); /* opennebula.c */ int opennebula(struct system_config *); diff --git a/agent/opennebula.c b/agent/opennebula.c index 8395920..69d36e0 100644 --- a/agent/opennebula.c +++ b/agent/opennebula.c @@ -40,12 +40,15 @@ opennebula(struct system_config *sc) int ret = -1; unsigned short unit; + if (sc->sc_state == STATE_INIT) { + sc->sc_state = STATE_169; + return (-1); + } + /* Return silently without error */ if ((fp = fopen("/mnt/context.sh", "r")) == NULL) goto done; - sc->sc_stack = "opennebula"; - while ((line = fparseln(fp, &len, &lineno, delim, FPARSELN_UNESCALL)) != NULL) { /* key */ diff --git a/agent/openstack.c b/agent/openstack.c index c60acd1..9088a1a 100644 --- a/agent/openstack.c +++ b/agent/openstack.c @@ -31,11 +31,14 @@ static int openstack_fetch(struct system_config *); - int openstack(struct system_config *sc) { - return tryendpoint(sc, openstack_fetch, cloudinit); + if (sc->sc_state == STATE_INIT) { + sc->sc_state = STATE_DHCP; + return (-1); + } + return openstack_fetch(sc); } static int @@ -53,7 +56,6 @@ openstack_fetch(struct system_config *sc) if ((json = metadata(sc, "/openstack/latest/meta_data.json", TEXT)) == NULL) goto fail; - sc->sc_stack = "openstack"; if ((j = json_parse(json, strlen(json))) == NULL) goto fail;