Code Monkey home page Code Monkey logo

terraform-azurerm-cdn-frontdoor's Introduction

Azure CDN FrontDoor

Changelog Notice Apache V2 License TF Registry

This Terraform module is designed to create an Azure CDN FrontDoor (Standard/Premium) resource.

Global versioning rule for Claranet Azure modules

Module version Terraform version AzureRM version
>= 7.x.x 1.3.x >= 3.0
>= 6.x.x 1.x >= 3.0
>= 5.x.x 0.15.x >= 2.0
>= 4.x.x 0.13.x / 0.14.x >= 2.0
>= 3.x.x 0.12.x >= 2.0
>= 2.x.x 0.12.x < 2.0
< 2.x.x 0.11.x < 2.0

Contributing

If you want to contribute to this repository, feel free to use our pre-commit git hook configuration which will help you automatically update and format some files for you by enforcing our Terraform code module best-practices.

More details are available in the CONTRIBUTING.md file.

Usage

This module is optimized to work with the Claranet terraform-wrapper tool which set some terraform variables in the environment needed by this module. More details about variables set by the terraform-wrapper available in the documentation.

module "azure_region" {
  source  = "claranet/regions/azurerm"
  version = "x.x.x"

  azure_region = var.azure_region
}

module "rg" {
  source  = "claranet/rg/azurerm"
  version = "x.x.x"

  location    = module.azure_region.location
  client_name = var.client_name
  environment = var.environment
  stack       = var.stack
}

module "logs" {
  source  = "claranet/run/azurerm//modules/logs"
  version = "x.x.x"

  client_name         = var.client_name
  environment         = var.environment
  stack               = var.stack
  location            = module.azure_region.location
  location_short      = module.azure_region.location_short
  resource_group_name = module.rg.resource_group_name
}

module "cdn_frontdoor" {
  source  = "claranet/cdn-frontdoor/azurerm"
  version = "x.x.x"

  client_name = var.client_name
  environment = var.environment
  stack       = var.stack

  resource_group_name = module.rg.resource_group_name

  sku_name = "Premium_AzureFrontDoor"

  logs_destinations_ids = [
    module.logs.log_analytics_workspace_id,
    module.logs.logs_storage_account_id,
  ]

  endpoints = [
    {
      name = "web"
    },
    {
      name    = "azure"
      enabled = false
    },
  ]

  origin_groups = [
    {
      name = "contoso"
      health_probe = {
        interval_in_seconds = 250
        path                = "/"
        protocol            = "Https"
        request_type        = "GET"
      }
      load_balancing = {
        successful_samples_required = 1
      }
    },
    {
      name = "contoso2"
      health_probe = {
        interval_in_seconds = 250
        path                = "/"
        protocol            = "Https"
        request_type        = "GET"
      }
    },
  ]

  origins = [
    {
      name                           = "web"
      origin_group_name              = "contoso"
      certificate_name_check_enabled = false
      host_name                      = "www.contoso.com"
    },
    {
      name                           = "azure"
      origin_group_name              = "contoso2"
      certificate_name_check_enabled = false
      host_name                      = "azure.contoso.com"
    },
  ]

  custom_domains = [{
    name      = "www"
    host_name = "www.contoso.com"
  }]

  routes = [
    {
      name                 = "route66"
      endpoint_name        = "web"
      origin_group_name    = "contoso"
      origins_names        = ["web", "azure"]
      forwarding_protocol  = "HttpsOnly"
      patterns_to_match    = ["/*"]
      supported_protocols  = ["Http", "Https"]
      custom_domains_names = ["www"]
      rule_sets_names      = ["my_rule_set", "my_rule_set2"]
    },
    {
      name                = "route2"
      endpoint_name       = "azure"
      origin_group_name   = "contoso2"
      origins_names       = ["web"]
      forwarding_protocol = "HttpsOnly"
      patterns_to_match   = ["/contoso"]
      supported_protocols = ["Http", "Https"]
      rule_sets_names     = ["my_rule_set", "my_rule_set2"]
    },
  ]

