GithubHelp home page GithubHelp logo

hashicorp / go-azure-helpers Goto Github PK

View Code? Open in Web Editor NEW
57.0 247.0 43.0 2.82 MB

This repository contains various helpers and wrappers for working with Azure and the Azure SDK for Go.

License: Mozilla Public License 2.0

Makefile 0.01% Go 99.75% Shell 0.23%

go-azure-helpers's Introduction

hashicorp/go-azure-helpers

This repository contains various helpers and wrappers for working with Azure - and contains a number of common types used in hashicorp/go-azure-sdk.

go-azure-helpers's People

Contributors

aczelandi avatar aristosvo avatar catriona-m avatar dependabot[bot] avatar derkoe avatar hashicorp-copywrite[bot] avatar hashicorp-tsccr[bot] avatar jackofallops avatar jiaweitao001 avatar katbyte avatar koikonom avatar laurentlesle avatar madewithsmiles avatar magodo avatar manicminer avatar mbfrahry avatar mdeggies avatar metacpp avatar mpminardi avatar ms-henglu avatar r0bnet avatar radeksimko avatar sinbai avatar stefanschoof avatar stephybun avatar tombuildsstuff avatar wodansson avatar wuxu92 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

go-azure-helpers's Issues

Authenticate as Az CLI + service principal

I can see this note here:

Authenticating as a Service Principal doesn't return all of the information we need for authentication purposes
as such Service Principal authentication is supported using the specific auth method

We ran az account show -o=json and the output for a service principal seems to be similar to that of a user. Could you please elaborate on which information is missing? Is there a chance it was added since the original change?

Thank you.

print assumed timezone on stderr when access tokens seem to be stale

Migrating this over from hashicorp/terraform-provider-azurerm#1707 where @cruwe opened the following issue:


Community Note

  • Please vote on this issue by adding a ๐Ÿ‘ reaction to the original issue to help the community and maintainers prioritize this request
  • Please do not leave "+1" or "me too" comments, they generate extra noise for issue followers and do not help prioritize the request
  • If you are interested in working on this issue or have submitted a pull request, please leave a comment

Description

To use terraform with MS Azure, a valid access token must be obtained beforehand with az login.

One may be tempted to run az packaged as a docker container (to prevent polluting the workstation). terraform (statically linked) does not warrant such hooks and loops.

By standard, docker containers run on UTC and accordingly, az formats and stores access tokens in UTC. When the host terraform is then invoked on runs on a different timezone and the variable $TZ is unset, tokens may be stale without the user realizing.

I have requested to document this behaviour with PR #1706. I do not know how to (I would do it myself then), but it may be of value to stderr the $TZ terraform assumes for the az token so that debugging such failures may be easier.

New or Affected Resource(s)

  • azurerm provider

References

  • PR #1706

No valid (unexpired) Azure CLI Auth Tokens found. when setting tenant_id

Migrating this over from hashicorp/terraform-provider-azurerm#1029 where @tobiaswi opened the following issue:


Hi there,

i found a bug when using azure cli for authentication.
My setup:

Terraform Version

Terraform v0.11.5

  • provider.azurerm v1.3.0
  • provider.random v1.1.0

Terraform Configuration Files

###################################
# Connectinon to Azure
###################################

provider "azurerm" {
  tenant_id       = "[my-tenant-uuid]"
  subscription_id = "[my-subscription-id]"
  version         = "1.3"
}

provider "random" {
  version = "~> 1.1"
}

Debug Output

relevant line:
2018-03-26T12:11:09.598+0200 [DEBUG] plugin.terraform-provider-azurerm_v1.3.0_x4: 2018/03/26 12:11:09 [DEBUG] Resource "https://management.core.windows.net/" isn't for the correct Tenant

Expected Behavior

after authentication with azure cli my terraform plan runs successfully while my accessToken is valid.

Actual Behavior

terraform plan does not run and throws:

  • provider.azurerm: No valid (unexpired) Azure CLI Auth Tokens found. Please run az login.

Steps to Reproduce

  1. az login -t tenantdomain.onmicrosoft.com
  2. terraform init -upgrade=true
  3. terraform plan

References

  • release v1.0.0
  • commit fe1e0f53c7e6bf84627092cf31b1fb2aacba652b
  • commit d42caca05988d6fe96e4d2c3ca8925699a51d2aa

Root cause:

When defining tenant_id in my provider as a tenant UUID

provider "azurerm" {
  tenant_id       = "[my-tenant-uuid]"
  subscription_id = "[my-subscription-id]"
  version         = "1.3"
}

but logging into azure cli with the tenants domain name

az login -t tenantdomain.onmicrosoft.com

my accessToken looks like this:

[
    {
        "expiresIn": 3599,
        "_authority": "https://login.microsoftonline.com/[tenantdomain.onmicrosoft.com]",
        "refreshToken": "[removed]",
        "accessToken": "[removed]",
        "expiresOn": "[removed]",
        "userId": "[removed]",
        "isMRRT": true,
        "_clientId": "[removed]",
        "tokenType": "Bearer",
        "resource": "https://management.core.windows.net/",
        "identityProvider": "[removed]"
    }
]

this causes:

azurerm/helpers/authentication/config.go
Line: 60 -> Does not load config from cliProfile if TenantID is set in terraform config
Line: 81 -> findValidAccessTokenForTenant passes the accessToken from disk aswell as the terraform config defined c.TenantID

azurerm/helpers/authentication/access_token.go
Line: 41 -> string compares the suffixes which evaluate to false (domain.onmicrosoft.com != xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)

I tried to come up with a fix but im not sure about the use cases that require to load the c.SubscriptionID and c.TenantID from the terraform file when running LoadTokensFromAzureCLI(). This bug could be avoided if we ignore the terraform provider config values as the local azureProfile.json holds the same "tenantId" as the accessTokens.json "_authority" suffix value no matter how you choose to login with the cli (-t uuid / -t dns)

