All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.microsoft.azure.toolkit.lib.common.model.AbstractAzResourceModule Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License. See License.txt in the project root for license information.
 */

package com.microsoft.azure.toolkit.lib.common.model;

import com.azure.core.exception.HttpResponseException;
import com.azure.core.util.paging.ContinuablePage;
import com.azure.resourcemanager.resources.fluentcore.arm.ResourceId;
import com.azure.resourcemanager.resources.fluentcore.arm.collection.SupportsGettingById;
import com.azure.resourcemanager.resources.fluentcore.arm.collection.SupportsGettingByName;
import com.azure.resourcemanager.resources.fluentcore.arm.collection.SupportsGettingByResourceGroup;
import com.azure.resourcemanager.resources.fluentcore.collection.SupportsDeletingById;
import com.azure.resourcemanager.resources.fluentcore.collection.SupportsListing;
import com.google.common.collect.Sets;
import com.microsoft.azure.toolkit.lib.AzService;
import com.microsoft.azure.toolkit.lib.Azure;
import com.microsoft.azure.toolkit.lib.account.IAzureAccount;
import com.microsoft.azure.toolkit.lib.common.event.AzureEventBus;
import com.microsoft.azure.toolkit.lib.common.exception.AzureToolkitRuntimeException;
import com.microsoft.azure.toolkit.lib.common.model.page.ItemPage;
import com.microsoft.azure.toolkit.lib.common.operation.AzureOperation;
import com.microsoft.azure.toolkit.lib.common.operation.Operation;
import com.microsoft.azure.toolkit.lib.common.task.AzureTaskManager;
import com.microsoft.azure.toolkit.lib.common.telemetry.AzureTelemeter;
import com.microsoft.azure.toolkit.lib.common.telemetry.AzureTelemetry;
import com.microsoft.azure.toolkit.lib.common.utils.Debouncer;
import com.microsoft.azure.toolkit.lib.common.utils.TailingDebouncer;
import com.microsoft.azure.toolkit.lib.resource.GenericResource;
import com.microsoft.azure.toolkit.lib.resource.GenericResourceModule;
import com.microsoft.azure.toolkit.lib.resource.ResourceDeployment;
import com.microsoft.azure.toolkit.lib.resource.ResourceGroup;
import com.microsoft.azure.toolkit.lib.resource.ResourceGroupModule;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.lang3.tuple.Pair;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

import static com.microsoft.azure.toolkit.lib.common.model.AbstractAzResource.is400;
import static com.microsoft.azure.toolkit.lib.common.model.AbstractAzResource.is404;
import static com.microsoft.azure.toolkit.lib.common.model.AzResource.RESOURCE_GROUP_PLACEHOLDER;
import static com.microsoft.azure.toolkit.lib.common.telemetry.AzureTelemeter.OPERATION_NAME;
import static com.microsoft.azure.toolkit.lib.common.telemetry.AzureTelemeter.OP_NAME;
import static com.microsoft.azure.toolkit.lib.common.telemetry.AzureTelemeter.OP_TYPE;
import static com.microsoft.azure.toolkit.lib.common.telemetry.AzureTelemeter.SERVICE_NAME;