  rule_sets = [
    {
      name                 = "my_rule_set"
      custom_resource_name = "custom_rule"

      rules = [{
        name                 = "redirect"
        custom_resource_name = "myrulename"
        order                = 1
        actions = {
          response_header_actions = [
            {
              header_action = "Overwrite"
              header_name   = "Access-Control-Allow-Origin"
              value         = "https://www.foo.bar.fr"
            },
            {
              header_action = "Overwrite"
              header_name   = "Access-Control-Allow-Credentials"
              value         = "true"
            },
            {
              header_action = "Overwrite"
              header_name   = "Access-Control-Allow-Headers"
              value         = "Authorization, Content-Type, ocp-apim-subscription-key"
            },
            {
              header_action = "Overwrite"
              header_name   = "Access-Control-Allow-Methods"
              value         = "POST,PUT,GET,DELETE,OPTIONS"
            },
          ]
          url_rewrite_actions = [{
            source_pattern = "/"
            destination    = "/contoso"
          }]
        }
        conditions = {
          is_device_conditions = [{
            operator     = "Equal"
            match_values = ["Desktop"]
          }]
        }
      }]
    },
    {
      name                 = "my_rule_set2"
      custom_resource_name = "custom_rule2"
    },
  ]

  firewall_policies = [{
    name                              = "test"
    enabled                           = true
    mode                              = "Prevention"
    redirect_url                      = "https://www.contoso.com"
    custom_block_response_status_code = 403
    custom_block_response_body        = "PGh0bWw+CjxoZWFkZXI+PHRpdGxlPkhlbGxvPC90aXRsZT48L2hlYWRlcj4KPGJvZHk+CkhlbGxvIHdvcmxkCjwvYm9keT4KPC9odG1sPg=="

    custom_rules = [
      {
        name                           = "Rule1"
        enabled                        = true
        priority                       = 1
        rate_limit_duration_in_minutes = 1
        rate_limit_threshold           = 10
        type                           = "MatchRule"
        action                         = "Block"
        match_conditions = [{
          match_variable     = "RemoteAddr"
          operator           = "IPMatch"
          negation_condition = false
          match_values       = ["10.0.1.0/24", "10.0.0.0/24"]
        }]
      },
      {
        name                           = "Rule2"
        enabled                        = true
        priority                       = 2
        rate_limit_duration_in_minutes = 1
        rate_limit_threshold           = 10
        type                           = "MatchRule"
        action                         = "Block"
        match_conditions = [
          {
            match_variable     = "RemoteAddr"
            operator           = "IPMatch"
            negation_condition = false
            match_values       = ["192.168.1.0/24"]
          },
          {
            match_variable     = "RequestHeader"
            selector           = "UserAgent"
            operator           = "Contains"
            negation_condition = false
            match_values       = ["windows"]
            transforms         = ["Lowercase", "Trim"]
          },
        ]
      },
    ]

    managed_rules = [
      {
        type    = "DefaultRuleSet"
        version = "1.0"
        action  = "Log"
        exclusions = [{
          match_variable = "QueryStringArgNames"
          operator       = "Equals"
          selector       = "not_suspicious"
        }]
        overrides = [
          {
            rule_group_name = "PHP"
            rules = [{
              rule_id = "933100"
              enabled = false
              action  = "Block"
            }]
          },
          {
            rule_group_name = "SQLI"
            exclusions = [{
              match_variable = "QueryStringArgNames"
              operator       = "Equals"
              selector       = "really_not_suspicious"
            }]
            rules = [{
              rule_id = "942200"
              action  = "Block"
              exclusions = [{
                match_variable = "QueryStringArgNames"
                operator       = "Equals"
                selector       = "innocent"
              }]
            }]
          },
        ]
      },
      {
        type    = "Microsoft_BotManagerRuleSet"
        version = "1.0"
        action  = "Log"
      },
    ]
  }]

  security_policies = [{
    name                 = "MySecurityPolicy"
    custom_resource_name = "MyBetterNamedSecurityPolicy"
    firewall_policy_name = "test"
    patterns_to_match    = ["/*"]
    custom_domain_names  = ["www"]
    endpoint_names       = ["web", "azure"]
  }]