The solution to avoid this bug is removing the defined tenant_id and subscription_id from the terraform provider config.
Maybe an entry in the documentation could show that this is a valid provider config for cli authentication?

provider "azurerm" {
  version = "1.3"
}

Storage Helper SAS Token blobContainerSigenedVersion issue

Hello,

While I was working enabling Azure App Service WebApp and Http logs, I've used data_source_storage_account_blob_container_sas resource to generate SAS token. When we created SAS token and attached it to App Service, the logging feature doesn't work. After going deep on this, I noticed that the SAS token version is older than the version when I generated with Azure Powershell. The data_source_storage_account_blob_container_sas resource uses storage helper in this repo and the blobContainerSignedVersion parameter was set to "2018-11-09" as constant in sas_token.go file line 17.
const ( connStringAccountKeyKey = "AccountKey" connStringAccountNameKey = "AccountName" blobContainerSignedVersion = "2018-11-09" )

Is there any way to update the version to generate the SAS token with the latest version "2019-02-02". Then we will be able to use data_source_storage_account_blob_container_sas resource and app service logging feature of the terraform azurerm provider.

InvalidAuthenticationTokenAudience

Hi @tombuildsstuff @katbyte ,

use MSI to authenticate on cloud shell (MSI_ENDPOINT=http://localhost:50342/oauth2/token) could intermittently get the error:
Error: Unable to list provider registration status, it is possible that this is due to invalid credentials or the service principal does not have permission to use the Resource Manager API, Azure error -- Original Error: autorest/azure: Service returned an error. Status=401 Code='InvalidAuthenticationTokenAudience' Message='The access token has been obtained for wrong audience or resource 'https://graph.windows.net/'. It should exactly match with one of the allowed audiences 'https://management.core.windows.net/','https://management.core.windows.net','https://management.azure.com/','https://management.azure.com'.

While the customer get the following error:
Error: Error loading state: Error retrieving keys for Storage Account 'aznestpdavetfstate1': storage.AccountsClient#ListKeys: Failure responding to request: StatusCode=401 -- Original Error: autorest/azure: Service returned an error. Status=401 Code='InvalidAuthenticationTokenAudience' Message='The access token has been obtained for wrong audience or resource 'https://graph.windows.net/'. It should exactly match with one of the allowed audiences 'https://management.core.windows.net/','https://management.core.windows.net','https://management.azure.com/','https://management.azure.com'.

I'm not familiar with authentication, I wonder if it's related to https://github.com/hashicorp/go-azure-helpers/blob/master/authentication/azure_sp_objectid.go#L27

Here attach the log of debugging. log.txt

New Common ID for Redis Cache

This is used in a bunch of places so should become a Common ID

This would also fix this being output as redis.NewRediID (note the missing s, due to the apparent pluralisation of this name)

Improving the error message used when a SP fails to obtain the authorization token

This is the error currently being returned which isn't clear:

Error: Error building account: Error getting authenticated object ID: Error listing Service Principals: autorest.DetailedError{Original:(*azure.RequestError)(0xc00010fdd0), PackageType:"graphrbac.ServicePrincipalsClient", Method:"List", StatusCode:400, Message:"Failure responding to request", ServiceError:[]uint8(nil), Response:(*http.Response)(0xc00010fd40)}

In order to make this more useful we should wrap this error message and explain the permissions needed.

Reference: hashicorp/terraform-provider-azurerm#5780

New Authentication Method: OIDC

Community Note

  • Please vote on this issue by adding a ๐Ÿ‘ reaction to the original issue to help the community and maintainers prioritize this request
  • Please do not leave "+1" or "me too" comments, they generate extra noise for issue followers and do not help prioritize the request
  • If you are interested in working on this issue or have submitted a pull request, please leave a comment

Description

Currently the only way to authenticate with a service principal you need to use secert or certificate. It would be nice if the azurerm provider supports authenticating a Service Principal or a AzureAD App using the Azure CLI.

Use-cases
Github announced OIDC Federated credentials for Github Actions. It can be used by the Azure Login action. It would be nice if azurerm also supported that scenario so you do not need a secret for deployment to Azure anymore.

See https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-azure for details.

Issue was raised with hashicorp/terraform earlier: #29830

New or Affected Resource(s)

No resources should be affected, this only a new way to authenticate with a service principal

Potential Terraform Configuration

No changes to the terraform configuration as authentication is handled by Azure CLI

References

Tags: Expand, Flatten and Schema types for Typed Models

I could have sworn we have this, but I can't immediately see it - we should be introducing a Schema type within the resourcemanager/tags package, in addition with an Expand/Flatten function, to ensure that these values can always be set into the state

Azure CLI Tokens + Azure Stack Terraform Provider in ADFS/Disconnected mode

Hello,
I'm struggling in authenticate in an ADFS Disconnected Azure Stack with Azure Stack Terraform Provider using Azure CLI Tokens.

Versions:

  • az cli : 2.0.67
  • terraform : 0.12.2
  • terraform azurestack provider : 0.7.0 and master branch tested

Steps performed:

  • az logout/az login โ‡’ successful (in browser)
  • az account get-access-token โ‡’ successful
  • az group list โ‡’ successful
  • terraform plan โ‡’ fail

2019-06-20T11:38:45.076+0200 [DEBUG] plugin.terraform-provider-azurestack: 2019/06/20 11:38:45 Testing if Service Principal / Client Certificate is applicable for Authentication..
2019-06-20T11:38:45.076+0200 [DEBUG] plugin.terraform-provider-azurestack: 2019/06/20 11:38:45 Testing if Service Principal / Client Secret is applicable for Authentication..
2019-06-20T11:38:45.076+0200 [DEBUG] plugin.terraform-provider-azurestack: 2019/06/20 11:38:45 Testing if Managed Service Identity is applicable for Authentication..
2019-06-20T11:38:45.076+0200 [DEBUG] plugin.terraform-provider-azurestack: 2019/06/20 11:38:45 Testing if Obtaining a token from the Azure CLI is applicable for Authentication..
2019-06-20T11:38:45.076+0200 [DEBUG] plugin.terraform-provider-azurestack: 2019/06/20 11:38:45 Using Obtaining a token from the Azure CLI for Authentication
2019-06-20T11:38:45.077+0200 [DEBUG] plugin.terraform-provider-azurestack: 2019/06/20 11:38:45 [DEBUG] Resource "https://management.adfs.azstack.local/4851e0c9-ca1e-405e-9589-976d89f72324" isn't for the correct Tenant
2019/06/20 11:38:45 [ERROR] : eval: *terraform.EvalConfigProvider, err: Error building ARM Client: Error populating Client ID from the Azure CLI: No Authorization Tokens were found - please re-authenticate using az login.
2019/06/20 11:38:45 [ERROR] : eval: *terraform.EvalSequence, err: Error building ARM Client: Error populating Client ID from the Azure CLI: No Authorization Tokens were found - please re-authenticate using az login.
2019/06/20 11:38:45 [ERROR] : eval: *terraform.EvalOpFilter, err: Error building ARM Client: Error populating Client ID from the Azure CLI: No Authorization Tokens were found - please re-authenticate using az login.
2019/06/20 11:38:45 [ERROR] : eval: *terraform.EvalSequence, err: Error building ARM Client: Error populating Client ID from the Azure CLI: No Authorization Tokens were found - please re-authenticate using az login.

Tenant id is correct. I don't know why it add https://management.adfs.azstack.local/ in front of it but why not.

Common IDs for Virtual Network and Subnet

These two Resource IDs are integral enough that these should be present within the Common IDs package (which'll mean they can be added as Common IDs within hashicorp/pandora too)

Resource provider RegisterForSubscription returns only last error

If RegisterForSubscription fails for multiple provider only the last error is returned:

for providerName := range providersToRegister {
go func(p string) {
defer wg.Done()
log.Printf("[DEBUG] Registering Resource Provider %q with namespace", p)
if innerErr := registerWithSubscription(ctx, p, client); innerErr != nil {
err = innerErr
}
}(providerName)
}

It should return all error and/or all providers that fail to register.

If someone have not enough permission to enable those provider it would be very helpful to have a error message with all resource provider that are unable to register.

Related: hashicorp/terraform-provider-azurerm#4799

azure_sp_objectid issue querying serviceprincipal

I am currently trying to optimize my terraform config and make use of the azurerm_client_config data source.

But there seems to be an issue using the graph api:

2020-02-28T23:46:11.316Z [DEBUG] plugin.terraform-provider-azurerm_v2.0.0_x5: [DEBUG] GoAzureHelpers Request: 
2020-02-28T23:46:11.316Z [DEBUG] plugin.terraform-provider-azurerm_v2.0.0_x5: GET /XXXXXXXX-XXXX-XXXX-XXXX-2483ee9f6480/servicePrincipals?%24filter=appId+eq+%27http%3A%2F%2FSP-test%27&api-version=1.6 HTTP/1.1
2020-02-28T23:46:11.316Z [DEBUG] plugin.terraform-provider-azurerm_v2.0.0_x5: Host: graph.windows.net
2020-02-28T23:46:11.316Z [DEBUG] plugin.terraform-provider-azurerm_v2.0.0_x5: User-Agent: Go/go1.12.6 (amd64-linux) go-autorest/v13.3.0 Azure-SDK-For-Go/v38.1.0 graphrbac/1.6
2020-02-28T23:46:11.316Z [DEBUG] plugin.terraform-provider-azurerm_v2.0.0_x5: Accept-Encoding: gzip
2020-02-28T23:46:11.316Z [DEBUG] plugin.terraform-provider-azurerm_v2.0.0_x5: 
2020-02-28T23:46:11.316Z [DEBUG] plugin.terraform-provider-azurerm_v2.0.0_x5: 
2020-02-28T23:46:11.522Z [DEBUG] plugin.terraform-provider-azurerm_v2.0.0_x5: [DEBUG] GoAzureHelpers Response for https://graph.windows.net/XXXXXXXX-XXXX-XXXX-XXXX-2483ee9f6480/servicePrincipals?%24filter=appId+eq+%27http%3A%2F%2FSP-test%27&api-version=1.6: 
2020-02-28T23:46:11.522Z [DEBUG] plugin.terraform-provider-azurerm_v2.0.0_x5: HTTP/1.1 400 Bad Request
2020-02-28T23:46:11.522Z [DEBUG] plugin.terraform-provider-azurerm_v2.0.0_x5: Content-Length: 271
2020-02-28T23:46:11.522Z [DEBUG] plugin.terraform-provider-azurerm_v2.0.0_x5: Access-Control-Allow-Origin: *
2020-02-28T23:46:11.522Z [DEBUG] plugin.terraform-provider-azurerm_v2.0.0_x5: Cache-Control: no-cache
2020-02-28T23:46:11.523Z [DEBUG] plugin.terraform-provider-azurerm_v2.0.0_x5: Client-Request-Id: XXXXXXXX-XXXX-XXXX-XXXX-4d35b2931c67
2020-02-28T23:46:11.523Z [DEBUG] plugin.terraform-provider-azurerm_v2.0.0_x5: Content-Type: application/json; odata=minimalmetadata; streaming=true; charset=utf-8
2020-02-28T23:46:11.523Z [DEBUG] plugin.terraform-provider-azurerm_v2.0.0_x5: Dataserviceversion: 3.0;
2020-02-28T23:46:11.523Z [DEBUG] plugin.terraform-provider-azurerm_v2.0.0_x5: Date: Fri, 28 Feb 2020 23:46:11 GMT
2020-02-28T23:46:11.523Z [DEBUG] plugin.terraform-provider-azurerm_v2.0.0_x5: Duration: 386484
2020-02-28T23:46:11.523Z [DEBUG] plugin.terraform-provider-azurerm_v2.0.0_x5: Expires: -1
2020-02-28T23:46:11.523Z [DEBUG] plugin.terraform-provider-azurerm_v2.0.0_x5: Ocp-Aad-Diagnostics-Server-Name: XXX
2020-02-28T23:46:11.523Z [DEBUG] plugin.terraform-provider-azurerm_v2.0.0_x5: Ocp-Aad-Session-Key: XXX
2020-02-28T23:46:11.523Z [DEBUG] plugin.terraform-provider-azurerm_v2.0.0_x5: Pragma: no-cache
2020-02-28T23:46:11.523Z [DEBUG] plugin.terraform-provider-azurerm_v2.0.0_x5: Request-Id: XXXXXXXX-XXXX-XXXX-XXXX-399f92ae69f2
2020-02-28T23:46:11.523Z [DEBUG] plugin.terraform-provider-azurerm_v2.0.0_x5: Strict-Transport-Security: max-age=31536000; includeSubDomains
2020-02-28T23:46:11.523Z [DEBUG] plugin.terraform-provider-azurerm_v2.0.0_x5: X-Aspnet-Version: 4.0.30319
2020-02-28T23:46:11.523Z [DEBUG] plugin.terraform-provider-azurerm_v2.0.0_x5: X-Ms-Dirapi-Data-Contract-Version: 1.6
2020-02-28T23:46:11.525Z [DEBUG] plugin.terraform-provider-azurerm_v2.0.0_x5: X-Powered-By: ASP.NET
2020-02-28T23:46:11.525Z [DEBUG] plugin.terraform-provider-azurerm_v2.0.0_x5: 
2020-02-28T23:46:11.528Z [DEBUG] plugin.terraform-provider-azurerm_v2.0.0_x5: {"odata.error":{"code":"Request_UnsupportedQuery","message":{"lang":"en","value":"Unsupported or invalid query filter clause specified for property 'appId' of resource 'ServicePrincipal'."},"requestId":"XXXXXXXX-XXXX-XXXX-XXXX-399f92ae69f2","date":"2020-02-28T23:46:11"}}

Wouldn't it be possible to use the CLI here as well as in auth_method_azure_cli_token

The command should be: az ad sp show --id http://SP-Name

Resource IDs: Adding `FromParseResult` to the `resourceids.ResourceID` interface

At this point in time the Resource ID structs which implement resourceids.ResourceID implement two top-level Parse functions:

ParseXXXID(input string) (*XXXID, error)
ParseXXXIDInsensitively(input string) (*XXXID, error)

Whilst this works great (and is something we want to continue using), both #188 and #189 have use-cases which need to be able to parse a given Resource ID with only access to the type - and as such are unable to take advantage of these Parse functions.

Instead we can support both of these - and reduce the TLOC by introducing a new method to the ResourceID interface:

FromParseResult(input resourceids.ParseResult) error

Which contains the assignment logic that today exists both inside the ParseXXX and ParseXXXInsensitively functions:

func (id *AvailabilitySetId) FromParseResult(input resourceids.ParseResult) error {
	var ok bool

	if id.SubscriptionId, ok = input.Parsed["subscriptionId"]; !ok {
		return resourceids.NewSegmentNotSpecifiedError(id, "subscriptionId", input)
	}

	if id.ResourceGroupName, ok = input.Parsed["resourceGroupName"]; !ok {
		return resourceids.NewSegmentNotSpecifiedError(id, "resourceGroupName", input)
	}

	if id.AvailabilitySetName, ok = input.Parsed["availabilitySetName"]; !ok {
		return resourceids.NewSegmentNotSpecifiedError(id, "availabilitySetName", input)
	}

	return nil
}

This means that both of the Parse functions can be updated to call this new FromParseResult function, meaning that we can update each Resource ID from:

// ParseAvailabilitySetID parses 'input' into a AvailabilitySetId
func ParseAvailabilitySetID(input string) (*AvailabilitySetId, error) {
	parser := resourceids.NewParserFromResourceIdType(AvailabilitySetId{})
	parsed, err := parser.Parse(input, false)
	if err != nil {
		return nil, fmt.Errorf("parsing %q: %+v", input, err)
	}

	var ok bool
	id := AvailabilitySetId{}

	if id.SubscriptionId, ok = parsed.Parsed["subscriptionId"]; !ok {
		return nil, resourceids.NewSegmentNotSpecifiedError(id, "subscriptionId", *parsed)
	}

	if id.ResourceGroupName, ok = parsed.Parsed["resourceGroupName"]; !ok {
		return nil, resourceids.NewSegmentNotSpecifiedError(id, "resourceGroupName", *parsed)
	}

	if id.AvailabilitySetName, ok = parsed.Parsed["availabilitySetName"]; !ok {
		return nil, resourceids.NewSegmentNotSpecifiedError(id, "availabilitySetName", *parsed)
	}

	return &id, nil
}

// ParseAvailabilitySetIDInsensitively parses 'input' case-insensitively into a AvailabilitySetId
// note: this method should only be used for API response data and not user input
func ParseAvailabilitySetIDInsensitively(input string) (*AvailabilitySetId, error) {
	parser := resourceids.NewParserFromResourceIdType(AvailabilitySetId{})
	parsed, err := parser.Parse(input, true)
	if err != nil {
		return nil, fmt.Errorf("parsing %q: %+v", input, err)
	}

	var ok bool
	id := AvailabilitySetId{}

	if id.SubscriptionId, ok = parsed.Parsed["subscriptionId"]; !ok {
		return nil, resourceids.NewSegmentNotSpecifiedError(id, "subscriptionId", *parsed)
	}

	if id.ResourceGroupName, ok = parsed.Parsed["resourceGroupName"]; !ok {
		return nil, resourceids.NewSegmentNotSpecifiedError(id, "resourceGroupName", *parsed)
	}

	if id.AvailabilitySetName, ok = parsed.Parsed["availabilitySetName"]; !ok {
		return nil, resourceids.NewSegmentNotSpecifiedError(id, "availabilitySetName", *parsed)
	}

	return &id, nil
}

to:

// ParseAvailabilitySetID parses 'input' into a AvailabilitySetId
func ParseAvailabilitySetID(input string) (*AvailabilitySetId, error) {
	parser := resourceids.NewParserFromResourceIdType(&AvailabilitySetId{})
	parsed, err := parser.Parse(input, false)
	if err != nil {
		return nil, fmt.Errorf("parsing %q: %+v", input, err)
	}

	id := &AvailabilitySetId{}

	if err := id.FromParseResult(*parsed); err != nil {
		return nil, err
	}

	return id, nil
}

// ParseAvailabilitySetIDInsensitively parses 'input' case-insensitively into a AvailabilitySetId
// note: this method should only be used for API response data and not user input
func ParseAvailabilitySetIDInsensitively(input string) (*AvailabilitySetId, error) {
	parser := resourceids.NewParserFromResourceIdType(&AvailabilitySetId{})
	parsed, err := parser.Parse(input, true)
	if err != nil {
		return nil, fmt.Errorf("parsing %q: %+v", input, err)
	}

	id := &AvailabilitySetId{}
	if err := id.FromParseResult(*parsed); err != nil {
		return nil, err
	}
	return id, nil
}

func (id *AvailabilitySetId) FromParseResult(input resourceids.ParseResult) error {
	var ok bool

	if id.SubscriptionId, ok = input.Parsed["subscriptionId"]; !ok {
		return resourceids.NewSegmentNotSpecifiedError(id, "subscriptionId", input)
	}

	if id.ResourceGroupName, ok = input.Parsed["resourceGroupName"]; !ok {
		return resourceids.NewSegmentNotSpecifiedError(id, "resourceGroupName", input)
	}

	if id.AvailabilitySetName, ok = input.Parsed["availabilitySetName"]; !ok {
		return resourceids.NewSegmentNotSpecifiedError(id, "availabilitySetName", input)
	}

	return nil
}

In terms of rollout, this needs to be done in a few stages:

  1. Update the Go SDK Generator in hashicorp/pandora to output the new FromParseResult function (and the Parse functions to use this)
  2. Update the commonids in this repository to output the new FromParseResult function (and update the Parse functions to use this)
  3. Add the new function to the ResourceID interface, meaning that any resourceids.ResourceId implementation which doesn't support this will fail to compile (meaning we can mop-up any implementations)
  4. Vendor those changes into hashicorp/terraform-provider-azurerm

Which then enables #188 and #189

Dynamic Resource IDs / a replacement for `azure.ValidateResourceID`

The new Resource ID parsing logic defines Go Structs for each Resource ID Type, which allow parsing a Resource ID based on a known set of Resource ID segments, allowing us to provide differentiation based on the Resource ID Segment Type in the future.

However some Azure Resources support specifying any Azure Resource ID as an argument, meaning that today we have a validation function in the form of azure.ValidateResourceID which is used to ensure that the argument looks vaguely like an Azure Resource ID, but only ensures that we have a matching number of Key/Value Pairs.

Resource ID Scopes follow a similar pattern, with the Scope being an Azure Resource ID of some description - which (at this point in time) gets parsed out as a literal string value - but isn't validated. The behaviour of both of these differs from the rest of the new Resource ID parsing logic, which implies a known set of Resource ID Segments for a given Resource ID - since these can be any Azure Resource ID.

This means that we're currently unable to use the ResourceIDReference Common Schema types and that we're unable to offer dynamic recasing to workaround issues where the Azure API returns a different casing than is defined in the Terraform configuration.


This commit introduces a proof-of-concept dynamic recaser, which means that we can dynamically recase a Resource ID, for example:

recaser.ReCase("/subscriptions/11111/resourcegroups/bobby/providers/Microsoft.Compute/availabilitySets/HeYO")

gets output as:

/subscriptions/11111/resourceGroups/bobby/providers/Microsoft.Compute/availabilitySets/HeYO

This is achieved by having an init() in each Resource ID type register itself with the recaser, meaning that as Resource ID Types are imported, these get registered, meaning those Resource IDs can be used in this recasing function - which can be used on scopes, fixing issues such as hashicorp/terraform-provider-azurerm#23644.

This logic should also be reusable for an updated ValidateGenericAzureResourceID function too - which presumably requires parsing the Resource ID twice, once case-insensitively (to check that the Resource ID type is valid, where an error can mean it's the wrong type) - and a second time to ensure the casing matches what we expect (which can be surfaced as an error).

In both cases this allows for best-effort validation and recasing of Resource IDs, without knowing the specific types being used - meaning that in the event we know the Resource ID type we can ensure the casing matches what we're expecting - and in the event we don't we can fallback to validating only a common set of keys (subscriptions, resourceGroups` etc).

This obviously requires additional investigation, however in early testing seems promising - however is dependent on #190 which introduces a new FromParseResult method that this functionality uses - but in time will allow us to roll out the commonschema.ResourceIDReference to better hand Resource ID casing.

Azure CLI Parsing auth doesn't refresh the token

Migrating this over from hashicorp/terraform-provider-azurerm#502 where @jwendl opened the following issue:


Terraform requires az login after 30 minutes of in activity, even though az account list still works.

Terraform Version

Terraform v0.10.8

Affected Resource(s)

All

If this issue appears to affect multiple resources, it may be an issue with Terraform's core, so please mention this.

Terraform Configuration Files

resource "random_id" "server" {
  keepers = {
    azi_id = 1
  }

  byte_length = 8
}

resource "azurerm_resource_group" "test" {
    name = "resourceGroup1"
    location = "West Europe"
}

resource "azurerm_cosmosdb_account" "test" {
  name                = "${random_id.server.hex}"
  location            = "${azurerm_resource_group.test.location}"
  resource_group_name = "${azurerm_resource_group.test.name}"
  offer_type          = "Standard"
  consistency_policy {
    consistency_level = "BoundedStaleness"
  }

  failover_policy {
    location = "West Europe"
    priority = 0
  }

  failover_policy {
    location = "East US"
    priority = 1
  }

  tags {
    hello = "world"
  }
}

Debug Output

Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.


Error: Error running plan: 1 error(s) occurred:

  • provider.azurerm: No valid (unexpired) Azure CLI Auth Tokens found. Please run az login.

Panic Output

Expected Behavior

az account list => works
terraform plan => works

Actual Behavior

az account list => works
terraform plan => gives the message above
(this happens after a bit of time from when I did az login, the command works for a while, but go to lunch and it shows above error).

Steps to Reproduce

Please list the steps required to reproduce the issue, for example:

  1. terraform plan

Important Factoids

References

Investigate: Another Common Type for Availability Zone

SQL @ 2023-02-01-preview now contains:

type AvailabilityZoneType string

const (
	AvailabilityZoneTypeNoPreference AvailabilityZoneType = "NoPreference"
	AvailabilityZoneTypeOne          AvailabilityZoneType = "1"
	AvailabilityZoneTypeThree        AvailabilityZoneType = "3"
	AvailabilityZoneTypeTwo          AvailabilityZoneType = "2"
)

func PossibleValuesForAvailabilityZoneType() []string {
	return []string{
		string(AvailabilityZoneTypeNoPreference),
		string(AvailabilityZoneTypeOne),
		string(AvailabilityZoneTypeThree),
		string(AvailabilityZoneTypeTwo),
	}
}

func (s *AvailabilityZoneType) UnmarshalJSON(bytes []byte) error {
	var decoded string
	if err := json.Unmarshal(bytes, &decoded); err != nil {
		return fmt.Errorf("unmarshaling: %+v", err)
	}
	out, err := parseAvailabilityZoneType(decoded)
	if err != nil {
		return fmt.Errorf("parsing %q: %+v", decoded, err)
	}
	*s = *out
	return nil
}

Which differs from other services - we need to investigate whether this needs to become a new Common Type (e.g. is centrally defined/used as a common swagger type) - and then create custom Expand and Flatten functions to normalise this on our side.

New Common ID for Private Endpoint

Private Endpoint is used across the AzureRM Provider, as such when the network service gets updated, the API version specific import needs to change in every place the Private Endpoint Resource ID is referenced, as can be seen in hashicorp/terraform-provider-azurerm#23875

We should make Private Endpoint a Common ID, meaning these imports reference the commonids directory, rather than privateendpoints within a given version of the network service across the Provider - which'll reduce the churn.

Resource ID format: /subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/privateEndpoints/%s

Please upgrade Azure SDK to v30 and go-autorest to 11.7

Hi,

I have a problem authenticating the azurerm backend with a local Azure CLI config. This is basically not supported in go-autorest v10.15.4.

Support for this was added in v 11.2.4: https://github.com/draychev/go-autorest/commit/572ba1dd7e638bc5e62929f7576c5730f37928ba

Also, the azurerm provider switched recently to v30 of azure sdk and base on this comment: hashicorp/terraform#18968 (comment) the versions should be kept in sync.

I can help with the upgrades if needed.

Resource IDs: support for parsing Data Plane IDs

The Resource ID Parser (that is, the resourceids.Parse.Parse function) assumes that we're parsing an ARM-style Resource ID (although these also work for Microsoft Graph), e.g.

/providers/Microsoft.Blah
/subscriptions/XXX
/subscriptions/XXX/resourceGroups/XXX

However most Data Plane resources use Resource IDs in the format:

https://{accountName}.{domainSuffix}/{segment}/{segment}

Which means these have to be defined by hand today - and means that these don't use the new Resource ID Parser logic, which we'll want going forwards.


Note

Some APIs also (incorrectly) return:

https://{accountName}.{domainSuffix}:{port}/{segment}/{segment}

Note: when the protocol is https and a value for port of 443 is specified, we should be able to trim this from the Domain Suffix for normalization purposes.


As such we're going to need to extend the Resource ID Parser - and the Resource ID Segment types to support parsing Resource IDs in this format.

Whilst the examples above - and the majority of Resource IDs in this format use https as the protocol, we have some instances in AzureRM where it could be useful to validate additional protocols too (e.g. tcp://).

As such I suspect we're going to need to introduce 3 new Resource ID Segment types here:

  • ProtocolResourceIdSegment - for parsing the protocol in the format {protocol}:// - e.g. https from https://.
  • AccountNameResourceIdSegment - for parsing the Account Name from the hostname - e.g. account1 from account1.blob.storage.azure.com. In this instance we can assume a domain name in the format {account}.{domainSuffix}.
  • DomainSuffixResourceIdSegment - for parsing the domain suffix from the hostname - e.g. blob.storage.azure.com from account1.blob.storage.azure.com. In this instance we can assume a domain name in the format {account}.{domainSuffix}.

In addition the Resource ID format functions (.ID() and .String()) will need to be updated to account for this at the same time.

Once this work is completed, Pandora also needs to become aware of the new Resource ID Segment Types - however this can be done in two stages:

  1. Adding the new Constants to Pandora (Importer, Data API and SDK).
  2. Open an issue to track implementing full support for the new Resource ID Segment Types - which'll be needed for supporting Data Plane functionality in Pandora.

Azure CLI: refreshing the token fails

Community Note

  • Please vote on this issue by adding a ๐Ÿ‘ reaction to the original issue to help the community and maintainers prioritize this request
  • Please do not leave "+1" or "me too" comments, they generate extra noise for issue followers and do not help prioritize the request
  • If you are interested in working on this issue or have submitted a pull request, please leave a comment

Porting this over from hashicorp/terraform-provider-azurerm#2602 where it was originally reported by @btai24 and @torumakabe:

Error: Error applying plan:

1 error(s) occurred:

* azurerm_resource_group.shared (destroy): 1 error(s) occurred:

* azurerm_resource_group.shared: Error deleting Resource Group "myrg": azure.BearerAuthorizer#WithAuthorization: Failed to refresh the Token for request to
https://management.azure.com/subscriptions/myid/operationresults/myresult?api-version=2018-05-01: StatusCode=0 -- Original Error: Manually created ServicePrincipalToken does not contain secret material to retrieve a new access token

New Common ID for Application Security Group

Application Security Groups are used across the AzureRM Provider, as such when the network service gets updated, the API version specific import needs to change in every place the Application Security Group Resource ID is referenced, as can be seen in hashicorp/terraform-provider-azurerm#23875

We should make Application Security Group a Common ID, meaning these imports reference the commonids directory, rather than applicationsecuritygroups within a given version of the network service across the Provider - which'll reduce the churn.

Resource ID format: /subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/applicationSecurityGroups/%s

obtainAuthorizationToken endpoint string missing

Reviewing the code the endpoint is only valid for stack can you check for empty and remove the --resource from the az account command if it is empty. I believe that is causing the failures I am experiencing with a user az cli account:

az account get-access-token --resource --subscription -o=json
az account get-access-token: error: argument --resource: expected one argument
usage: az account get-access-token [-h] [--verbose] [--debug]
[--output {json,jsonc,table,tsv}]
[--query JMESPATH]
[--subscription SUBSCRIPTION]
[--resource RESOURCE]

-vs-
az account get-access-token --subscription 31deaf44-bdac-40c1-89fa-376f984921c0 -o=json
{
"accessToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6InBpVmxsb1FEU01LeGgxbTJ5Z3FHU1ZkZ0ZwQSIsImtpZCI6InBpVmxsb1FEU01LeGgxbTJ5Z3FHU1ZkZ0ZwQSJ9.eyJhdWQiOiJodHRwczovL21hbmFnZW1lbnQuY29yZS53aW5kb3dzLm5ldC8iLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC84M2IwZjVlYS02NDk5LTRlNTItODRlMS1mNTg2ZTMxOGQ4NjUvIiwiaWF0IjoxNTc5MjkzMDY2LCJuYmYiOjE1NzkyOTMwNjYsImV4cCI6MTU3OTI5Njk2NiwiYWNyIjoiMSIsImFpbyI6IjQyTmdZT2lSUEJiK2M2TFhsTmc4djQ3RE80M0RyeWhOckZubFZXUEtWOU4yYnZyWkEzOEIiLCJhbXIiOlsid2lhIl0sImFwcGlkIjoiMDRiMDc3OTUtOGRkYi00NjFhLWJiZWUtMDJmOWUxYmY3YjQ2IiwiYXBwaWRhY3IiOiIwIiwiZmFtaWx5X25hbWUiOiJLaXJrcGF0cmljayIsImdpdmVuX25hbWUiOiJNYXJrIiwiZ3JvdXBzIjpbIjRlY2E3ZDAxLTQ3YWUtNGIzNi1iMWRmLTQ3OTZhOTQ5MWYwZCIsIjNlMzI2MGVjLTE0MTktNGE4MC1hZTJhLTE4NjNiMTRkZDk2MiIsIjE3M2ZiNmVlLTdlY2YtNDU4YS1hM2IyLTNkYmU2MDgxMjU2NyIsIjM3ZTAxNTEzLTExNjMtNDc2MC1hOTRkLTU0NmQxYWM1MjNlNCIsImNiOTQwNGMwLWFhN2ItNGJkYS1hNWE2LWMwMjZjZWI4NjRkNyIsIjJmYzcxMTUxLTljOWMtNDYyNC04YTQyLTA2MjlkZjViZDlhNCIsIjk0ZTdhN2Q2LTAzMGItNGEwYi04ZTc5LWJiZGI2YTBkNDIwNCIsImU3ZTQyZWJiLTk2NjYtNDQ5Zi1hMGRlLTcyNjQ2NTk5NzRkZSIsIjU2MGVhZDE1LWY5ZjMtNGY1Yi05OTAyLWEzNmJjNWExMzI3ZSJdLCJpbl9jb3JwIjoidHJ1ZSIsImlwYWRkciI6IjEyLjM0LjM2LjI1MCIsIm5hbWUiOiJLaXJrcGF0cmljaywgTWFyayIsIm9pZCI6IjAxNmYyMDM3LTk3MDItNDdjOC1hNmIyLWRlYmFlNWY0MTlkMiIsIm9ucHJlbV9zaWQiOiJTLTEtNS0yMS0yMDk0ODEyNjE0LTE5NjI0OTE0MDEtMTIwMjE1OTMyMC0yNDkzMzUiLCJwdWlkIjoiMTAwMzIwMDA2RDE3MkQwOCIsInNjcCI6InVzZXJfaW1wZXJzb25hdGlvbiIsInN1YiI6IlpnU1pWcnFZdkhfMVdWM0pjNWFYSUhGTW9FdU1qa0RyejVzSWRXSC1Rd2ciLCJ0aWQiOiI4M2IwZjVlYS02NDk5LTRlNTItODRlMS1mNTg2ZTMxOGQ4NjUiLCJ1bmlxdWVfbmFtZSI6Im1hcmsua2lya3BhdHJpY2tAamFja3Nvbi5jb20iLCJ1cG4iOiJtYXJrLmtpcmtwYXRyaWNrQGphY2tzb24uY29tIiwidXRpIjoiUXg2TDNPTWk0RU9Ba1FtdTcwMWNBQSIsInZlciI6IjEuMCJ9.bkMmNP6ZUw7JLy9poasWOpUL_cnOjCduR7ciFaMHCRW8oFB5anqZgBbM5FCUCXrRg84FME2fTqzxlu29qz0R5KwqDH5peYyxw3JELvMSIFxThVeUmdCVwIpJjlqUo7yr3wl0P0bRZU6jlNsktIIjICkM5drxv7rU8TBsmkmg3vIq0zg9nHZuQVi_N55iJrRJN-vrtKToygt5k68GloM9c484NQ4Ji5MQuHeftSrx2rmiWw-TBI6CgCsQea13zi1A4tlSaZ8HfqH-GMuKTN-gRDrLKXJDKtjsFB55-EV4MNwblfz1hwWS8kGDfJxEufO9dOybUbRJx-4Be16ulsUDjg",
"expiresOn": "2020-01-17 16:36:05.562116",
"subscription": "mysub",
"tenant": "myten",
"tokenType": "Bearer"
}

and the resulting:
2020-01-17T14:41:40.703-0500 [DEBUG] plugin.terraform-provider-azurerm_v1.41.0_x4: Testing if Service Principal / Client Certificate is applicable for Authentication..
2020-01-17T14:41:40.703-0500 [DEBUG] plugin.terraform-provider-azurerm_v1.41.0_x4: Testing if Multi Tenant Service Principal / Client Secret is applicable for Authentication..
2020-01-17T14:41:40.703-0500 [DEBUG] plugin.terraform-provider-azurerm_v1.41.0_x4: Testing if Service Principal / Client Secret is applicable for Authentication..
2020-01-17T14:41:40.703-0500 [DEBUG] plugin.terraform-provider-azurerm_v1.41.0_x4: Testing if Managed Service Identity is applicable for Authentication..
2020-01-17T14:41:40.703-0500 [DEBUG] plugin.terraform-provider-azurerm_v1.41.0_x4: Testing if Obtaining a token from the Azure CLI is applicable for Authentication..
2020-01-17T14:41:40.703-0500 [DEBUG] plugin.terraform-provider-azurerm_v1.41.0_x4: Using Obtaining a token from the Azure CLI for Authentication
2020/01/17 14:41:42 [ERROR] module.microazure: eval: *terraform.EvalConfigProvider, err: Error building account: Error getting authenticated object ID: Error parsing json result from the Azure CLI: Error waiting for the Azure CLI: exit status 2
2020/01/17 14:41:42 [ERROR] module.microazure: eval: *terraform.EvalSequence, err: Error building account: Error getting authenticated object ID: Error parsing json result from the Azure CLI: Error waiting for the Azure CLI: exit status 2
2020/01/17 14:41:42 [ERROR] module.microazure: eval: *terraform.EvalOpFilter, err: Error building account: Error getting authenticated object ID: Error parsing json result from the Azure CLI: Error waiting for the Azure CLI: exit status 2
2020/01/17 14:41:42 [ERROR] module.microazure: eval: *terraform.EvalSequence, err: Error building account: Error getting authenticated object ID: Error parsing json result from the Azure CLI: Error waiting for the Azure CLI: exit status 2

auth: support for multiple user assigned identities

As originally reported in hashicorp/terraform-provider-azurerm#2441 by @LaurentLesle it's possible for a Virtual Machine / Virtual Machine Scale Set to have multiple identities assigned.

When using Managed Service Identity for authentication at this time we only support authenticating using a single identity; we'd need to get Azure/go-autorest updated to be able to (optionally) specify the Object ID/Client ID (as documented here) which should be used to obtain the credentials from the metadata endpoint.

Once that's available it should be possible for users to specify a custom Client ID which is passed to this function as a part of the Managed Service Identity auth method

Pointer `from`/ `to` generic return type mismatch.

Hey, there is a return type mismatch of the pointer's generic to and from methods.

Example copied from the code

// FromBool turns a boolean into a pointer to a boolean
func FromBool(input bool) *bool {
	...
}

// From is a generic function that returns the value of a pointer
// If the pointer is nil, a zero value for the underlying type of the pointer is returned.
func From[T any](input *T) (output T) {
	...
}
  • FromBool(...) returns a pointer
  • From(...) returns the value

The same for the generic To(...), etc...

Resource IDs: new Common ID for `CompositeResourceID`

Composite Resource IDs are two Resource IDs combined together with a pipe character (e.g. {first}|{second}) and are used in the AzureRM Provider primarily for Virtual Resources which map 1:1 - for example the association resources.

Whilst every time we need one of these today we end up reimplementing the same logic, we should be able to use Generics to have a single Composite Resource ID which both implements the ResourceId interface, but also takes two generic arguments for the ResourceId type, for example:

// to ensure this implements `.ID()` and `.String()` so this can be used as a regular Resource ID
var _ resourceids.ResourceID = CompositeResourceID[resourceids.ResourceId, resourceids.ResourceID]{}

type CompositeResourceID[T1 resourceids.ResourceId, T2 resourceids.ResourceID] struct {
  First T1
  Second T2
}

However where this currently falls down is the way that ResourceID Parsing is done, where the resourceids.Parser.Parse method returns a ParseResult, which is a mapping of the Resource ID Segment Name : Values - which must be then assigned to the Fields within each struct, which isn't currently possible without calling the specific Parse functions for T1 and T2 above.

As such, this work is dependent on #190, which adds a new FromParseResult method to the resourceids.ResourceId interface, but once that's available it should be possible to make a generic Parse function similar to below:

func Parse[resourceids.ResourceId, T2 resourceids.ResourceID](input string) (*CompositeResourceID[T1, T2], error) {
  components := strings.Split(input, "|")
  if len(components) != 2 {
    // TODO
  }

  output := CompositeResourceID[T1, T2]{}

  // Parse the first of the two Resource IDs from the components
  firstParser := resourceids.NewParserFromResourceIdType(T1{})
  firstParseResult, err := firstParser.Parse(components[0], false)
  if err != nil {
    // TODO
  }
  first, err := T1{}.FromParseResult(firstParseResult.ParseResult)
  if err != nil {
    // TODO
  }
  output.First = first
  
  // ...
}

Which would mean that the code used in the Provider can be reduced from:

components := strings.Split(d.GetID(), "|")
if len(components) != 2 {
  // TODO
}
first, err := ParseFirstID(components[0])
if err != nil {
  // TODO
}
second, err :=ParseSecondID(components[1])
if err != nil {
  // TODO
}

To the same as used elsewhere in the Provider:

id, err := ParseCompositeResourceID[FirstId, SecondId](d.GetID())
if err != nil {
  // TODO
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.