mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-07 10:06:57 +02:00
Removed the env, and moved to config file changes for ADD endpoints, need to update docs next
This commit is contained in:
parent
c7a05610f4
commit
fe0af65a14
4
main.go
4
main.go
@ -253,9 +253,9 @@ func main() {
|
|||||||
}
|
}
|
||||||
p, err = awssd.NewAWSSDProvider(domainFilter, cfg.AWSZoneType, cfg.DryRun, cfg.AWSSDServiceCleanup, cfg.TXTOwnerID, sd.New(awsSession))
|
p, err = awssd.NewAWSSDProvider(domainFilter, cfg.AWSZoneType, cfg.DryRun, cfg.AWSSDServiceCleanup, cfg.TXTOwnerID, sd.New(awsSession))
|
||||||
case "azure-dns", "azure":
|
case "azure-dns", "azure":
|
||||||
p, err = azure.NewAzureProvider(cfg.AzureConfigFile, domainFilter, zoneNameFilter, zoneIDFilter, cfg.AzureSubscriptionID, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.DryRun)
|
p, err = azure.NewAzureProvider(cfg.AzureConfigFile, domainFilter, zoneNameFilter, zoneIDFilter, cfg.AzureSubscriptionID, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.AzureActiveDirectoryAuthorityHost, cfg.DryRun)
|
||||||
case "azure-private-dns":
|
case "azure-private-dns":
|
||||||
p, err = azure.NewAzurePrivateDNSProvider(cfg.AzureConfigFile, domainFilter, zoneIDFilter, cfg.AzureSubscriptionID, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.DryRun)
|
p, err = azure.NewAzurePrivateDNSProvider(cfg.AzureConfigFile, domainFilter, zoneIDFilter, cfg.AzureSubscriptionID, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.AzureActiveDirectoryAuthorityHost, cfg.DryRun)
|
||||||
case "bluecat":
|
case "bluecat":
|
||||||
p, err = bluecat.NewBluecatProvider(cfg.BluecatConfigFile, cfg.BluecatDNSConfiguration, cfg.BluecatDNSServerName, cfg.BluecatDNSDeployType, cfg.BluecatDNSView, cfg.BluecatGatewayHost, cfg.BluecatRootZone, cfg.TXTPrefix, cfg.TXTSuffix, domainFilter, zoneIDFilter, cfg.DryRun, cfg.BluecatSkipTLSVerify)
|
p, err = bluecat.NewBluecatProvider(cfg.BluecatConfigFile, cfg.BluecatDNSConfiguration, cfg.BluecatDNSServerName, cfg.BluecatDNSDeployType, cfg.BluecatDNSView, cfg.BluecatGatewayHost, cfg.BluecatRootZone, cfg.TXTPrefix, cfg.TXTSuffix, domainFilter, zoneIDFilter, cfg.DryRun, cfg.BluecatSkipTLSVerify)
|
||||||
case "vinyldns":
|
case "vinyldns":
|
||||||
|
@ -101,6 +101,7 @@ type Config struct {
|
|||||||
AzureResourceGroup string
|
AzureResourceGroup string
|
||||||
AzureSubscriptionID string
|
AzureSubscriptionID string
|
||||||
AzureUserAssignedIdentityClientID string
|
AzureUserAssignedIdentityClientID string
|
||||||
|
AzureActiveDirectoryAuthorityHost string
|
||||||
BluecatDNSConfiguration string
|
BluecatDNSConfiguration string
|
||||||
BluecatConfigFile string
|
BluecatConfigFile string
|
||||||
BluecatDNSView string
|
BluecatDNSView string
|
||||||
|
@ -58,6 +58,7 @@ type AzureProvider struct {
|
|||||||
dryRun bool
|
dryRun bool
|
||||||
resourceGroup string
|
resourceGroup string
|
||||||
userAssignedIdentityClientID string
|
userAssignedIdentityClientID string
|
||||||
|
activeDirectoryAuthorityHost string
|
||||||
zonesClient ZonesClient
|
zonesClient ZonesClient
|
||||||
recordSetsClient RecordSetsClient
|
recordSetsClient RecordSetsClient
|
||||||
}
|
}
|
||||||
@ -65,8 +66,8 @@ type AzureProvider struct {
|
|||||||
// NewAzureProvider creates a new Azure provider.
|
// NewAzureProvider creates a new Azure provider.
|
||||||
//
|
//
|
||||||
// Returns the provider or an error if a provider could not be created.
|
// Returns the provider or an error if a provider could not be created.
|
||||||
func NewAzureProvider(configFile string, domainFilter endpoint.DomainFilter, zoneNameFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, subscriptionID string, resourceGroup string, userAssignedIdentityClientID string, dryRun bool) (*AzureProvider, error) {
|
func NewAzureProvider(configFile string, domainFilter endpoint.DomainFilter, zoneNameFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, subscriptionID string, resourceGroup string, userAssignedIdentityClientID string, activeDirectoryAuthorityHost string, dryRun bool) (*AzureProvider, error) {
|
||||||
cfg, err := getConfig(configFile, subscriptionID, resourceGroup, userAssignedIdentityClientID)
|
cfg, err := getConfig(configFile, subscriptionID, resourceGroup, userAssignedIdentityClientID, activeDirectoryAuthorityHost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read Azure config file '%s': %v", configFile, err)
|
return nil, fmt.Errorf("failed to read Azure config file '%s': %v", configFile, err)
|
||||||
}
|
}
|
||||||
@ -90,6 +91,7 @@ func NewAzureProvider(configFile string, domainFilter endpoint.DomainFilter, zon
|
|||||||
dryRun: dryRun,
|
dryRun: dryRun,
|
||||||
resourceGroup: cfg.ResourceGroup,
|
resourceGroup: cfg.ResourceGroup,
|
||||||
userAssignedIdentityClientID: cfg.UserAssignedIdentityID,
|
userAssignedIdentityClientID: cfg.UserAssignedIdentityID,
|
||||||
|
activeDirectoryAuthorityHost: cfg.ActiveDirectoryAuthorityHost,
|
||||||
zonesClient: zonesClient,
|
zonesClient: zonesClient,
|
||||||
recordSetsClient: recordSetsClient,
|
recordSetsClient: recordSetsClient,
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -52,6 +52,7 @@ type AzurePrivateDNSProvider struct {
|
|||||||
dryRun bool
|
dryRun bool
|
||||||
resourceGroup string
|
resourceGroup string
|
||||||
userAssignedIdentityClientID string
|
userAssignedIdentityClientID string
|
||||||
|
activeDirectoryAuthorityHost string
|
||||||
zonesClient PrivateZonesClient
|
zonesClient PrivateZonesClient
|
||||||
recordSetsClient PrivateRecordSetsClient
|
recordSetsClient PrivateRecordSetsClient
|
||||||
}
|
}
|
||||||
@ -59,8 +60,8 @@ type AzurePrivateDNSProvider struct {
|
|||||||
// NewAzurePrivateDNSProvider creates a new Azure Private DNS provider.
|
// NewAzurePrivateDNSProvider creates a new Azure Private DNS provider.
|
||||||
//
|
//
|
||||||
// Returns the provider or an error if a provider could not be created.
|
// Returns the provider or an error if a provider could not be created.
|
||||||
func NewAzurePrivateDNSProvider(configFile string, domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, subscriptionID string, resourceGroup string, userAssignedIdentityClientID string, dryRun bool) (*AzurePrivateDNSProvider, error) {
|
func NewAzurePrivateDNSProvider(configFile string, domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, subscriptionID string, resourceGroup string, userAssignedIdentityClientID string, activeDirectoryAuthorityHost string, dryRun bool) (*AzurePrivateDNSProvider, error) {
|
||||||
cfg, err := getConfig(configFile, subscriptionID, resourceGroup, userAssignedIdentityClientID)
|
cfg, err := getConfig(configFile, subscriptionID, resourceGroup, userAssignedIdentityClientID, activeDirectoryAuthorityHost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read Azure config file '%s': %v", configFile, err)
|
return nil, fmt.Errorf("failed to read Azure config file '%s': %v", configFile, err)
|
||||||
}
|
}
|
||||||
@ -83,6 +84,7 @@ func NewAzurePrivateDNSProvider(configFile string, domainFilter endpoint.DomainF
|
|||||||
dryRun: dryRun,
|
dryRun: dryRun,
|
||||||
resourceGroup: cfg.ResourceGroup,
|
resourceGroup: cfg.ResourceGroup,
|
||||||
userAssignedIdentityClientID: cfg.UserAssignedIdentityID,
|
userAssignedIdentityClientID: cfg.UserAssignedIdentityID,
|
||||||
|
activeDirectoryAuthorityHost: cfg.ActiveDirectoryAuthorityHost,
|
||||||
zonesClient: zonesClient,
|
zonesClient: zonesClient,
|
||||||
recordSetsClient: recordSetsClient,
|
recordSetsClient: recordSetsClient,
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -222,13 +222,13 @@ func createMockRecordSetMultiWithTTL(name, recordType string, ttl int64, values
|
|||||||
}
|
}
|
||||||
|
|
||||||
// newMockedAzureProvider creates an AzureProvider comprising the mocked clients for zones and recordsets
|
// newMockedAzureProvider creates an AzureProvider comprising the mocked clients for zones and recordsets
|
||||||
func newMockedAzureProvider(domainFilter endpoint.DomainFilter, zoneNameFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool, resourceGroup string, userAssignedIdentityClientID string, zones []*dns.Zone, recordSets []*dns.RecordSet) (*AzureProvider, error) {
|
func newMockedAzureProvider(domainFilter endpoint.DomainFilter, zoneNameFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool, resourceGroup string, userAssignedIdentityClientID string, activeDirectoryAuthorityHost string, zones []*dns.Zone, recordSets []*dns.RecordSet) (*AzureProvider, error) {
|
||||||
zonesClient := newMockZonesClient(zones)
|
zonesClient := newMockZonesClient(zones)
|
||||||
recordSetsClient := newMockRecordSetsClient(recordSets)
|
recordSetsClient := newMockRecordSetsClient(recordSets)
|
||||||
return newAzureProvider(domainFilter, zoneNameFilter, zoneIDFilter, dryRun, resourceGroup, userAssignedIdentityClientID, &zonesClient, &recordSetsClient), nil
|
return newAzureProvider(domainFilter, zoneNameFilter, zoneIDFilter, dryRun, resourceGroup, userAssignedIdentityClientID, activeDirectoryAuthorityHost, &zonesClient, &recordSetsClient), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAzureProvider(domainFilter endpoint.DomainFilter, zoneNameFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool, resourceGroup string, userAssignedIdentityClientID string, zonesClient ZonesClient, recordsClient RecordSetsClient) *AzureProvider {
|
func newAzureProvider(domainFilter endpoint.DomainFilter, zoneNameFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool, resourceGroup string, userAssignedIdentityClientID string, activeDirectoryAuthorityHost string, zonesClient ZonesClient, recordsClient RecordSetsClient) *AzureProvider {
|
||||||
return &AzureProvider{
|
return &AzureProvider{
|
||||||
domainFilter: domainFilter,
|
domainFilter: domainFilter,
|
||||||
zoneNameFilter: zoneNameFilter,
|
zoneNameFilter: zoneNameFilter,
|
||||||
@ -236,6 +236,7 @@ func newAzureProvider(domainFilter endpoint.DomainFilter, zoneNameFilter endpoin
|
|||||||
dryRun: dryRun,
|
dryRun: dryRun,
|
||||||
resourceGroup: resourceGroup,
|
resourceGroup: resourceGroup,
|
||||||
userAssignedIdentityClientID: userAssignedIdentityClientID,
|
userAssignedIdentityClientID: userAssignedIdentityClientID,
|
||||||
|
activeDirectoryAuthorityHost: activeDirectoryAuthorityHost,
|
||||||
zonesClient: zonesClient,
|
zonesClient: zonesClient,
|
||||||
recordSetsClient: recordsClient,
|
recordSetsClient: recordsClient,
|
||||||
}
|
}
|
||||||
@ -246,7 +247,7 @@ func validateAzureEndpoints(t *testing.T, endpoints []*endpoint.Endpoint, expect
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAzureRecord(t *testing.T) {
|
func TestAzureRecord(t *testing.T) {
|
||||||
provider, err := newMockedAzureProvider(endpoint.NewDomainFilter([]string{"example.com"}), endpoint.NewDomainFilter([]string{}), provider.NewZoneIDFilter([]string{""}), true, "k8s", "",
|
provider, err := newMockedAzureProvider(endpoint.NewDomainFilter([]string{"example.com"}), endpoint.NewDomainFilter([]string{}), provider.NewZoneIDFilter([]string{""}), true, "k8s", "", "",
|
||||||
[]*dns.Zone{
|
[]*dns.Zone{
|
||||||
createMockZone("example.com", "/dnszones/example.com"),
|
createMockZone("example.com", "/dnszones/example.com"),
|
||||||
},
|
},
|
||||||
@ -286,7 +287,7 @@ func TestAzureRecord(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAzureMultiRecord(t *testing.T) {
|
func TestAzureMultiRecord(t *testing.T) {
|
||||||
provider, err := newMockedAzureProvider(endpoint.NewDomainFilter([]string{"example.com"}), endpoint.NewDomainFilter([]string{}), provider.NewZoneIDFilter([]string{""}), true, "k8s", "",
|
provider, err := newMockedAzureProvider(endpoint.NewDomainFilter([]string{"example.com"}), endpoint.NewDomainFilter([]string{}), provider.NewZoneIDFilter([]string{""}), true, "k8s", "", "",
|
||||||
[]*dns.Zone{
|
[]*dns.Zone{
|
||||||
createMockZone("example.com", "/dnszones/example.com"),
|
createMockZone("example.com", "/dnszones/example.com"),
|
||||||
},
|
},
|
||||||
@ -381,6 +382,7 @@ func testAzureApplyChangesInternal(t *testing.T, dryRun bool, client RecordSetsC
|
|||||||
dryRun,
|
dryRun,
|
||||||
"group",
|
"group",
|
||||||
"",
|
"",
|
||||||
|
"",
|
||||||
&zonesClient,
|
&zonesClient,
|
||||||
client,
|
client,
|
||||||
)
|
)
|
||||||
@ -440,7 +442,7 @@ func testAzureApplyChangesInternal(t *testing.T, dryRun bool, client RecordSetsC
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAzureNameFilter(t *testing.T) {
|
func TestAzureNameFilter(t *testing.T) {
|
||||||
provider, err := newMockedAzureProvider(endpoint.NewDomainFilter([]string{"nginx.example.com"}), endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), true, "k8s", "",
|
provider, err := newMockedAzureProvider(endpoint.NewDomainFilter([]string{"nginx.example.com"}), endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), true, "k8s", "", "",
|
||||||
[]*dns.Zone{
|
[]*dns.Zone{
|
||||||
createMockZone("example.com", "/dnszones/example.com"),
|
createMockZone("example.com", "/dnszones/example.com"),
|
||||||
},
|
},
|
||||||
@ -506,6 +508,7 @@ func testAzureApplyChangesInternalZoneName(t *testing.T, dryRun bool, client Rec
|
|||||||
dryRun,
|
dryRun,
|
||||||
"group",
|
"group",
|
||||||
"",
|
"",
|
||||||
|
"",
|
||||||
&zonesClient,
|
&zonesClient,
|
||||||
client,
|
client,
|
||||||
)
|
)
|
||||||
|
@ -41,9 +41,10 @@ type config struct {
|
|||||||
UseManagedIdentityExtension bool `json:"useManagedIdentityExtension" yaml:"useManagedIdentityExtension"`
|
UseManagedIdentityExtension bool `json:"useManagedIdentityExtension" yaml:"useManagedIdentityExtension"`
|
||||||
UseWorkloadIdentityExtension bool `json:"useWorkloadIdentityExtension" yaml:"useWorkloadIdentityExtension"`
|
UseWorkloadIdentityExtension bool `json:"useWorkloadIdentityExtension" yaml:"useWorkloadIdentityExtension"`
|
||||||
UserAssignedIdentityID string `json:"userAssignedIdentityID" yaml:"userAssignedIdentityID"`
|
UserAssignedIdentityID string `json:"userAssignedIdentityID" yaml:"userAssignedIdentityID"`
|
||||||
|
ActiveDirectoryAuthorityHost string `json:"activeDirectoryAuthorityHost" yaml:"activeDirectoryAuthorityHost"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func getConfig(configFile, subscriptionID, resourceGroup, userAssignedIdentityClientID string) (*config, error) {
|
func getConfig(configFile, subscriptionID, resourceGroup, userAssignedIdentityClientID, activeDirectoryAuthorityHost string) (*config, error) {
|
||||||
contents, err := os.ReadFile(configFile)
|
contents, err := os.ReadFile(configFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read Azure config file '%s': %v", configFile, err)
|
return nil, fmt.Errorf("failed to read Azure config file '%s': %v", configFile, err)
|
||||||
@ -65,6 +66,10 @@ func getConfig(configFile, subscriptionID, resourceGroup, userAssignedIdentityCl
|
|||||||
if userAssignedIdentityClientID != "" {
|
if userAssignedIdentityClientID != "" {
|
||||||
cfg.UserAssignedIdentityID = userAssignedIdentityClientID
|
cfg.UserAssignedIdentityID = userAssignedIdentityClientID
|
||||||
}
|
}
|
||||||
|
// If activeDirectoryAuthorityHost is provided explicitly, override existing one in config file
|
||||||
|
if activeDirectoryAuthorityHost != "" {
|
||||||
|
cfg.ActiveDirectoryAuthorityHost = activeDirectoryAuthorityHost
|
||||||
|
}
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,17 +157,6 @@ func getCloudConfiguration(name string) (cloud.Configuration, error) {
|
|||||||
return cloud.AzureGovernment, nil
|
return cloud.AzureGovernment, nil
|
||||||
case "AZURECHINACLOUD":
|
case "AZURECHINACLOUD":
|
||||||
return cloud.AzureChina, nil
|
return cloud.AzureChina, nil
|
||||||
case "AZURECUSTOMCLOUD":
|
|
||||||
azureAdEndpoint := os.Getenv("AZURE_AD_ENDPOINT")
|
|
||||||
|
|
||||||
if azureAdEndpoint == "" {
|
|
||||||
return cloud.Configuration{}, fmt.Errorf("AD Endpoint Not set: %s", name)
|
|
||||||
} else {
|
|
||||||
customCloud := cloud.Configuration{
|
|
||||||
ActiveDirectoryAuthorityHost: os.Getenv("AZURE_AD_ENDPOINT"),
|
|
||||||
}
|
|
||||||
return customCloud, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return cloud.Configuration{}, fmt.Errorf("unknown cloud name: %s", name)
|
return cloud.Configuration{}, fmt.Errorf("unknown cloud name: %s", name)
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||||||
package azure
|
package azure
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
"path"
|
"path"
|
||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
@ -30,23 +29,14 @@ func TestGetCloudConfiguration(t *testing.T) {
|
|||||||
tests := map[string]struct {
|
tests := map[string]struct {
|
||||||
cloudName string
|
cloudName string
|
||||||
expected cloud.Configuration
|
expected cloud.Configuration
|
||||||
setEnv map[string]string
|
|
||||||
}{
|
}{
|
||||||
"AzureChinaCloud": {"AzureChinaCloud", cloud.AzureChina, nil},
|
"AzureChinaCloud": {"AzureChinaCloud", cloud.AzureChina},
|
||||||
"AzurePublicCloud": {"", cloud.AzurePublic, nil},
|
"AzurePublicCloud": {"", cloud.AzurePublic},
|
||||||
"AzureUSGovernment": {"AzureUSGovernmentCloud", cloud.AzureGovernment, nil},
|
"AzureUSGovernment": {"AzureUSGovernmentCloud", cloud.AzureGovernment},
|
||||||
"AzureCustomCloud": {"AzureCustomCloud", cloud.Configuration{ActiveDirectoryAuthorityHost: "https://custom.microsoftonline.com/"}, map[string]string{"AZURE_AD_ENDPOINT": "https://custom.microsoftonline.com/"}},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, test := range tests {
|
for name, test := range tests {
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
if test.setEnv != nil {
|
|
||||||
for key, value := range test.setEnv {
|
|
||||||
os.Setenv(key, value)
|
|
||||||
defer os.Unsetenv(key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cloudCfg, err := getCloudConfiguration(test.cloudName)
|
cloudCfg, err := getCloudConfiguration(test.cloudName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("got unexpected err %v", err)
|
t.Errorf("got unexpected err %v", err)
|
||||||
@ -61,10 +51,11 @@ func TestGetCloudConfiguration(t *testing.T) {
|
|||||||
func TestOverrideConfiguration(t *testing.T) {
|
func TestOverrideConfiguration(t *testing.T) {
|
||||||
_, filename, _, _ := runtime.Caller(0)
|
_, filename, _, _ := runtime.Caller(0)
|
||||||
configFile := path.Join(path.Dir(filename), "config_test.json")
|
configFile := path.Join(path.Dir(filename), "config_test.json")
|
||||||
cfg, err := getConfig(configFile, "subscription-override", "rg-override", "")
|
cfg, err := getConfig(configFile, "subscription-override", "rg-override", "", "aad-endpoint-override")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("got unexpected err %v", err)
|
t.Errorf("got unexpected err %v", err)
|
||||||
}
|
}
|
||||||
assert.Equal(t, cfg.SubscriptionID, "subscription-override")
|
assert.Equal(t, cfg.SubscriptionID, "subscription-override")
|
||||||
assert.Equal(t, cfg.ResourceGroup, "rg-override")
|
assert.Equal(t, cfg.ResourceGroup, "rg-override")
|
||||||
|
assert.Equal(t, cfg.ActiveDirectoryAuthorityHost, "aad-endpoint-override")
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user