  extra_tags = {
    foo = "bar"
  }
}

Providers

Name Version
azurecaf ~> 1.2, >= 1.2.22
azurerm ~> 3.39

Modules

Name Source Version
diagnostics claranet/diagnostic-settings/azurerm ~> 6.5.0

Resources

Name Type
azurerm_cdn_frontdoor_custom_domain.cdn_frontdoor_custom_domain resource
azurerm_cdn_frontdoor_endpoint.cdn_frontdoor_endpoint resource
azurerm_cdn_frontdoor_firewall_policy.cdn_frontdoor_firewall_policy resource
azurerm_cdn_frontdoor_origin.cdn_frontdoor_origin resource
azurerm_cdn_frontdoor_origin_group.cdn_frontdoor_origin_group resource
azurerm_cdn_frontdoor_profile.cdn_frontdoor_profile resource
azurerm_cdn_frontdoor_route.cdn_frontdoor_route resource
azurerm_cdn_frontdoor_rule.cdn_frontdoor_rule resource
azurerm_cdn_frontdoor_rule_set.cdn_frontdoor_rule_set resource
azurerm_cdn_frontdoor_security_policy.cdn_frontdoor_security_policy resource
azurecaf_name.cdn_frontdoor_custom_domain data source
azurecaf_name.cdn_frontdoor_endpoint data source
azurecaf_name.cdn_frontdoor_firewall_policy data source
azurecaf_name.cdn_frontdoor_origin data source
azurecaf_name.cdn_frontdoor_origin_group data source
azurecaf_name.cdn_frontdoor_profile data source
azurecaf_name.cdn_frontdoor_route data source
azurecaf_name.cdn_frontdoor_rule data source
azurecaf_name.cdn_frontdoor_rule_set data source
azurecaf_name.cdn_frontdoor_security_policy data source

Inputs

