Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,33 @@ public Optional<ClusterConnection> getReferencedClusterConnection(
return Optional.of(clusterConnection);
}

/// Sets the phase to [CRPhase#ERROR] for a failure without an exception, and logs the message.
public void setErrorPhase(
CR resource,
S status,
String message
) {
log.warn(
"Failed to reconcile {} [resource={}/{}, message={}]",
resource.getKind(),
resource.getMetadata().getNamespace(),
resource.getMetadata().getName(),
message
);

status.setPhase(CRPhase.ERROR)
.setMessage(message);
}

public <E extends Exception> UpdateControl<CR> handleError(
CR resource,
S status,
E exception
) {
log.error(
"Failed to reconcile resource [resource={}]",
"Failed to reconcile {} [resource={}/{}]",
resource.getKind(),
resource.getMetadata().getNamespace(),
resource.getMetadata().getName(),
exception
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,6 @@ public UpdateControl<ClusterConnection> reconcile(

return UpdateControl.patchStatus(resource);
} catch (RuntimeException e) {
log.error("Failed to check database connectivity", e);

return handleError(
resource,
status,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,17 @@ public UpdateControl<DefaultPrivilege> reconcile(

invalid.removeAll(allowedPrivilegesForObjectType);

status.setPhase(CRPhase.ERROR)
.setMessage("DefaultPrivilege contains invalid privileges for the specified objectType. [resource=%s/%s, objectType=%s, invalidPrivileges=%s, allowedPrivilegesForObjectType=%s]".formatted(
getResourceNamespaceOrOwn(resource, clusterRef.getNamespace()),
clusterRef.getName(),
setErrorPhase(
resource,
status,
"DefaultPrivilege contains invalid privileges for the specified objectType. [resource=%s/%s, objectType=%s, invalidPrivileges=%s, allowedPrivilegesForObjectType=%s]".formatted(
namespace,
name,
objectType,
invalid,
objectType.privileges()
));
)
);

return UpdateControl.patchStatus(resource);
}
Expand Down Expand Up @@ -188,7 +191,8 @@ public DeleteControl cleanup(
"Failed to delete DefaultPrivilege [resource={}/{}, status.phase={}]",
namespace,
name,
status.getPhase()
status.getPhase(),
e
);

status.setMessage("Deletion failed: %s".formatted(e.getMessage()));
Expand Down Expand Up @@ -232,13 +236,16 @@ private UpdateControl<DefaultPrivilege> reconcileInTransaction(
));

if (!unsupportedPrivileges.isEmpty()) {
status.setPhase(CRPhase.ERROR)
.setMessage("The following privileges require a newer PostgreSQL version (current: %d): %s [resource=%s/%s]".formatted(
setErrorPhase(
resource,
status,
"The following privileges require a newer PostgreSQL version (current: %d): %s [resource=%s/%s]".formatted(
databaseMajorVersion,
unsupportedPrivileges,
getResourceNamespaceOrOwn(resource, namespace),
namespace,
name
));
)
);

return UpdateControl.patchStatus(resource);
}
Expand Down Expand Up @@ -269,6 +276,20 @@ private UpdateControl<DefaultPrivilege> reconcileInTransaction(
);
}

if (privilegesToRevoke.isEmpty() && privilegesToGrant.isEmpty()) {
log.info(
"DefaultPrivilege up-to-date [resource={}/{}]",
namespace,
name
);
} else {
log.info(
"Updating DefaultPrivilege [resource={}/{}]",
namespace,
name
);
}

status.setPhase(CRPhase.READY)
.setMessage(null);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,17 @@ public UpdateControl<Grant> reconcile(

invalid.removeAll(allowedPrivilegesForObjectType);

status.setPhase(CRPhase.ERROR)
.setMessage("Grant contains invalid privileges for the specified objectType. [resource=%s/%s, objectType=%s, invalidPrivileges=%s, allowedPrivilegesForObjectType=%s]".formatted(
getResourceNamespaceOrOwn(resource, clusterRef.getNamespace()),
clusterRef.getName(),
setErrorPhase(
resource,
status,
"Grant contains invalid privileges for the specified objectType. [resource=%s/%s, objectType=%s, invalidPrivileges=%s, allowedPrivilegesForObjectType=%s]".formatted(
namespace,
name,
objectType,
invalid,
objectType.privileges()
));
)
);

return UpdateControl.patchStatus(resource);
}
Expand Down Expand Up @@ -197,7 +200,8 @@ public DeleteControl cleanup(
"Failed to delete Grant [resource={}/{}, status.phase={}]",
namespace,
name,
status.getPhase()
status.getPhase(),
e
);

status.setMessage("Deletion failed: %s".formatted(e.getMessage()));
Expand Down Expand Up @@ -252,13 +256,16 @@ private UpdateControl<Grant> reconcileInTransaction(
));

if (!unsupportedPrivileges.isEmpty()) {
status.setPhase(CRPhase.ERROR)
.setMessage("The following privileges require a newer PostgreSQL version (current: %d): %s [resource=%s/%s]".formatted(
setErrorPhase(
resource,
status,
"The following privileges require a newer PostgreSQL version (current: %d): %s [resource=%s/%s]".formatted(
databaseMajorVersion,
unsupportedPrivileges,
getResourceNamespaceOrOwn(resource, namespace),
namespace,
name
));
)
);

return UpdateControl.patchStatus(resource);
}
Expand All @@ -284,18 +291,24 @@ private UpdateControl<Grant> reconcileInTransaction(
});

