static int VerifyTablePromise(CfdbConn *cfdb, char *table_path, Rlist *columns, Attributes a, Promise *pp) { char name[CF_MAXVARSIZE], type[CF_MAXVARSIZE], query[CF_MAXVARSIZE], table[CF_MAXVARSIZE], db[CF_MAXVARSIZE]; int i, count, size, no_of_cols, *size_table, *done, identified, retval = true; char **name_table, **type_table; CfOut(cf_verbose, "", " -> Verifying promised table structure for \"%s\"", table_path); if (!ValidateSQLTableName(table_path, db, table)) { CfOut(cf_error, "", " !! The structure of the promiser did not match that for an SQL table, i.e. \"database.table\"\n"); return false; } else { CfOut(cf_verbose, "", " -> Assuming database \"%s\" with table \"%s\"", db, table); } /* Verify the existence of the tables within the database */ if (!TableExists(cfdb, table)) { CfOut(cf_error, "", " !! The database did not contain the promised table \"%s\"\n", table_path); if ((a.database.operation) && (strcmp(a.database.operation, "create") == 0)) { if ((!DONTDO) && ((a.transaction.action) != cfa_warn)) { cfPS(cf_error, CF_CHG, "", pp, a, " -> Database.table %s doesn't seem to exist, creating\n", table_path); return CreateTableColumns(cfdb, table, columns, a, pp); } else { CfOut(cf_error, "", " -> Database.table %s doesn't seem to exist, but only a warning was promised\n", table_path); } } return false; } /* Get a list of the columns in the table */ QueryTableColumns(query, db, table); CfNewQueryDB(cfdb, query); if (cfdb->maxcolumns != 3) { cfPS(cf_error, CF_FAIL, "", pp, a, "Could not make sense of the columns"); CfDeleteQuery(cfdb); return false; } /* Assume that the Rlist has been validated and consists of a,b,c */ count = 0; no_of_cols = RlistLen(columns); if (!NewSQLColumns(table, columns, &name_table, &type_table, &size_table, &done)) { cfPS(cf_error, CF_FAIL, "", pp, a, "Could not make sense of the columns"); return false; } /* Obtain columns from the named table - if any */ while (CfFetchRow(cfdb)) { char *sizestr; name[0] = '\0'; type[0] = '\0'; size = CF_NOINT; strlcpy(name, CfFetchColumn(cfdb, 0), CF_MAXVARSIZE); strlcpy(type, CfFetchColumn(cfdb, 1), CF_MAXVARSIZE); ToLowerStrInplace(type); sizestr = CfFetchColumn(cfdb, 2); if (sizestr) { size = Str2Int(sizestr); } CfOut(cf_verbose, "", " ... discovered column (%s,%s,%d)", name, type, size); if (sizestr && (size == CF_NOINT)) { cfPS(cf_verbose, CF_NOP, "", pp, a, " !! Integer size of SQL datatype could not be determined or was not specified - invalid promise."); DeleteSQLColumns(name_table, type_table, size_table, done, no_of_cols); CfDeleteQuery(cfdb); return false; } identified = false; for (i = 0; i < no_of_cols; i++) { if (done[i]) { continue; } if (strcmp(name, name_table[i]) == 0) { CheckSQLDataType(type, type_table[i], pp); if (size != size_table[i]) { cfPS(cf_error, CF_FAIL, "", pp, a, " !! Promised column \"%s\" in database.table \"%s\" has a non-matching array size (%d != %d)", name, table_path, size, size_table[i]); } else { CfOut(cf_verbose, "", " -> Promised column \"%s\" in database.table \"%s\" is as promised", name, table_path); } count++; done[i] = true; identified = true; break; } } if (!identified) { cfPS(cf_error, CF_FAIL, "", pp, a, "Column \"%s\" found in database.table \"%s\" is not part of its promise.", name, table_path); if ((a.database.operation) && (strcmp(a.database.operation, "drop") == 0)) { cfPS(cf_error, CF_FAIL, "", pp, a, "Cfengine will not promise to repair this, as the operation is potentially too destructive."); // Future allow deletion? } retval = false; } } CfDeleteQuery(cfdb); /* Now look for deviations - only if we have promised to create missing */ if ((a.database.operation) && (strcmp(a.database.operation, "drop") == 0)) { return retval; } if (count != no_of_cols) { for (i = 0; i < no_of_cols; i++) { if (!done[i]) { CfOut(cf_error, "", " !! Promised column \"%s\" missing from database table %s", name_table[i], pp->promiser); if ((!DONTDO) && ((a.transaction.action) != cfa_warn)) { if (size_table[i] > 0) { snprintf(query, CF_MAXVARSIZE - 1, "ALTER TABLE %s ADD %s %s(%d)", table, name_table[i], type_table[i], size_table[i]); } else { snprintf(query, CF_MAXVARSIZE - 1, "ALTER TABLE %s ADD %s %s", table, name_table[i], type_table[i]); } CfVoidQueryDB(cfdb, query); cfPS(cf_error, CF_CHG, "", pp, a, " !! Adding promised column \"%s\" to database table %s", name_table[i], table); retval = true; } else { cfPS(cf_error, CF_WARN, "", pp, a, " !! Promised column \"%s\" missing from database table %s but only a warning was promised", name_table[i], table); retval = false; } } } } DeleteSQLColumns(name_table, type_table, size_table, done, no_of_cols); return retval; }
static int VerifyTablePromise(EvalContext *ctx, CfdbConn *cfdb, char *table_path, Rlist *columns, const Attributes *a, const Promise *pp, PromiseResult *result) { assert(a != NULL); char name[CF_MAXVARSIZE], type[CF_MAXVARSIZE], query[CF_MAXVARSIZE], table[CF_MAXVARSIZE], db[CF_MAXVARSIZE]; int i, count, size, no_of_cols, *size_table, *done, identified, retval = true; char **name_table, **type_table; Log(LOG_LEVEL_VERBOSE, "Verifying promised table structure for '%s'", table_path); if (!ValidateSQLTableName(table_path, db, table)) { Log(LOG_LEVEL_ERR, "The structure of the promiser did not match that for an SQL table, i.e. 'database.table'"); return false; } else { Log(LOG_LEVEL_VERBOSE, "Assuming database '%s' with table '%s'", db, table); } /* Verify the existence of the tables within the database */ if (!TableExists(cfdb, table)) { Log(LOG_LEVEL_ERR, "The database did not contain the promised table '%s'", table_path); if ((a->database.operation) && (strcmp(a->database.operation, "create") == 0)) { if ((!DONTDO) && ((a->transaction.action) != cfa_warn)) { cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_CHANGE, pp, a, "Database.table '%s' doesn't seem to exist, creating", table_path); *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE); return CreateTableColumns(cfdb, table, columns); } else { Log(LOG_LEVEL_WARNING, "Database.table '%s' doesn't seem to exist, but only a warning was promised", table_path); } } return false; } /* Get a list of the columns in the table */ QueryTableColumns(query, db, table); CfNewQueryDB(cfdb, query); if (cfdb->maxcolumns != 3) { cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Could not make sense of the columns"); *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL); CfDeleteQuery(cfdb); return false; } /* Assume that the Rlist has been validated and consists of a,b,c */ count = 0; no_of_cols = RlistLen(columns); if (!NewSQLColumns(table, columns, &name_table, &type_table, &size_table, &done)) { cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Could not make sense of the columns"); *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL); return false; } /* Obtain columns from the named table - if any */ while (CfFetchRow(cfdb)) { char *sizestr; name[0] = '\0'; type[0] = '\0'; size = CF_NOINT; strlcpy(name, CfFetchColumn(cfdb, 0), CF_MAXVARSIZE); strlcpy(type, CfFetchColumn(cfdb, 1), CF_MAXVARSIZE); ToLowerStrInplace(type); sizestr = CfFetchColumn(cfdb, 2); if (sizestr) { size = IntFromString(sizestr); } Log(LOG_LEVEL_VERBOSE, "Discovered database column (%s,%s,%d)", name, type, size); if (sizestr && (size == CF_NOINT)) { cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, a, "Integer size of SQL datatype could not be determined or was not specified - invalid promise."); DeleteSQLColumns(name_table, type_table, size_table, done, no_of_cols); CfDeleteQuery(cfdb); return false; } identified = false; for (i = 0; i < no_of_cols; i++) { if (done[i]) { continue; } if (strcmp(name, name_table[i]) == 0) { CheckSQLDataType(type, type_table[i], pp); if (size != size_table[i]) { cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Promised column '%s' in database.table '%s' has a non-matching array size (%d != %d)", name, table_path, size, size_table[i]); *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL); } else { Log(LOG_LEVEL_VERBOSE, "Promised column '%s' in database.table '%s' is as promised", name, table_path); } count++; done[i] = true; identified = true; break; } } if (!identified) { cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Column '%s' found in database.table '%s' is not part of its promise.", name, table_path); *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL); if ((a->database.operation) && (strcmp(a->database.operation, "drop") == 0)) { cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "CFEngine will not promise to repair this, as the operation is potentially too destructive."); // Future allow deletion? *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL); } retval = false; } } CfDeleteQuery(cfdb); /* Now look for deviations - only if we have promised to create missing */ if ((a->database.operation) && (strcmp(a->database.operation, "drop") == 0)) { return retval; } if (count != no_of_cols) { for (i = 0; i < no_of_cols; i++) { if (!done[i]) { Log(LOG_LEVEL_ERR, "Promised column '%s' missing from database table '%s'", name_table[i], pp->promiser); if ((!DONTDO) && ((a->transaction.action) != cfa_warn)) { if (size_table[i] > 0) { snprintf(query, CF_MAXVARSIZE - 1, "ALTER TABLE %s ADD %s %s(%d)", table, name_table[i], type_table[i], size_table[i]); } else { snprintf(query, CF_MAXVARSIZE - 1, "ALTER TABLE %s ADD %s %s", table, name_table[i], type_table[i]); } CfVoidQueryDB(cfdb, query); cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_CHANGE, pp, a, "Adding promised column '%s' to database table '%s'", name_table[i], table); *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE); retval = true; } else { cfPS(ctx, LOG_LEVEL_WARNING, PROMISE_RESULT_WARN, pp, a, "Promised column '%s' missing from database table '%s' but only a warning was promised", name_table[i], table); *result = PromiseResultUpdate(*result, PROMISE_RESULT_WARN); retval = false; } } } } DeleteSQLColumns(name_table, type_table, size_table, done, no_of_cols); return retval; }