Name Description Type Default Required
cdn_frontdoor_profile_name Specifies the name of the FrontDoor Profile. string "" no
client_name Client name/account used in naming. string n/a yes
custom_diagnostic_settings_name Custom name of the diagnostics settings, name will be 'default' if not set. string "default" no
custom_domains CDN FrontDoor Custom Domains configurations.
list(object({
name = string
custom_resource_name = optional(string)
host_name = string
dns_zone_id = optional(string)
tls = optional(object({
certificate_type = optional(string, "ManagedCertificate")
minimum_tls_version = optional(string, "TLS12")
cdn_frontdoor_secret_id = optional(string)
}), {})
}))
[] no
default_tags_enabled Option to enable or disable default tags. bool true no
endpoints CDN FrontDoor Endpoints configurations.
list(object({
name = string
prefix = optional(string)
custom_resource_name = optional(string)
enabled = optional(bool, true)
}))
[] no
environment Project environment. string n/a yes
extra_tags Extra tags to add. map(string) {} no
firewall_policies CDN Frontdoor Firewall Policies configurations.
list(object({
name = string
custom_resource_name = optional(string)
enabled = optional(bool, true)
mode = optional(string, "Prevention")
redirect_url = optional(string)
custom_block_response_status_code = optional(number)
custom_block_response_body = optional(string)
custom_rules = optional(list(object({
name = string
action = string
enabled = optional(bool, true)
priority = number
type = string
rate_limit_duration_in_minutes = optional(number, 1)
rate_limit_threshold = optional(number, 10)
match_conditions = list(object({
match_variable = string
match_values = list(string)
operator = string
selector = optional(string)
negate_condition = optional(bool)
transforms = optional(list(string), [])
}))
})), [])
managed_rules = optional(list(object({
type = string
version = optional(string, "1.0")
action = string
exclusions = optional(list(object({
match_variable = string
operator = string
selector = string
})), [])
overrides = optional(list(object({
rule_group_name = string
exclusions = optional(list(object({
match_variable = string
operator = string
selector = string
})), [])
rules = optional(list(object({
rule_id = string
action = string
enabled = optional(bool, true)
exclusions = optional(list(object({
match_variable = string
operator = string
selector = string
})), []) })), [])
})), [])
})), [])
}))
[] no
logs_categories Log categories to send to destinations. list(string) null no
logs_destinations_ids List of destination resources IDs for logs diagnostics destination. Can be Storage Account, Log Analytics Workspace and Event Hub. No more than one of each can be set. Empty list to disable logging. list(string) n/a yes
logs_metrics_categories Metrics categories to send to destinations. list(string) null no
name_prefix Optional prefix for the generated name string "" no
name_suffix Optional suffix for the generated name string "" no
origin_groups CDN FrontDoor Origin Groups configurations.
list(object({
name = string
custom_resource_name = optional(string)
session_affinity_enabled = optional(bool, true)
restore_traffic_time_to_healed_or_new_endpoint_in_minutes = optional(number, 10)
health_probe = optional(object({
interval_in_seconds = number
path = string
protocol = string
request_type = string
}))
load_balancing = optional(object({
additional_latency_in_milliseconds = optional(number, 50)
sample_size = optional(number, 4)
successful_samples_required = optional(number, 3)
}), {})
}))
[] no
origins CDN FrontDoor Origins configurations.
list(object({
name = string
custom_resource_name = optional(string)
origin_group_name = string
enabled = optional(bool, true)
certificate_name_check_enabled = optional(bool, true)

host_name = string
http_port = optional(number, 80)
https_port = optional(number, 443)
origin_host_header = optional(string)
priority = optional(number, 1)
weight = optional(number, 1)

private_link = optional(object({
request_message = optional(string)
target_type = optional(string)
location = string
private_link_target_id = string
}))
}))
[] no
resource_group_name Resource group name. string n/a yes
response_timeout_seconds Specifies the maximum response timeout in seconds. Possible values are between 16 and 240 seconds (inclusive). number 120 no
routes CDN FrontDoor Routes configurations.
list(object({
name = string
custom_resource_name = optional(string)
enabled = optional(bool, true)

endpoint_name = string
origin_group_name = string
origins_names = list(string)

forwarding_protocol = optional(string, "HttpsOnly")
patterns_to_match = optional(list(string), ["/*"])
supported_protocols = optional(list(string), ["Http", "Https"])
cache = optional(object({
query_string_caching_behavior = optional(string, "IgnoreQueryString")
query_strings = optional(list(string))
compression_enabled = optional(bool, false)
content_types_to_compress = optional(list(string))
}))

custom_domains_names = optional(list(string), [])
origin_path = optional(string, "/")
rule_sets_names = optional(list(string), [])

https_redirect_enabled = optional(bool, true)
link_to_default_domain = optional(bool, true)
}))
[] no
rule_sets CDN FrontDoor Rule Sets and associated Rules configurations.
list(object({
name = string
custom_resource_name = optional(string)
rules = optional(list(object({
name = string
custom_resource_name = optional(string)
order = number
behavior_on_match = optional(string, "Continue")

actions = object({
url_rewrite_actions = optional(list(object({
source_pattern = optional(string)
destination = optional(string)
preserve_unmatched_path = optional(bool, false)
})), [])
url_redirect_actions = optional(list(object({
redirect_type = string
destination_hostname = string
redirect_protocol = optional(string, "MatchRequest")
destination_path = optional(string, "")
query_string = optional(string, "")
destination_fragment = optional(string, "")
})), [])
route_configuration_override_actions = optional(list(object({
cache_duration = optional(string, "1.12:00:00")
cdn_frontdoor_origin_group_id = optional(string)
forwarding_protocol = optional(string, "MatchRequest")
query_string_caching_behavior = optional(string, "IgnoreQueryString")
query_string_parameters = optional(list(string))
compression_enabled = optional(bool, false)
cache_behavior = optional(string, "HonorOrigin")
})), [])
request_header_actions = optional(list(object({
header_action = string
header_name = string
value = optional(string)
})), [])
response_header_actions = optional(list(object({
header_action = string
header_name = string
value = optional(string)
})), [])
})

conditions = optional(object({
remote_address_conditions = optional(list(object({
operator = string
negate_condition = optional(bool, false)
match_values = optional(list(string))
})), [])
request_method_conditions = optional(list(object({
match_values = list(string)
operator = optional(string, "Equal")
negate_condition = optional(bool, false)
})), [])
query_string_conditions = optional(list(object({
operator = string
negate_condition = optional(bool, false)
match_values = optional(list(string))
transforms = optional(list(string), ["Lowercase"])
})), [])
post_args_conditions = optional(list(object({
post_args_name = string
operator = string
negate_condition = optional(bool, false)
match_values = optional(list(string))
transforms = optional(list(string), ["Lowercase"])
})), [])
request_uri_conditions = optional(list(object({
operator = string
negate_condition = optional(bool, false)
match_values = optional(list(string))
transforms = optional(list(string), ["Lowercase"])
})), [])
request_header_conditions = optional(list(object({
header_name = string
operator = string
negate_condition = optional(bool, false)
match_values = optional(list(string))
transforms = optional(list(string), ["Lowercase"])
})), [])
request_body_conditions = optional(list(object({
operator = string
match_values = list(string)
negate_condition = optional(bool, false)
transforms = optional(list(string), ["Lowercase"])
})), [])
request_scheme_conditions = optional(list(object({
operator = optional(string, "Equal")
negate_condition = optional(bool, false)
match_values = optional(string, "HTTP")
})), [])
url_path_conditions = optional(list(object({
operator = string
negate_condition = optional(bool, false)
match_values = optional(list(string))
transforms = optional(list(string), ["Lowercase"])
})), [])
url_file_extension_conditions = optional(list(object({
operator = string
negate_condition = optional(bool, false)
match_values = list(string)
transforms = optional(list(string), ["Lowercase"])
})), [])
url_filename_conditions = optional(list(object({
operator = string
match_values = list(string)
negate_condition = optional(bool, false)
transforms = optional(list(string), ["Lowercase"])
})), [])
http_version_conditions = optional(list(object({
match_values = list(string)
operator = optional(string, "Equal")
negate_condition = optional(bool, false)
})), [])
cookies_conditions = optional(list(object({
cookie_name = string
operator = string
negate_condition = optional(bool, false)
match_values = optional(list(string))
transforms = optional(list(string), ["Lowercase"])
})), [])
is_device_conditions = optional(list(object({
operator = optional(string, "Equal")
negate_condition = optional(bool, false)
match_values = optional(list(string), ["Mobile"])
})), [])
socket_address_conditions = optional(list(object({
operator = string
negate_condition = optional(bool, false)
match_values = optional(list(string))
})), [])
client_port_conditions = optional(list(object({
operator = string
negate_condition = optional(bool, false)
match_values = optional(list(string))
})), [])
server_port_conditions = optional(list(object({
operator = string
match_values = list(string)
negate_condition = optional(bool, false)
})), [])
host_name_conditions = optional(list(object({
operator = string
match_values = optional(list(string))
transforms = optional(list(string), ["Lowercase"])
})), [])
ssl_protocol_conditions = optional(list(object({
match_values = list(string)
operator = optional(string, "Equal")
negate_condition = optional(bool, false)
})), [])
}), null)
})), [])
}))
[] no
security_policies CDN FrontDoor Security policies configurations.
list(object({
name = string
custom_resource_name = optional(string)
firewall_policy_name = string
patterns_to_match = optional(list(string), ["/*"])
custom_domain_names = optional(list(string), [])
endpoint_names = optional(list(string), [])
}))
[] no
sku_name Specifies the SKU for this CDN FrontDoor Profile. Possible values include Standard_AzureFrontDoor and Premium_AzureFrontDoor. string "Standard_AzureFrontDoor" no
stack Project stack name. string n/a yes
use_caf_naming Use the Azure CAF naming provider to generate default resource name. custom_name override this if set. Legacy default name is used if this is set to false. bool true no

Outputs

Name Description
custom_domains CDN FrontDoor custom domains outputs.
endpoints CDN FrontDoor endpoints outputs.
firewall_policies CDN FrontDoor firewall policies outputs.
origin_groups CDN FrontDoor origin groups outputs.
origins CDN FrontDoor origins outputs.
profile_id The ID of the CDN FrontDoor Profile.
profile_name The name of the CDN FrontDoor Profile.
rule_sets CDN FrontDoor rule sets outputs.
rules CDN FrontDoor rules outputs.
security_policies CDN FrontDoor security policies outputs.

Related documentation

Azure Front Door REST API: docs.microsoft.com/en-us/rest/api/frontdoor/

terraform-azurerm-cdn-frontdoor's People

Contributors

bd-clara avatar bzspi avatar maxpoullain avatar rossifumax avatar shr3ps avatar zfiel avatar

Stargazers

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

Watchers

 avatar  avatar  avatar  avatar  avatar

terraform-azurerm-cdn-frontdoor's Issues

[BUG] Route Configuration Override Actions when is disabled

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

Terraform Version

1.5.7

AzureRM Provider Version

3.79.0

Affected Resource(s)/Data Source(s)

route_configuration_override_action

Terraform Configuration Files

rules = [
  {
    name                 = "redirectroot"
    custom_resource_name = "redirectroot"
    order                = 1
    conditions = {
      url_path_conditions = [{
        operator     = "Equal"
        match_values = ["/"]
        transforms   = []
      }]
    }
    actions = {
      route_configuration_override_actions = [{
        cdn_frontdoor_origin_group_id = data.azurerm_cdn_frontdoor_origin_group.origin_ea_website.id
        forwarding_protocol = "MatchRequest"
        cache_behavior = "Disabled"
        query_string_parameters = []
      }]
    }
  }
]

