From 64f5e09439f0aa5835b901e502f9dc425311283a Mon Sep 17 00:00:00 2001 From: rainzm Date: Mon, 2 Aug 2021 11:24:03 +0800 Subject: [PATCH] feat(multicloud): support webapp for azure --- pkg/multicloud/azure/app_service_plan.go | 199 ++++++++++++ pkg/multicloud/azure/autoscale_setting.go | 156 +++++++++ pkg/multicloud/azure/azure.go | 11 +- pkg/multicloud/azure/region.go | 3 +- .../azure/shell/app_service_plan.go | 81 +++++ pkg/multicloud/azure/shell/web_app.go | 68 ++++ pkg/multicloud/azure/web_app.go | 302 ++++++++++++++++++ 7 files changed, 816 insertions(+), 4 deletions(-) create mode 100644 pkg/multicloud/azure/app_service_plan.go create mode 100644 pkg/multicloud/azure/autoscale_setting.go create mode 100644 pkg/multicloud/azure/shell/app_service_plan.go create mode 100644 pkg/multicloud/azure/shell/web_app.go create mode 100644 pkg/multicloud/azure/web_app.go diff --git a/pkg/multicloud/azure/app_service_plan.go b/pkg/multicloud/azure/app_service_plan.go new file mode 100644 index 0000000000..dcb6940e2a --- /dev/null +++ b/pkg/multicloud/azure/app_service_plan.go @@ -0,0 +1,199 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package azure + +import ( + "fmt" + "net/url" + + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SAppServicePlan struct { + multicloud.AzureTags + region *SRegion + + Properties SAppServicePlanProperties + ID string + Kind string + Location string + Name string + Type string + Sku SSkuDescription +} + +type SAppServicePlanProperties struct { + FreeOfferExpirationTime string + GeoRegion string + HostingEnvironmentProfile SHostingEnvironmentProfile + HyperV bool + IsSpot bool + MaximumElasticWorkerCount int + MaximumNumberOfWorkers int + NumberOfSites int + PerSiteScaling bool + // 应用服务环境的预配状态 + ProvisioningState string + Reserved bool + ResourceGroup string + SpotExpirationTime string + // Creating Pending Ready + Status string + Subscription string + TargetWorkerCount int + TargetWorkerSizeId int + WorkerTierName string +} + +type SHostingEnvironmentProfile struct { + ID string + Name string + Type string +} + +type SSkuDescription struct { + Capabilities []SCapability + Capacity int + Family string + locations []string + Name string + Size string + SkuCapacity SSkuCapacity + Tier string +} + +type SCapability struct { + Name string + Reason string + Value string +} + +type SSkuCapacity struct { + Default int + Maximum int + Minimum int + ScaleType string +} + +func (r *SRegion) GetAppServicePlanWithCache(id string) (*SAppServicePlan, error) { + if r.appServicePlans == nil { + plans, err := r.GetAppServicePlans() + if err != nil { + return nil, err + } + appServicePlans := make(map[string]*SAppServicePlan, len(plans)) + for i := range plans { + appServicePlans[plans[i].ID] = &plans[i] + } + r.appServicePlans = appServicePlans + } + if ret, ok := r.appServicePlans[id]; ok { + return ret, nil + } + return r.GetAppServicePlan(id) +} + +func (r *SRegion) GetAppServicePlans() ([]SAppServicePlan, error) { + result := []SAppServicePlan{} + resource := "Microsoft.Web/serverfarms" + err := r.list(resource, url.Values{"api-version": []string{"2019-08-01"}}, &result) + if err != nil { + return nil, err + } + for i := range result { + result[i].region = r + } + return result, nil +} + +func (r *SRegion) GetAppServicePlan(planId string) (*SAppServicePlan, error) { + asp := &SAppServicePlan{region: r} + params := url.Values{"api-version": []string{"2019-08-01"}} + return asp, r.get(planId, params, asp) +} + +type SServerFarmSku struct { + ResourceType string + Sku SSkuSpec + Capacity SSkuCapacity +} + +type SSkuSpec struct { + Name string + Tier string +} + +func (asp *SAppServicePlan) GetSkus() ([]SServerFarmSku, error) { + result := []SServerFarmSku{} + resource := fmt.Sprintf("subscriptions/%s/resourceGroups/%s/providers/Microsoft.Web/serverfarms/%s/skus", asp.region.client._subscriptionId(), asp.Properties.ResourceGroup, asp.Name) + err := asp.region.client.list(resource, url.Values{"api-version": []string{"2019-08-01"}}, &result) + if err != nil { + return nil, err + } + return result, nil +} + +type SAppServicePlanVnet struct { + Id string + Name string + Properties SASPVnetProperties +} + +type SASPVnetProperties struct { + VnetResourceId string +} + +func (asp *SAppServicePlan) GetNetworks() ([]SNetwork, error) { + vnets, err := asp.GetVnets() + if err != nil { + return nil, errors.Wrap(err, "unable to get Vnets") + } + networks := make([]SNetwork, 0, len(vnets)) + for i := range vnets { + network, err := asp.region.GetNetwork(vnets[i].Properties.VnetResourceId) + if err != nil { + return nil, errors.Wrap(err, "unable to get Network") + } + networks = append(networks, *network) + } + return networks, nil +} + +func (asp *SAppServicePlan) GetVnets() ([]SAppServicePlanVnet, error) { + result := []SAppServicePlanVnet{} + resource := fmt.Sprintf("subscriptions/%s/resourceGroups/%s/providers/Microsoft.Web/serverfarms/%s/virtualNetworkConnections", asp.region.client._subscriptionId(), asp.Properties.ResourceGroup, asp.Name) + err := asp.region.client.list(resource, url.Values{"api-version": []string{"2019-08-01"}}, &result) + if err != nil { + return nil, err + } + return result, nil +} + +type SSkuInfos struct { + ResourceType string + Skus []SResourceSku +} + +func (r *SRegion) GetAppServiceSkus() ([]SSkuInfos, error) { + result := []SSkuInfos{} + resource := "Microsoft.Web/skus" + err := r.client.list(resource, url.Values{"api-version": []string{"2019-08-01"}}, &result) + if err != nil { + return nil, err + } + return result, err +} diff --git a/pkg/multicloud/azure/autoscale_setting.go b/pkg/multicloud/azure/autoscale_setting.go new file mode 100644 index 0000000000..afbd725871 --- /dev/null +++ b/pkg/multicloud/azure/autoscale_setting.go @@ -0,0 +1,156 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package azure + +import ( + "net/url" + + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SAutoscaleSettingResource struct { + multicloud.AzureTags + region *SRegion + + Properties SAutoscaleSettingProperties + ID string + Location string + Name string + Type string +} + +type SAutoscaleSettingProperties struct { + Enabled bool + Name string + TargetResourceUri string + Profiles []SAutoscaleProfile +} + +type SAutoscaleProfile struct { + Capacity SScaleCapacity + FixedDate STimeWindow + Name string + Recurrence SRecurrence + Rule []SScaleRule +} + +type SRecurrence struct { + Frequency string + Schedule SRecurrentSchedule +} + +type SRecurrentSchedule struct { + Days []string + Hours []int + Minutes []int + TimeZone string +} + +type STimeWindow struct { + // 采用 ISO 8601 格式的配置文件的结束时间。 + End string + // 采用 ISO 8601 格式的配置文件的开始时间。 + Start string + TimeZone string +} + +type SScaleAction struct { + Cooldown string + Direction string + Type string + Value string +} + +// 可以在此配置文件期间使用的实例数。 +type SScaleCapacity struct { + Default string + Maximum string + Minimum string +} + +// Decrease string +// Increase string +// None string + +// 为缩放操作提供触发器和参数的规则。 +type SScaleRule struct { + MetricTrigger SMetricTrigger + ScaleAction SScaleAction +} + +// 维度运算符。 仅支持"Equals"和"NotEquals"。 "Equals"等于任何值。 "NotEquals"不等于所有值 +type SScaleRuleMetricDimensionOperationType struct { + Equals string + NotEquals string +} + +//触发缩放规则时应发生的操作类型。 +// ChangeCount string +// ExactCount string +// PercentChangeCount string + +type SScaleRuleMetricDimension struct { + DimensionName string + Operator string + Values []string +} + +type SMetricTrigger struct { + // 维度条件的列表。 例如: [{"DimensionName": "AppName"、"Operator": "Equals"、"Values": ["App1"]}、{"DimensionName": "Deployment"、"Operator": "Equals"、"Values": ["default"]}]。 + Dimensions []SScaleRuleMetricDimension + + // 一个值,该值指示度量值是否应除以每个实例。 + DividePerInstance bool + + // 定义规则监视内容的指标的名称。 + MetricName string + + // 定义规则监视内容的指标的命名空间。 + MetricNamespace string + + // 规则监视的资源的资源标识符。 + MetricResourceUri string + + // 用于比较指标数据和阈值的运算符。 + // Equals GreaterThan GreaterThanOrEqual LessThan LessThanOrEqual NotEquals + Operator string + + // 指标统计信息类型。 来自多个实例的指标进行组合的方式。 + // Average Max Min Sum + Statistic string + + // 触发缩放操作的度量值的阈值。 + Threshold int + + // 时间聚合类型。 随着时间推移,收集的数据应如何组合。 默认值为 Average。 + // Average Count Last Maximum Minimum Total + TimeAggregation string + + //规则监视的度量值的粒度。 必须是从指标的指标定义返回的预定义值之一。 必须介于 12 小时和 1 分钟之间。 + TimeGrain string + + //收集实例数据的时间范围。 此值必须大于指标集合中的延迟,可能会因资源而异。 必须介于 12 小时和 5 分钟之间。 + TimeWindow string +} + +func (r *SRegion) GetAutoscaleSettingResources() ([]SAutoscaleSettingResource, error) { + result := []SAutoscaleSettingResource{} + resource := "microsoft.insights/autoscalesettings" + err := r.client.list(resource, url.Values{"api-version": []string{"2015-04-01"}}, &result) + if err != nil { + return nil, err + } + return result, nil +} diff --git a/pkg/multicloud/azure/azure.go b/pkg/multicloud/azure/azure.go index 82d5be4307..aec0080a01 100644 --- a/pkg/multicloud/azure/azure.go +++ b/pkg/multicloud/azure/azure.go @@ -508,10 +508,14 @@ func (self *SAzureClient) _list(resource string, params url.Values) (jsonutils.J path = fmt.Sprintf("subscriptions/%s/resourceGroups/%s/providers/%s", subId, params.Get("resourceGroups"), resource) params.Del("resourceGroups") default: - if len(subId) == 0 { - return nil, fmt.Errorf("no avaiable subscriptions") + if strings.HasPrefix(resource, "subscriptions/") || strings.HasPrefix(resource, "/subscriptions/") { + path = resource + } else { + if len(subId) == 0 { + return nil, fmt.Errorf("no avaiable subscriptions") + } + path = fmt.Sprintf("subscriptions/%s/providers/%s", subId, resource) } - path = fmt.Sprintf("subscriptions/%s/providers/%s", subId, resource) } params.Set("api-version", self._apiVersion(resource, params)) return self.jsonRequest("GET", path, nil, params, true) @@ -976,6 +980,7 @@ func (self *SAzureClient) GetCapabilities() []string { cloudprovider.CLOUD_CAPABILITY_SAML_AUTH, cloudprovider.CLOUD_CAPABILITY_WAF, cloudprovider.CLOUD_CAPABILITY_CACHE, + cloudprovider.CLOUD_CAPABILITY_APP, } return caps } diff --git a/pkg/multicloud/azure/region.go b/pkg/multicloud/azure/region.go index 0ca8257da6..ec428af2f1 100644 --- a/pkg/multicloud/azure/region.go +++ b/pkg/multicloud/azure/region.go @@ -42,7 +42,8 @@ type SRegion struct { multicloud.SRegion client *SAzureClient - storageCache *SStoragecache + storageCache *SStoragecache + appServicePlans map[string]*SAppServicePlan ID string Name string diff --git a/pkg/multicloud/azure/shell/app_service_plan.go b/pkg/multicloud/azure/shell/app_service_plan.go new file mode 100644 index 0000000000..bb8fe095c6 --- /dev/null +++ b/pkg/multicloud/azure/shell/app_service_plan.go @@ -0,0 +1,81 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + "fmt" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/multicloud/azure" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type AppServicePlanListOptions struct { + } + shellutils.R(&AppServicePlanListOptions{}, "app-service-plan-list", "List app service plan", func(cli *azure.SRegion, args *AppServicePlanListOptions) error { + appServicePlans, err := cli.GetAppServicePlans() + if err != nil { + return err + } + fmt.Printf("sku: %s\n", jsonutils.Marshal(appServicePlans[0].Sku)) + printList(appServicePlans, len(appServicePlans), 0, 0, []string{}) + return nil + }) + type AppServicePlanShowOptions struct { + ID string + } + shellutils.R(&AppServicePlanShowOptions{}, "app-service-plan-show", "Show app service plan", func(cli *azure.SRegion, args *AppServicePlanShowOptions) error { + appServicePlan, err := cli.GetAppServicePlan(args.ID) + if err != nil { + return err + } + printObject(appServicePlan) + return nil + }) + shellutils.R(&AppServicePlanListOptions{}, "autoscale-setting-list", "List autoscale setting in app service plan", func(cli *azure.SRegion, args *AppServicePlanListOptions) error { + appServicePlans, err := cli.GetAutoscaleSettingResources() + if err != nil { + return err + } + printList(appServicePlans, len(appServicePlans), 0, 0, []string{}) + return nil + }) + shellutils.R(&AppServicePlanShowOptions{}, "app-service-sku-list", "List sku usable in app service plan", func(cli *azure.SRegion, args *AppServicePlanShowOptions) error { + appServicePlan, err := cli.GetAppServicePlan(args.ID) + if err != nil { + return err + } + skus, err := appServicePlan.GetSkus() + if err != nil { + return err + } + printList(skus, len(skus), 0, 0, []string{}) + return nil + }) + shellutils.R(&AppServicePlanShowOptions{}, "app-service-network-list", "List network connected with app service plan", func(cli *azure.SRegion, args *AppServicePlanShowOptions) error { + appServicePlan, err := cli.GetAppServicePlan(args.ID) + if err != nil { + return err + } + networks, err := appServicePlan.GetNetworks() + if err != nil { + return err + } + printList(networks, len(networks), 0, 0, []string{}) + return nil + }) +} diff --git a/pkg/multicloud/azure/shell/web_app.go b/pkg/multicloud/azure/shell/web_app.go new file mode 100644 index 0000000000..912bb97a4e --- /dev/null +++ b/pkg/multicloud/azure/shell/web_app.go @@ -0,0 +1,68 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/azure" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type AppSiteListOptions struct { + } + shellutils.R(&AppSiteListOptions{}, "app-site-list", "List app service plan", func(cli *azure.SRegion, args *AppSiteListOptions) error { + ass, err := cli.GetAppSites() + if err != nil { + return err + } + printList(ass, len(ass), 0, 0, []string{}) + return nil + }) + type AppSiteShowOptions struct { + ID string + } + shellutils.R(&AppSiteShowOptions{}, "app-site-show", "Show app service plan", func(cli *azure.SRegion, args *AppSiteShowOptions) error { + as, err := cli.GetAppSite(args.ID) + if err != nil { + return err + } + printObject(as) + return nil + }) + shellutils.R(&AppSiteShowOptions{}, "app-site-deployment-list", "List sku usable in app service plan", func(cli *azure.SRegion, args *AppSiteShowOptions) error { + as, err := cli.GetAppSite(args.ID) + if err != nil { + return err + } + deployments, err := as.GetDeployments() + if err != nil { + return err + } + printList(deployments, len(deployments), 0, 0, []string{}) + return nil + }) + shellutils.R(&AppSiteShowOptions{}, "app-site-slot-list", "List slots ofr App site", func(cli *azure.SRegion, args *AppSiteShowOptions) error { + as, err := cli.GetAppSite(args.ID) + if err != nil { + return err + } + slots, err := as.GetSlots() + if err != nil { + return err + } + printList(slots, len(slots), 0, 0, []string{}) + return nil + }) +} diff --git a/pkg/multicloud/azure/web_app.go b/pkg/multicloud/azure/web_app.go new file mode 100644 index 0000000000..0802ede373 --- /dev/null +++ b/pkg/multicloud/azure/web_app.go @@ -0,0 +1,302 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package azure + +import ( + "fmt" + "net/url" + "strings" + + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SAppSite struct { + multicloud.AzureTags + region *SRegion + appServicePlan *SAppServicePlan + + Properties SAppSiteProperties + Id string + Name string + Kind string + Location string + Type string + + stack *string +} + +type SAppSiteProperties struct { + AvailabilityState string + DefaultHostName string + Enabled bool + EnabledHostNames []string + HostNameSslStates []SHostNameSslState + HostNames []string + HttpsOnly bool + OutboundIpAddresses string + ResourceGroup string + ServerFarmId string + SiteConfig SSiteConfig + State string +} + +type SSiteConfig struct { + // TODO +} + +type SHostNameSslState struct { + HostType string + Name string + SslState string + Thumbprint string + ToUpdate bool + VirtualIP string +} + +func (r *SRegion) GetAppSites() ([]SAppSite, error) { + result := []SAppSite{} + resource := "Microsoft.Web/sites" + err := r.list(resource, url.Values{"api-version": []string{"2019-08-01"}}, &result) + if err != nil { + return nil, err + } + for i := range result { + result[i].region = r + } + return result, nil +} + +func (r *SRegion) GetICloudApps() ([]cloudprovider.ICloudApp, error) { + ass, err := r.GetAppSites() + if err != nil { + return nil, err + } + apps := make([]cloudprovider.ICloudApp, 0, len(ass)) + for i := range ass { + apps = append(apps, &SApp{ + SAppSite: ass[i], + }) + } + return apps, nil +} + +func (r *SRegion) GetICloudAppById(id string) (cloudprovider.ICloudApp, error) { + as, err := r.GetAppSite(id) + if err != nil { + return nil, err + } + return &SApp{ + SAppSite: *as, + }, nil +} + +func (as *SAppSite) GetSlots() ([]SAppSite, error) { + result := []SAppSite{} + resource := fmt.Sprintf("subscriptions/%s/resourceGroups/%s/providers/Microsoft.Web/sites/%s/slots", as.region.client.subscriptionId, as.Properties.ResourceGroup, as.Name) + err := as.region.list(resource, url.Values{"api-version": []string{"2019-08-01"}}, &result) + if err != nil { + return nil, err + } + return result, nil +} + +func (r *SRegion) GetAppSite(siteId string) (*SAppSite, error) { + as := &SAppSite{region: r} + params := url.Values{"api-version": []string{"2019-08-01"}} + return as, r.get(siteId, params, as) +} + +func (as *SAppSite) GetAppServicePlan() (*SAppServicePlan, error) { + if as.appServicePlan != nil { + return as.appServicePlan, nil + } + plan, err := as.region.GetAppServicePlanWithCache(as.Properties.ServerFarmId) + if err != nil { + return nil, errors.Wrap(err, "unable to get AppServicePlan") + } + as.appServicePlan = plan + return plan, nil +} + +type SDeployment struct { + ID string + Name string + Kind string + Type string + Properties SDeploymentProperties +} + +type SDeploymentProperties struct { + Active bool + Author string + AuthorEmail string + Deployer string + Details string + EndTime string + Message string + StartTime string + Status string +} + +func (as *SAppSite) GetDeployments() ([]SDeployment, error) { + result := []SDeployment{} + resource := fmt.Sprintf("subscriptions/%s/resourceGroups/%s/providers/Microsoft.Web/sites/%s/deployments", as.region.client._subscriptionId(), as.Properties.ResourceGroup, as.Name) + err := as.region.client.list(resource, url.Values{"api-version": []string{"2019-08-01"}}, &result) + if err != nil { + return nil, err + } + return result, nil +} + +type SStringDictionary struct { + Id string + Kind string + Name string + Type string + Properties map[string]string +} + +func (as *SAppSite) GetStack() (string, error) { + if as.stack != nil { + return *as.stack, nil + } + // result := SStringDictionary{} + resource := fmt.Sprintf("subscriptions/%s/resourceGroups/%s/providers/Microsoft.Web/sites/%s/config/metadata", as.region.client.subscriptionId, as.Properties.ResourceGroup, as.Name) + + params := url.Values{} + params.Set("api-version", "2019-08-01") + path := resource + "/list" + response, err := as.region.client.jsonRequest("POST", path, nil, params, true) + if err != nil { + return "", err + } + stack, err := response.GetString("properties", "CURRENT_STACK") + if err != nil { + return "", errors.Wrapf(err, "unable to parse response correctly, response: %s", response) + } + as.stack = &stack + return stack, nil +} + +func (as *SAppSite) GetId() string { + return as.Id +} + +func (as *SAppSite) GetName() string { + return as.Name +} + +func (as *SAppSite) GetGlobalId() string { + return strings.ToLower(as.Id) +} + +func (as *SAppSite) GetStatus() string { + return "ready" +} + +func (as *SAppSite) GetProjectId() string { + return strings.ToLower(as.Properties.ResourceGroup) +} + +type SApp struct { + multicloud.SResourceBase + multicloud.AzureTags + SAppSite +} + +type SAppEnvironment struct { + multicloud.SResourceBase + multicloud.AzureTags + SAppSite +} + +func (ae *SAppEnvironment) GetInstanceType() (string, error) { + asp, err := ae.SAppSite.GetAppServicePlan() + if err != nil { + return "", err + } + return asp.Sku.Name, nil +} + +func (ae *SAppEnvironment) GetInstanceNumber() (int, error) { + asp, err := ae.SAppSite.GetAppServicePlan() + if err != nil { + return 0, err + } + return asp.Sku.Capacity, nil +} + +func (a *SApp) GetEnvironments() ([]cloudprovider.ICloudAppEnvironment, error) { + sites, err := a.SAppSite.GetSlots() + if err != nil { + return nil, err + } + aes := make([]cloudprovider.ICloudAppEnvironment, 0, len(sites)+1) + aes = append(aes, &SAppEnvironment{ + SAppSite: a.SAppSite, + }) + for i := range sites { + sites[i].region = a.region + aes = append(aes, &SAppEnvironment{ + SAppSite: sites[i], + }) + } + return aes, nil +} + +var techStacks = map[string]string{ + "dotnet": ".NET", + "dotnetcore": ".NET Core", + "aspdotnet": "ASP.NET", + "node": "Node", + "python": "Python", + "php": "PHP", + "ruby": "Ruby", + "java": "Java", + "javacontainers": "Jave Containers", +} + +func (a *SApp) GetTechStack() string { + if strings.Contains(a.SAppSite.Kind, "container") { + return "Docker container" + } + stack, err := a.GetStack() + if err != nil { + log.Errorf("unable to GetStack: %v", err) + } + if s, ok := techStacks[stack]; ok { + return s + } + return stack +} + +func (a *SApp) GetType() string { + return "web" +} + +func (a *SApp) GetKind() string { + return a.Kind +} + +func (a *SApp) GetOsType() string { + if strings.Contains(a.Kind, "linux") { + return "Linux" + } + return "Windows" +}