if (!missingObjects.isEmpty()) {
status.setPhase(CRPhase.ERROR)
.setMessage("Did not grant or revoke any privileges as the listed %s objects do not exist [resource=%s/%s]%n%s".formatted(
setErrorPhase(
resource,
status,
"Did not grant or revoke any privileges as the listed %s objects do not exist [resource=%s/%s]%n • %s".formatted(
objectType,
getResourceNamespaceOrOwn(resource, namespace),
namespace,
name,
String.join("\n • ", missingObjects)
));
)
);

return UpdateControl.patchStatus(resource)
.rescheduleAfter(Duration.ofSeconds(60));
}

// Whether any grant or revoke statement changes a privilege, only used for the log
var updated = false;

// 1. Reconcile objects explicitly listed in the Spec (processObjects).
// We know these are NOT owned (filtered above) and ARE in the spec.
for (var object : processObjects) {
Expand All @@ -312,6 +325,8 @@ private UpdateControl<Grant> reconcileInTransaction(
object,
privilegesToRevoke
);

updated = true;
}

// If we are not in the "ALL" mode, e.g. objects is an empty List, do explicit grants
Expand All @@ -331,17 +346,27 @@ private UpdateControl<Grant> reconcileInTransaction(
object,
privilegesToGrant
);

updated = true;
}
}
}

// 2. Bulk grant ("ALL" mode only)
if (isAllMode) {
grantService.grantOnAll(
var executed = grantService.grantOnAll(
tx,
spec,
expectedPrivileges
);

// The bulk grant changes something only if a non-owned object misses an expected privilege
if (executed) {
updated |= processObjects.stream()
.anyMatch(object -> !currentObjectPrivileges.getOrDefault(object, Set.of())
.containsAll(expectedPrivileges)
);
}
}

// 2. Revoke orphaned object privileges (Objects with privileges but not in Spec)
Expand All @@ -367,14 +392,30 @@ private UpdateControl<Grant> reconcileInTransaction(
object,
privilegesToRevoke
);

updated = true;
}
}

if (updated) {
log.info(
"Updating Grant [resource={}/{}]",
namespace,
name
);
} else {
log.info(
"Grant up-to-date [resource={}/{}]",
namespace,
name
);
}

String message = null;
if (!ownedObjects.isEmpty()) {
message = "The role is the owner of the listed %s objects and thus we did not need to grant or revoke any privileges from them. [resource=%s/%s]%n%s".formatted(
message = "The role is the owner of the listed %s objects and thus we did not need to grant or revoke any privileges from them. [resource=%s/%s]%n • %s".formatted(
objectType,
getResourceNamespaceOrOwn(resource, namespace),
namespace,
name,
String.join("\n • ", ownedObjects)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,11 @@ public void grant(
tx.execute(statement);
}

public void grantOnAll(
/// Grants the privileges on all objects of the objectType in the schema.
/// Only `TABLE` and `SEQUENCE` support this, for all other objectTypes nothing is executed.
///
/// @return `true` if the statement was executed
public boolean grantOnAll(
DSLContext tx,
GrantSpec spec,
Set<Privilege> privilegesToGrant
Expand All @@ -432,7 +436,7 @@ public void grantOnAll(
// grant <privileges> on all <objectType>s in schema <schema> to <role>;
// is only supported by TABLE, SEQUENCE, FUNCTION, PROCEDURE and ROUTINE
if (objectType != TABLE && objectType != SEQUENCE) {
return;
return false;
}

var privileges = privilegesToGrant.stream()
Expand All @@ -448,6 +452,8 @@ public void grantOnAll(
);

tx.execute(statement);

return true;
}

public void revoke(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,12 @@ void errorWhenInvalidPrivileges() {
.extracting(DefaultPrivilege::getStatus)
.satisfies(status -> {
assertThat(status.getPhase()).isEqualTo(CRPhase.ERROR);
assertThat(status.getMessage()).startsWith("DefaultPrivilege contains invalid privileges for the specified objectType");
assertThat(status.getMessage())
.startsWith("DefaultPrivilege contains invalid privileges for the specified objectType")
.contains("[resource=%s/%s,".formatted(
defaultPrivilege.getMetadata().getNamespace(),
defaultPrivilege.getMetadata().getName()
));
});
}

Expand Down
Loading