Debug Output/Panic Output

โ•ท
โ”‚ Error: expanding 'actions': the 'route_configuration_override_action' block is not valid, if the 'cache_behavior' is set to 'Disabled' you cannot define the 'query_string_caching_behavior', got "IgnoreQueryString"
โ”‚
โ”‚   with module.app_service_ea_front_cdn.azurerm_cdn_frontdoor_rule.cdn_frontdoor_rule["web-app-rule-redirect.redirectroot"],
โ”‚   on .terraform/modules/app_service_ea_front_cdn/r-cdn-frontdoor-rules.tf line 11, in resource "azurerm_cdn_frontdoor_rule" "cdn_frontdoor_rule":
โ”‚   11: resource "azurerm_cdn_frontdoor_rule" "cdn_frontdoor_rule" {
โ”‚
โ•ต

Expected Behaviour

When cache_behavior is setup as "Disabled" it should ignore cache_duration, query_string_caching_behavior, query_string_parameters, compression_enabled on route_configuration_override_action https://github.com/claranet/terraform-azurerm-cdn-frontdoor/blob/master/r-cdn-frontdoor-rules.tf#L45

Actual Behaviour

No response

Steps to Reproduce

No response

Important Factoids

No response

References

No response

[BUG] Rule url path condition must not begin with the URLs leading slash

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

Terraform Version

1.5.7

AzureRM Provider Version

3.86.0

Affected Resource(s)/Data Source(s)

azurerm_cdn_frontdoor_rule

Terraform Configuration Files

conditions = {
  url_path_conditions = [{
    operator     = "Contains"
    match_values = ["/home"]
    transforms   = []
  }]
}

Debug Output/Panic Output

โ”‚ Error: "conditions.0.url_path_condition.0.match_values.0" must not begin with the URLs leading slash(e.g. /), got "/home"
โ”‚
โ”‚   with module.app_service_ea_front_cdn.azurerm_cdn_frontdoor_rule.cdn_frontdoor_rule["web-app-rule-redirect.redirecthome"],
โ”‚   on .terraform/modules/app_service_ea_front_cdn/r-cdn-frontdoor-rules.tf line 11, in resource "azurerm_cdn_frontdoor_rule" "cdn_frontdoor_rule":
โ”‚   11: resource "azurerm_cdn_frontdoor_rule" "cdn_frontdoor_rule" {
โ”‚

Expected Behaviour

No response

Actual Behaviour

No response

Steps to Reproduce

No response

Important Factoids

No response

References

No response

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.