@Slf4j
@RequiredArgsConstructor
@ToString(onlyExplicitlyIncluded = true)
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public abstract class AbstractAzResourceModule, P extends AzResource, R>
    implements AzResourceModule {
    @Getter
    @Nonnull
    @ToString.Include
    @EqualsAndHashCode.Include
    private final String name;
    @Getter
    @EqualsAndHashCode.Include
    protected final P parent; // null only in NONE.
    @Nonnull
    @ToString.Include
    protected final AtomicLong syncTimeRef = new AtomicLong(-1);
    @Nonnull
    protected final Map> resources = Collections.synchronizedMap(new LinkedHashMap<>());
    private final Map tempResources = Collections.synchronizedMap(new LinkedHashMap<>());

    @Nonnull
    private final Debouncer fireEvents = new TailingDebouncer(this::fireChildrenChangedEvent, 300);
    private final Lock lock = new ReentrantLock();
    private Iterator> pages;

    @Override
    public void refresh() {
        log.debug("[{}]:refresh()", this.name);
        this.invalidateCache();
        AzureEventBus.emit("module.refreshed.module", this);
        AzureEventBus.emit("resource.children_changed.resource", this.getParent());
    }

    protected void invalidateCache() {
        log.debug("[{}]:invalidateCache()", this.name);
        if (this.lock.tryLock()) {
            try {
                this.resources.entrySet().removeIf(e -> !e.getValue().isPresent());
                this.syncTimeRef.set(-1);
            } finally {
                this.lock.unlock();
            }
        }
        log.debug("[{}]:invalidateCache->resources.invalidateCache()", this.name);
        final Collection> values = new ArrayList<>(this.resources.values());
        values.forEach(v -> v.ifPresent(AbstractAzResource::invalidateCache));
    }

    @Nonnull
    @Override
    public List list() { // getResources
        log.debug("[{}]:list()", this.name);
        if (isAuthRequiredForListing()) {
            Azure.az(IAzureAccount.class).account();
        }
        if (this.parent instanceof AbstractAzResource && ((AbstractAzResource) this.parent).isDraftForCreating()) {
            log.debug("[{}]:list->parent.isDraftForCreating()=true", this.name);
            return Collections.emptyList();
        }
        if (this.syncTimeRef.get() < 1) { // 0, -1 or too old.
            try {
                this.lock.lock();
                if (this.syncTimeRef.get() == -1) { // -1 or too old.
                    log.debug("[{}]:list->this.reload()", this.name);
                    this.reloadResources();
                }
            } finally {
                this.lock.unlock();
            }
        }
        log.debug("[{}]:list->this.resources.values()", this.name);
        return this.resources.values().stream().filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList());
    }

    private void reloadResources() {
        log.debug("[{}]:reloadResources()", this.name);
        this.syncTimeRef.set(0);
        try {
            log.debug("[{}]:reloadResources->loadResourcePagesFromAzure()", this.name);
            final Map loadedResources = getResourcesFromAzure();
            this.telemeterResourceCount(loadedResources);
            log.debug("[{}]:reloadResources->setResources(xxx)", this.name);
            this.setResources(loadedResources);
        } catch (final Exception e) {
            log.debug("[{}]:reloadResources->setResources([])", this.name);
            if (is404(e)) {
                log.debug("[{}]:reloadResources->loadResourceFromAzure()=SC_NOT_FOUND", this.name, e);
                this.setResources(Collections.emptyMap());
            } else {
                log.debug("[{}]:reloadResources->loadResourcePagesFromAzure()=EXCEPTION", this.name, e);
                this.resources.clear();
                this.syncTimeRef.compareAndSet(0, System.currentTimeMillis());
                throw e;
            }
        }
    }

    private void telemeterResourceCount(final Map loadedResources) {
        final Map properties = new HashMap<>();
        final String service = this.getServiceNameForTelemetry();
        properties.put(SERVICE_NAME, service);
        properties.put(OPERATION_NAME, "count_resources");
        properties.put(OP_NAME, service + ".count_resources");
        properties.put(OP_TYPE, Operation.Type.AZURE);
        properties.put("resourceType", this.getFullResourceType());
        properties.put("subscriptionId", this.getSubscriptionId());
        properties.put("count", loadedResources.size() + "");
        AzureTelemeter.log(AzureTelemetry.Type.INFO, properties);
    }

    protected Map getResourcesFromAzure() {
        this.pages = this.loadResourcePagesFromAzure();
        final ContinuablePage page = pages.hasNext() ? pages.next() : new ItemPage<>(Collections.emptyList());
        return page.getElements().stream()
            .collect(Collectors.toMap(r -> this.newResource(r).getId().toLowerCase(), r -> r));
    }

    public void loadMoreResources() {
        log.debug("[{}]:loadMoreResources()", this.name);
        try {
            this.lock.lock();
            if (Objects.isNull(this.pages)) {
                this.reloadResources();
            } else if (this.pages.hasNext()) {
                final ContinuablePage page = this.pages.next();
                final Map loadedResources = page.getElements().stream()
                    .collect(Collectors.toMap(r -> this.newResource(r).getId().toLowerCase(), r -> r));
                log.debug("[{}]:loadMoreResources->addResources(xxx)", this.name);
                this.addResources(loadedResources);
                fireEvents.debounce();
            }
        } finally {
            this.lock.unlock();
        }
    }

    public boolean hasMoreResources() {
        return Objects.nonNull(this.pages) && this.pages.hasNext();
    }

    private void setResources(Map loadedResources) {
        final Set localResources = this.resources.values().stream().filter(Optional::isPresent).map(Optional::get)
            .map(AbstractAzResource::getId).map(String::toLowerCase).collect(Collectors.toSet());
        final Set creating = this.resources.values().stream().filter(Optional::isPresent).map(Optional::get)
            .filter(AbstractAzResource::isDraftForCreating)
            .map(AbstractAzResource::getId).map(String::toLowerCase).collect(Collectors.toSet());
        log.debug("[{}]:reload().creating={}", this.name, creating);
        final Sets.SetView refreshed = Sets.intersection(localResources, loadedResources.keySet());
        log.debug("[{}]:reload().refreshed={}", this.name, refreshed);
        final Sets.SetView deleted = Sets.difference(Sets.difference(localResources, loadedResources.keySet()), creating);
        log.debug("[{}]:reload().deleted={}", this.name, deleted);
        final Sets.SetView added = Sets.difference(loadedResources.keySet(), localResources);
        log.debug("[{}]:reload().added={}", this.name, added);
        log.debug("[{}]:reload.deleted->deleteResourceFromLocal", this.name);
        deleted.forEach(id -> this.resources.getOrDefault(id, Optional.empty()).ifPresent(r -> {
            r.deleteFromCache();
            r.setRemote(null);
        }));

        final AzureTaskManager m = AzureTaskManager.getInstance();
        log.debug("[{}]:reload.refreshed->resource.setRemote", this.name);
        refreshed.forEach(id -> this.resources.getOrDefault(id, Optional.empty()).ifPresent(r -> m.runOnPooledThread(() -> r.setRemote(loadedResources.get(id)))));
        log.debug("[{}]:reload.added->addResourceToLocal", this.name);
        final Map newResources = new HashMap<>();
        added.forEach(id -> newResources.put(id, loadedResources.get(id)));
        addResources(newResources);
        this.syncTimeRef.set(System.currentTimeMillis());
    }

    protected void addResources(Map loadedResources) {
        final Set added = loadedResources.keySet();
        log.debug("[{}]:reload().added={}", this.name, added);
        loadedResources.values().stream().map(r -> Pair.of(r, this.newResource(r)))
            .sorted(Comparator.comparing(p -> p.getValue().getName())) // sort by name when adding into cache
            .forEach(p -> {
                final R remote = p.getKey();
                final T resource = p.getValue();
                AzureTaskManager.getInstance().runOnPooledThread(() -> resource.setRemote(remote));
                this.addResourceToLocal(resource.getId(), resource, true);
            });
        this.syncTimeRef.set(System.currentTimeMillis());
    }

    public void clear() {
        log.debug("[{}]:clear()", this.name);
        try {
            this.lock.lock();
            this.resources.clear();
            this.syncTimeRef.set(-1);
        } finally {
            this.lock.unlock();
        }
    }

    @Nullable
    @Override
    public T get(@Nonnull String name, @Nullable String rgName) {
        final String resourceGroup = normalizeResourceGroupName(name, rgName);
        log.debug("[{}]:get({}, {})", this.name, name, resourceGroup);
        if (StringUtils.isBlank(name) || (this.parent instanceof AbstractAzResource && ((AbstractAzResource) this.parent).isDraftForCreating())) {
            log.debug("[{}]:get->parent.isDraftForCreating()=true||isBlank(name)=true", this.name);
            return null;
        }
        final String id = this.toResourceId(name, resourceGroup).toLowerCase();
        if (isAuthRequiredForResource(id)) {
            Azure.az(IAzureAccount.class).account();
        }
        if (!this.resources.containsKey(id)) {
            R remote = null;
            try {
                log.debug("[{}]:get({}, {})->loadResourceFromAzure()", this.name, name, resourceGroup);
                remote = loadResourceFromAzure(name, resourceGroup);
            } catch (final Exception e) {
                log.debug("[{}]:get({}, {})->loadResourceFromAzure()=EXCEPTION", this.name, name, resourceGroup, e);
                final Throwable cause = e instanceof HttpResponseException ? e : ExceptionUtils.getRootCause(e);
                if (cause instanceof HttpResponseException && !is404(e) && !is400(e)) {
                    log.debug("[{}]:get({}, {})->loadResourceFromAzure()=SC_NOT_FOUND", this.name, name, resourceGroup, e);
                    throw e;
                }
            }
            if (Objects.isNull(remote)) {
                log.debug("[{}]:get({}, {})->addResourceToLocal({}, null)", this.name, name, resourceGroup, name);
                this.addResourceToLocal(id, null, true);
            } else {
                final T resource = newResource(remote);
                resource.setRemote(remote);
                log.debug("[{}]:get({}, {})->addResourceToLocal({}, resource)", this.name, name, resourceGroup, name);
                this.addResourceToLocal(resource.getId(), resource, true);
            }
        }
        log.debug("[{}]:get({}, {})->this.resources.get({})", this.name, id, resourceGroup, name);
        return this.resources.getOrDefault(id, Optional.empty()).orElse(null);
    }

    @Nullable
    public T get(@Nonnull String resourceId) {
        final ResourceId id = ResourceId.fromString(resourceId);
        log.debug("[{}]:get({})", this.name, resourceId);
        return this.get(id.name(), id.resourceGroupName());
    }

    @Override
    public boolean exists(@Nonnull String name, @Nullable String rgName) {
        final String resourceGroup = normalizeResourceGroupName(name, rgName);
        log.debug("[{}]:exists({}, {})", this.name, name, resourceGroup);
        final T resource = this.get(name, resourceGroup);
        return Objects.nonNull(resource) && resource.exists();
    }

    @Override
    public void delete(@Nonnull String name, @Nullable String rgName) {
        final String resourceGroup = normalizeResourceGroupName(name, rgName);
        log.debug("[{}]:delete({}, {})", this.name, name, resourceGroup);
        log.debug("[{}]:delete->this.get({}, {})", this.name, name, resourceGroup);
        final T resource = this.get(name, resourceGroup);
        if (Objects.nonNull(resource)) {
            log.debug("[{}]:delete->resource.delete()", this.name);
            resource.delete();
        } else {
            throw new AzureToolkitRuntimeException(String.format("resource \"%s\" doesn't exist", name));
        }
    }

    @Nonnull
    public T getOrDraft(@Nonnull String name, @Nullable String rgName) {
        final String resourceGroup = normalizeResourceGroupName(name, rgName);
        log.debug("[{}]:getOrDraft({}, {})", this.name, name, resourceGroup);
        final String id = this.toResourceId(name, resourceGroup).toLowerCase();
        return Optional.ofNullable(this.get(name, resourceGroup))
            .orElseGet(() -> this.tempResources.computeIfAbsent(id, (i) -> this.cast(this.newDraftForCreate(name, resourceGroup))));
    }

    @Nonnull
    public T getOrTemp(@Nonnull String name, @Nullable String rgName) {
        final String resourceGroup = normalizeResourceGroupName(name, rgName);
        log.debug("[{}]:getOrTemp({}, {})", this.name, name, rgName);
        final String id = this.toResourceId(name, resourceGroup).toLowerCase();
        return Optional.ofNullable(this.get(name, resourceGroup))
            .orElseGet(() -> this.tempResources.computeIfAbsent(id, (i) -> this.newResource(name, resourceGroup)));
    }

    @Nonnull
    public T getOrInit(@Nonnull String name, @Nullable String rgName) {
        final String resourceGroup = normalizeResourceGroupName(name, rgName);
        log.debug("[{}]:getOrDraft({}, {})", this.name, name, rgName);
        final String id = this.toResourceId(name, resourceGroup).toLowerCase();
        return Optional.ofNullable(this.get(name, resourceGroup))
            .orElseGet(() -> {
                final T resource = this.newResource(name, resourceGroup);
                log.debug("[{}]:get({}, {})->addResourceToLocal({}, resource)", this.name, id, resourceGroup, name);
                this.addResourceToLocal(id, resource);
                return resource;
            });
    }

    @Nonnull
    public List listCachedResources() { // getResources
        return this.resources.values().stream().filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList());
    }

    @Nonnull
    public List listByResourceGroup(@Nonnull String resourceGroup) {
        log.debug("[{}]:listByResourceGroupName({})", this.name, resourceGroup);
        return this.list().stream().filter(r -> r.getResourceGroupName().equalsIgnoreCase(resourceGroup)).collect(Collectors.toList());
    }

    @Nonnull
    public > D updateOrCreate(@Nonnull String name, @Nullable String rgName) {
        final String resourceGroup = normalizeResourceGroupName(name, rgName);
        log.debug("[{}]:updateOrCreate({}, {})", this.name, name, resourceGroup);
        final T resource = this.get(name, resourceGroup);
        if (Objects.nonNull(resource)) {
            return this.cast(this.newDraftForUpdate(resource));
        }
        return this.create(name, rgName);
    }

    @Nonnull
    public > D create(@Nonnull String name, @Nullable String rgName) {
        final String resourceGroup = normalizeResourceGroupName(name, rgName);
        log.debug("[{}]:create({}, {})", this.name, name, resourceGroup);
        // TODO: use generics to avoid class casting
        log.debug("[{}]:create->newDraftForCreate({}, {})", this.name, name, resourceGroup);
        final String id = this.toResourceId(name, resourceGroup).toLowerCase();
        T resource = this.tempResources.get(id);
        if (!Objects.nonNull(resource) || !resource.isDraftForCreating()) {
            resource = this.cast(this.newDraftForCreate(name, resourceGroup));
            this.tempResources.put(id, resource);
        }
        return this.cast(resource);
    }

    @Nonnull
    @Override
    public T create(@Nonnull AzResource.Draft d) {
        final AzResource.Draft draft = this.cast(d);
        log.debug("[{}]:create(draft:{})", this.name, draft);
        final T existing = this.get(draft.getName(), draft.getResourceGroupName());
        if (Objects.isNull(existing)) {
            final T resource = cast(draft);
            final P parent = Objects.requireNonNull(resource.getParent());
            if (parent instanceof AbstractAzResource && ((AbstractAzResource) parent).isDraftForCreating()) {
                ((AzResource.Draft) parent).createIfNotExist();
            }
            // this will notify azure explorer to show a draft resource first
            log.debug("[{}]:create->addResourceToLocal({})", this.name, resource);
            this.addResourceToLocal(resource.getId(), resource);
            AzureEventBus.emit("resource.creation_started.resource", resource);
            log.debug("[{}]:create->doModify(draft.createResourceInAzure({}))", this.name, resource);
            try {
                resource.doModify(draft::createResourceInAzure, AzResource.Status.CREATING);
            } catch (final RuntimeException e) {
                resource.setStatus(AzResource.Status.ERROR);
                resource.deleteFromCache();
                throw AzureToolkitRuntimeException.addDefaultActions(e);
            }
            AzureEventBus.emit("azure.explorer.highlight_resource", resource);
            return resource;
        }
        throw new AzureToolkitRuntimeException(String.format("resource \"%s\" is existing", existing.getName()));
    }

    @Nonnull
    > D update(@Nonnull T resource) {
        log.debug("[{}]:update(resource:{})", this.name, resource);
        if (resource.isDraftForCreating()) {
            log.warn("[{}]:updating(resource:{}) from a draftForCreating", this.name, resource);
        }
        if (resource.isDraftForUpdating()) {
            return this.cast(resource);
        }
        log.debug("[{}]:update->newDraftForUpdate({})", this.name, resource);
        final T draft = this.cast(this.newDraftForUpdate(resource));
        return this.cast(draft);
    }

    @Nonnull
    @Override
    public T update(@Nonnull AzResource.Draft draft) {
        final AzResource.Draft d = this.cast(draft);
        log.debug("[{}]:update(draft:{})", this.name, draft);
        final T resource = this.get(draft.getName(), draft.getResourceGroupName());
        if (Objects.nonNull(resource) && Objects.nonNull(resource.getRemote())) {
            log.debug("[{}]:update->doModify(draft.updateResourceInAzure({}))", this.name, resource.getRemote());
            resource.doModify(() -> d.updateResourceInAzure(Objects.requireNonNull(resource.getRemote())), AzResource.Status.UPDATING);
            return resource;
        }
        throw new AzureToolkitRuntimeException(String.format("resource \"%s\" doesn't exist", draft.getName()));
    }

    @Nonnull
    @SneakyThrows(UnsupportedEncodingException.class)
    public String toResourceId(@Nonnull String resourceName, @Nullable String resourceGroup) {
        resourceGroup = StringUtils.firstNonBlank(resourceGroup, this.getParent().getResourceGroupName(), RESOURCE_GROUP_PLACEHOLDER);
        // resource (ACR repository) name may contain "/".
        final String encoded = URLEncoder.encode(resourceName, "UTF-8");
        return String.format("%s/%s/%s", this.parent.getId(), this.getName(), encoded).replace(RESOURCE_GROUP_PLACEHOLDER, resourceGroup);
    }

    protected void deleteResourceFromLocal(@Nonnull String id, boolean... silent) {
        log.debug("[{}]:deleteResourceFromLocal({})", this.name, id);
        log.debug("[{}]:deleteResourceFromLocal->this.resources.remove({})", this.name, id);
        id = id.toLowerCase();
        final Optional removed = this.resources.remove(id);
        if (Objects.nonNull(removed) && removed.isPresent()) {
            this.deleteResourceFromLocalResourceGroup(removed.get(), silent);
            if ((silent.length == 0 || !silent[0])) {
                log.debug("[{}]:deleteResourceFromLocal->fireResourcesChangedEvent()", this.name);
                fireEvents.debounce();
            }
        }
    }

    protected void deleteResourceFromLocalResourceGroup(@Nonnull T resource, boolean... silent) {
        final ResourceId rId = ResourceId.fromString(resource.getId());
        final ResourceGroup resourceGroup = resource.getResourceGroup();
        if (Objects.isNull(rId.parent()) && Objects.nonNull(resourceGroup) &&
            !(resource instanceof ResourceGroup) && !(resource instanceof ResourceDeployment)) {
            final GenericResourceModule genericResourceModule = resourceGroup.genericResources();
            genericResourceModule.deleteResourceFromLocal(resource.getId(), silent);
        }
    }

    protected void addResourceToLocal(@Nonnull String id, @Nullable T resource, boolean... silent) {
        log.debug("[{}]:addResourceToLocal({}, {})", this.name, id, resource);
        id = id.toLowerCase();
        final Optional oldResource = this.resources.getOrDefault(id, Optional.empty());
        final Optional newResource = Optional.ofNullable(resource);
        if (!oldResource.isPresent()) {
            log.debug("[{}]:addResourceToLocal->this.resources.put({}, {})", this.name, id, resource);
            this.resources.put(id, newResource);
            if (newResource.isPresent()) {
                this.addResourceToLocalResourceGroup(id, resource, silent);
                if (silent.length == 0 || !silent[0]) {
                    log.debug("[{}]:addResourceToLocal->fireResourcesChangedEvent()", this.name);
                    fireEvents.debounce();
                }
            }
        }
    }

    protected void addResourceToLocalResourceGroup(@Nonnull String id, @Nonnull T resource, boolean... silent) {
        final ResourceId rId = ResourceId.fromString(id);
        final ResourceGroup resourceGroup = resource.getResourceGroup();
        if (Objects.isNull(rId.parent()) && Objects.nonNull(resourceGroup) && !this.isMocked() &&
            !(resource instanceof ResourceGroup) && !(resource instanceof ResourceDeployment)) {
            final GenericResourceModule genericResourceModule = resourceGroup.genericResources();
            final GenericResource genericResource = genericResourceModule.newResource(resource);
            genericResourceModule.addResourceToLocal(resource.getId(), genericResource, silent);
        }
    }

    private void fireChildrenChangedEvent() {
        log.debug("[{}]:fireChildrenChangedEvent()", this.name);
        if (this.getParent() instanceof AbstractAzServiceSubscription) {
            @SuppressWarnings("unchecked") final AzResourceModule

service = (AzResourceModule

) this.getParent().getModule(); AzureEventBus.emit("service.children_changed.service", service); } if (this instanceof AzService) { AzureEventBus.emit("service.children_changed.service", this); } AzureEventBus.emit("resource.children_changed.resource", this.getParent()); AzureEventBus.emit("module.children_changed.module", this); } @Nonnull @AzureOperation(name = "azure/$resource.load_resources_by_page.type", params = {"this.getResourceTypeName()"}) protected Iterator> loadResourcePagesFromAzure() { log.debug("[{}]:loadPagedResourcesFromAzure()", this.getName()); final Object client = this.getClient(); if (!this.parent.exists() || Objects.isNull(client)) { return Collections.emptyIterator(); } else if (client instanceof SupportsListing) { log.debug("[{}]:loadPagedResourcesFromAzure->client.list()", this.name); return this.>cast(client).list().iterableByPage(getPageSize()).iterator(); } else { log.debug("[{}]:loadPagedResourcesFromAzure->NOT Supported", this.name); throw new AzureToolkitRuntimeException("not supported"); } } @Nullable @AzureOperation(name = "azure/$resource.load_resource.resource|type", params = {"name", "this.getResourceTypeName()"}) protected R loadResourceFromAzure(@Nonnull String name, @Nullable String resourceGroup) { log.debug("[{}]:loadResourceFromAzure({}, {})", this.getName(), name, resourceGroup); final Object client = this.getClient(); resourceGroup = StringUtils.firstNonBlank(resourceGroup, this.getParent().getResourceGroupName()); resourceGroup = StringUtils.equals(resourceGroup, RESOURCE_GROUP_PLACEHOLDER) ? null : resourceGroup; if (client instanceof SupportsGettingById && StringUtils.isNotEmpty(resourceGroup)) { log.debug("[{}]:loadResourceFromAzure->client.getById({}, {})", this.name, resourceGroup, name); return this.>cast(client).getById(toResourceId(name, resourceGroup)); } else if (client instanceof SupportsGettingByResourceGroup && StringUtils.isNotEmpty(resourceGroup)) { log.debug("[{}]:loadResourceFromAzure->client.getByResourceGroup({}, {})", this.name, resourceGroup, name); return this.>cast(client).getByResourceGroup(resourceGroup, name); } else if (client instanceof SupportsGettingByName) { log.debug("[{}]:loadResourceFromAzure->client.getByName({})", this.name, name); return this.>cast(client).getByName(name); } else { // fallback to filter the named resource from all resources in current module. log.debug("[{}]:loadResourceFromAzure->this.list().filter({}).getRemote()", this.name, name); return this.list().stream().filter(r -> StringUtils.equals(name, r.getName())).findAny().map(AbstractAzResource::getRemote).orElse(null); } } @AzureOperation(name = "azure/$resource.delete_resource.resource|type", params = {"nameFromResourceId(resourceId)", "this.getResourceTypeName()"}) protected void deleteResourceFromAzure(@Nonnull String resourceId) { log.debug("[{}]:deleteResourceFromAzure({})", this.getName(), resourceId); final Object client = this.getClient(); if (client instanceof SupportsDeletingById) { log.debug("[{}]:deleteResourceFromAzure->client.deleteById({})", this.name, resourceId); ((SupportsDeletingById) client).deleteById(resourceId); } } private String normalizeResourceGroupName(String name, @Nullable String rgName) { rgName = StringUtils.firstNonBlank(rgName, this.getParent().getResourceGroupName()); if (StringUtils.isBlank(rgName) || StringUtils.equalsIgnoreCase(rgName, RESOURCE_GROUP_PLACEHOLDER)) { if (this instanceof ResourceGroupModule) { return name; } else if (this instanceof AzService) { return RESOURCE_GROUP_PLACEHOLDER; } throw new IllegalArgumentException("Resource Group name is required for " + this.getResourceTypeName()); } return rgName; } @Nonnull protected AzResource.Draft newDraftForCreate(@Nonnull String name, @Nullable String rgName) { throw new AzureToolkitRuntimeException("not supported"); } @Nonnull protected AzResource.Draft newDraftForUpdate(@Nonnull T t) { throw new AzureToolkitRuntimeException("not supported"); } @Nonnull protected abstract T newResource(@Nonnull R r); @Nonnull protected abstract T newResource(@Nonnull String name, @Nullable String resourceGroupName); /** * get track2 client, which is used to implement {@link #loadResourcePagesFromAzure}, {@link #loadResourceFromAzure} and {@link #deleteResourceFromAzure} */ @Nullable protected Object getClient() { return null; } @Override @Nonnull public String getFullResourceType() { return this.getParent().getFullResourceType() + "/" + this.getName(); } @Nonnull public String getResourceTypeName() { return this.getFullResourceType(); } @Nonnull public String getSubscriptionId() { return this.getParent().getSubscriptionId(); } @Nonnull public String getId() { return this.toResourceId("", null); } @Nonnull protected D cast(@Nonnull Object origin) { //noinspection unchecked return (D) origin; } public static int getPageSize() { return Azure.az().config().getPageSize(); } public boolean isAuthRequiredForCreating() { return !isMocked(); } public boolean isAuthRequiredForListing() { return !isMocked(); } protected boolean isAuthRequiredForResource(@Nonnull String resourceId) { return !isMocked(resourceId); } public static boolean isMocked(@Nonnull String resourceId) { final String subscriptionId = ResourceId.fromString(resourceId).subscriptionId(); return Subscription.MOCK_SUBSCRIPTION_ID.equals(subscriptionId) || !Character.isLetterOrDigit(subscriptionId.charAt(0)); } public boolean isMocked() { final String subscriptionId = this.getSubscriptionId(); return Subscription.MOCK_SUBSCRIPTION_ID.equals(subscriptionId) || !Character.isLetterOrDigit(subscriptionId.trim().charAt(0)); } public String getServiceNameForTelemetry() { return this.getParent().getModule().getServiceNameForTelemetry(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy