Merge pull request #7 from reyk/probe-order

Allow to specify the probed cloud stacks with -c cloud[,cloud...]
This commit is contained in:
reykfloeter 2019-06-11 12:18:02 +02:00 committed by GitHub
commit 678b29acef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 210 additions and 85 deletions

View file

@ -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");

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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 *);

View file

@ -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 */

View file

@ -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;