github.com/inge4pres/terraform@v0.7.5-0.20160930053151-bd083f84f376/builtin/providers/aws/resource_aws_cloudfront_distribution.go (about)

     1  package aws
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"time"
     7  
     8  	"github.com/aws/aws-sdk-go/aws"
     9  	"github.com/aws/aws-sdk-go/service/cloudfront"
    10  	"github.com/hashicorp/terraform/helper/resource"
    11  	"github.com/hashicorp/terraform/helper/schema"
    12  )
    13  
    14  func resourceAwsCloudFrontDistribution() *schema.Resource {
    15  	return &schema.Resource{
    16  		Create: resourceAwsCloudFrontDistributionCreate,
    17  		Read:   resourceAwsCloudFrontDistributionRead,
    18  		Update: resourceAwsCloudFrontDistributionUpdate,
    19  		Delete: resourceAwsCloudFrontDistributionDelete,
    20  		Importer: &schema.ResourceImporter{
    21  			State: resourceAwsCloudFrontDistributionImport,
    22  		},
    23  
    24  		Schema: map[string]*schema.Schema{
    25  			"arn": {
    26  				Type:     schema.TypeString,
    27  				Computed: true,
    28  			},
    29  			"aliases": &schema.Schema{
    30  				Type:     schema.TypeSet,
    31  				Optional: true,
    32  				Elem:     &schema.Schema{Type: schema.TypeString},
    33  				Set:      aliasesHash,
    34  			},
    35  			"cache_behavior": &schema.Schema{
    36  				Type:     schema.TypeSet,
    37  				Optional: true,
    38  				Set:      cacheBehaviorHash,
    39  				Elem: &schema.Resource{
    40  					Schema: map[string]*schema.Schema{
    41  						"allowed_methods": &schema.Schema{
    42  							Type:     schema.TypeList,
    43  							Required: true,
    44  							Elem:     &schema.Schema{Type: schema.TypeString},
    45  						},
    46  						"cached_methods": &schema.Schema{
    47  							Type:     schema.TypeList,
    48  							Required: true,
    49  							Elem:     &schema.Schema{Type: schema.TypeString},
    50  						},
    51  						"compress": &schema.Schema{
    52  							Type:     schema.TypeBool,
    53  							Optional: true,
    54  							Default:  false,
    55  						},
    56  						"default_ttl": &schema.Schema{
    57  							Type:     schema.TypeInt,
    58  							Required: true,
    59  						},
    60  						"forwarded_values": &schema.Schema{
    61  							Type:     schema.TypeSet,
    62  							Required: true,
    63  							Set:      forwardedValuesHash,
    64  							MaxItems: 1,
    65  							Elem: &schema.Resource{
    66  								Schema: map[string]*schema.Schema{
    67  									"cookies": &schema.Schema{
    68  										Type:     schema.TypeSet,
    69  										Required: true,
    70  										Set:      cookiePreferenceHash,
    71  										MaxItems: 1,
    72  										Elem: &schema.Resource{
    73  											Schema: map[string]*schema.Schema{
    74  												"forward": &schema.Schema{
    75  													Type:     schema.TypeString,
    76  													Required: true,
    77  												},
    78  												"whitelisted_names": &schema.Schema{
    79  													Type:     schema.TypeList,
    80  													Optional: true,
    81  													Elem:     &schema.Schema{Type: schema.TypeString},
    82  												},
    83  											},
    84  										},
    85  									},
    86  									"headers": &schema.Schema{
    87  										Type:     schema.TypeList,
    88  										Optional: true,
    89  										Elem:     &schema.Schema{Type: schema.TypeString},
    90  									},
    91  									"query_string": &schema.Schema{
    92  										Type:     schema.TypeBool,
    93  										Required: true,
    94  									},
    95  									"query_string_cache_keys": &schema.Schema{
    96  										Type:     schema.TypeList,
    97  										Optional: true,
    98  										Elem:     &schema.Schema{Type: schema.TypeString},
    99  									},
   100  								},
   101  							},
   102  						},
   103  						"max_ttl": &schema.Schema{
   104  							Type:     schema.TypeInt,
   105  							Required: true,
   106  						},
   107  						"min_ttl": &schema.Schema{
   108  							Type:     schema.TypeInt,
   109  							Required: true,
   110  						},
   111  						"path_pattern": &schema.Schema{
   112  							Type:     schema.TypeString,
   113  							Required: true,
   114  						},
   115  						"smooth_streaming": &schema.Schema{
   116  							Type:     schema.TypeBool,
   117  							Optional: true,
   118  						},
   119  						"target_origin_id": &schema.Schema{
   120  							Type:     schema.TypeString,
   121  							Required: true,
   122  						},
   123  						"trusted_signers": &schema.Schema{
   124  							Type:     schema.TypeList,
   125  							Optional: true,
   126  							Elem:     &schema.Schema{Type: schema.TypeString},
   127  						},
   128  						"viewer_protocol_policy": &schema.Schema{
   129  							Type:     schema.TypeString,
   130  							Required: true,
   131  						},
   132  					},
   133  				},
   134  			},
   135  			"comment": &schema.Schema{
   136  				Type:     schema.TypeString,
   137  				Optional: true,
   138  			},
   139  			"custom_error_response": &schema.Schema{
   140  				Type:     schema.TypeSet,
   141  				Optional: true,
   142  				Set:      customErrorResponseHash,
   143  				Elem: &schema.Resource{
   144  					Schema: map[string]*schema.Schema{
   145  						"error_caching_min_ttl": &schema.Schema{
   146  							Type:     schema.TypeInt,
   147  							Optional: true,
   148  						},
   149  						"error_code": &schema.Schema{
   150  							Type:     schema.TypeInt,
   151  							Required: true,
   152  						},
   153  						"response_code": &schema.Schema{
   154  							Type:     schema.TypeInt,
   155  							Optional: true,
   156  						},
   157  						"response_page_path": &schema.Schema{
   158  							Type:     schema.TypeString,
   159  							Optional: true,
   160  						},
   161  					},
   162  				},
   163  			},
   164  			"default_cache_behavior": &schema.Schema{
   165  				Type:     schema.TypeSet,
   166  				Required: true,
   167  				Set:      defaultCacheBehaviorHash,
   168  				MaxItems: 1,
   169  				Elem: &schema.Resource{
   170  					Schema: map[string]*schema.Schema{
   171  						"allowed_methods": &schema.Schema{
   172  							Type:     schema.TypeList,
   173  							Required: true,
   174  							Elem:     &schema.Schema{Type: schema.TypeString},
   175  						},
   176  						"cached_methods": &schema.Schema{
   177  							Type:     schema.TypeList,
   178  							Required: true,
   179  							Elem:     &schema.Schema{Type: schema.TypeString},
   180  						},
   181  						"compress": &schema.Schema{
   182  							Type:     schema.TypeBool,
   183  							Optional: true,
   184  							Default:  false,
   185  						},
   186  						"default_ttl": &schema.Schema{
   187  							Type:     schema.TypeInt,
   188  							Required: true,
   189  						},
   190  						"forwarded_values": &schema.Schema{
   191  							Type:     schema.TypeSet,
   192  							Required: true,
   193  							Set:      forwardedValuesHash,
   194  							MaxItems: 1,
   195  							Elem: &schema.Resource{
   196  								Schema: map[string]*schema.Schema{
   197  									"cookies": &schema.Schema{
   198  										Type:     schema.TypeSet,
   199  										Optional: true,
   200  										Set:      cookiePreferenceHash,
   201  										MaxItems: 1,
   202  										Elem: &schema.Resource{
   203  											Schema: map[string]*schema.Schema{
   204  												"forward": &schema.Schema{
   205  													Type:     schema.TypeString,
   206  													Required: true,
   207  												},
   208  												"whitelisted_names": &schema.Schema{
   209  													Type:     schema.TypeList,
   210  													Optional: true,
   211  													Elem:     &schema.Schema{Type: schema.TypeString},
   212  												},
   213  											},
   214  										},
   215  									},
   216  									"headers": &schema.Schema{
   217  										Type:     schema.TypeList,
   218  										Optional: true,
   219  										Elem:     &schema.Schema{Type: schema.TypeString},
   220  									},
   221  									"query_string": &schema.Schema{
   222  										Type:     schema.TypeBool,
   223  										Required: true,
   224  									},
   225  									"query_string_cache_keys": &schema.Schema{
   226  										Type:     schema.TypeList,
   227  										Optional: true,
   228  										Elem:     &schema.Schema{Type: schema.TypeString},
   229  									},
   230  								},
   231  							},
   232  						},
   233  						"max_ttl": &schema.Schema{
   234  							Type:     schema.TypeInt,
   235  							Required: true,
   236  						},
   237  						"min_ttl": &schema.Schema{
   238  							Type:     schema.TypeInt,
   239  							Required: true,
   240  						},
   241  						"smooth_streaming": &schema.Schema{
   242  							Type:     schema.TypeBool,
   243  							Optional: true,
   244  						},
   245  						"target_origin_id": &schema.Schema{
   246  							Type:     schema.TypeString,
   247  							Required: true,
   248  						},
   249  						"trusted_signers": &schema.Schema{
   250  							Type:     schema.TypeList,
   251  							Optional: true,
   252  							Elem:     &schema.Schema{Type: schema.TypeString},
   253  						},
   254  						"viewer_protocol_policy": &schema.Schema{
   255  							Type:     schema.TypeString,
   256  							Required: true,
   257  						},
   258  					},
   259  				},
   260  			},
   261  			"default_root_object": &schema.Schema{
   262  				Type:     schema.TypeString,
   263  				Optional: true,
   264  			},
   265  			"enabled": &schema.Schema{
   266  				Type:     schema.TypeBool,
   267  				Required: true,
   268  			},
   269  			"http_version": &schema.Schema{
   270  				Type:         schema.TypeString,
   271  				Optional:     true,
   272  				Default:      "http2",
   273  				ValidateFunc: validateHTTP,
   274  			},
   275  			"logging_config": &schema.Schema{
   276  				Type:     schema.TypeSet,
   277  				Optional: true,
   278  				Set:      loggingConfigHash,
   279  				MaxItems: 1,
   280  				Elem: &schema.Resource{
   281  					Schema: map[string]*schema.Schema{
   282  						"bucket": &schema.Schema{
   283  							Type:     schema.TypeString,
   284  							Required: true,
   285  						},
   286  						"include_cookies": &schema.Schema{
   287  							Type:     schema.TypeBool,
   288  							Optional: true,
   289  							Default:  false,
   290  						},
   291  						"prefix": &schema.Schema{
   292  							Type:     schema.TypeString,
   293  							Optional: true,
   294  							Default:  "",
   295  						},
   296  					},
   297  				},
   298  			},
   299  			"origin": &schema.Schema{
   300  				Type:     schema.TypeSet,
   301  				Required: true,
   302  				Set:      originHash,
   303  				Elem: &schema.Resource{
   304  					Schema: map[string]*schema.Schema{
   305  						"custom_origin_config": &schema.Schema{
   306  							Type:          schema.TypeSet,
   307  							Optional:      true,
   308  							ConflictsWith: []string{"origin.s3_origin_config"},
   309  							Set:           customOriginConfigHash,
   310  							MaxItems:      1,
   311  							Elem: &schema.Resource{
   312  								Schema: map[string]*schema.Schema{
   313  									"http_port": &schema.Schema{
   314  										Type:     schema.TypeInt,
   315  										Required: true,
   316  									},
   317  									"https_port": &schema.Schema{
   318  										Type:     schema.TypeInt,
   319  										Required: true,
   320  									},
   321  									"origin_protocol_policy": &schema.Schema{
   322  										Type:     schema.TypeString,
   323  										Required: true,
   324  									},
   325  									"origin_ssl_protocols": &schema.Schema{
   326  										Type:     schema.TypeList,
   327  										Required: true,
   328  										Elem:     &schema.Schema{Type: schema.TypeString},
   329  									},
   330  								},
   331  							},
   332  						},
   333  						"domain_name": &schema.Schema{
   334  							Type:     schema.TypeString,
   335  							Required: true,
   336  						},
   337  						"custom_header": &schema.Schema{
   338  							Type:     schema.TypeSet,
   339  							Optional: true,
   340  							Set:      originCustomHeaderHash,
   341  							Elem: &schema.Resource{
   342  								Schema: map[string]*schema.Schema{
   343  									"name": &schema.Schema{
   344  										Type:     schema.TypeString,
   345  										Required: true,
   346  									},
   347  									"value": &schema.Schema{
   348  										Type:     schema.TypeString,
   349  										Required: true,
   350  									},
   351  								},
   352  							},
   353  						},
   354  						"origin_id": &schema.Schema{
   355  							Type:     schema.TypeString,
   356  							Required: true,
   357  						},
   358  						"origin_path": &schema.Schema{
   359  							Type:     schema.TypeString,
   360  							Optional: true,
   361  						},
   362  						"s3_origin_config": &schema.Schema{
   363  							Type:          schema.TypeSet,
   364  							Optional:      true,
   365  							ConflictsWith: []string{"origin.custom_origin_config"},
   366  							Set:           s3OriginConfigHash,
   367  							MaxItems:      1,
   368  							Elem: &schema.Resource{
   369  								Schema: map[string]*schema.Schema{
   370  									"origin_access_identity": &schema.Schema{
   371  										Type:     schema.TypeString,
   372  										Required: true,
   373  									},
   374  								},
   375  							},
   376  						},
   377  					},
   378  				},
   379  			},
   380  			"price_class": &schema.Schema{
   381  				Type:     schema.TypeString,
   382  				Optional: true,
   383  				Default:  "PriceClass_All",
   384  			},
   385  			"restrictions": &schema.Schema{
   386  				Type:     schema.TypeSet,
   387  				Required: true,
   388  				Set:      restrictionsHash,
   389  				MaxItems: 1,
   390  				Elem: &schema.Resource{
   391  					Schema: map[string]*schema.Schema{
   392  						"geo_restriction": &schema.Schema{
   393  							Type:     schema.TypeSet,
   394  							Required: true,
   395  							Set:      geoRestrictionHash,
   396  							MaxItems: 1,
   397  							Elem: &schema.Resource{
   398  								Schema: map[string]*schema.Schema{
   399  									"locations": &schema.Schema{
   400  										Type:     schema.TypeList,
   401  										Optional: true,
   402  										Elem:     &schema.Schema{Type: schema.TypeString},
   403  									},
   404  									"restriction_type": &schema.Schema{
   405  										Type:     schema.TypeString,
   406  										Required: true,
   407  									},
   408  								},
   409  							},
   410  						},
   411  					},
   412  				},
   413  			},
   414  			"viewer_certificate": &schema.Schema{
   415  				Type:     schema.TypeSet,
   416  				Required: true,
   417  				Set:      viewerCertificateHash,
   418  				MaxItems: 1,
   419  				Elem: &schema.Resource{
   420  					Schema: map[string]*schema.Schema{
   421  						"acm_certificate_arn": &schema.Schema{
   422  							Type:          schema.TypeString,
   423  							Optional:      true,
   424  							ConflictsWith: []string{"viewer_certificate.cloudfront_default_certificate", "viewer_certificate.iam_certificate_id"},
   425  						},
   426  						"cloudfront_default_certificate": &schema.Schema{
   427  							Type:          schema.TypeBool,
   428  							Optional:      true,
   429  							ConflictsWith: []string{"viewer_certificate.acm_certificate_arn", "viewer_certificate.iam_certificate_id"},
   430  						},
   431  						"iam_certificate_id": &schema.Schema{
   432  							Type:          schema.TypeString,
   433  							Optional:      true,
   434  							ConflictsWith: []string{"viewer_certificate.acm_certificate_arn", "viewer_certificate.cloudfront_default_certificate"},
   435  						},
   436  						"minimum_protocol_version": &schema.Schema{
   437  							Type:     schema.TypeString,
   438  							Optional: true,
   439  							Default:  "SSLv3",
   440  						},
   441  						"ssl_support_method": &schema.Schema{
   442  							Type:     schema.TypeString,
   443  							Optional: true,
   444  						},
   445  					},
   446  				},
   447  			},
   448  			"web_acl_id": &schema.Schema{
   449  				Type:     schema.TypeString,
   450  				Optional: true,
   451  			},
   452  			"caller_reference": &schema.Schema{
   453  				Type:     schema.TypeString,
   454  				Computed: true,
   455  			},
   456  			"status": &schema.Schema{
   457  				Type:     schema.TypeString,
   458  				Computed: true,
   459  			},
   460  			"active_trusted_signers": &schema.Schema{
   461  				Type:     schema.TypeMap,
   462  				Computed: true,
   463  			},
   464  			"domain_name": &schema.Schema{
   465  				Type:     schema.TypeString,
   466  				Computed: true,
   467  			},
   468  			"last_modified_time": &schema.Schema{
   469  				Type:     schema.TypeString,
   470  				Computed: true,
   471  			},
   472  			"in_progress_validation_batches": &schema.Schema{
   473  				Type:     schema.TypeInt,
   474  				Computed: true,
   475  			},
   476  			"etag": &schema.Schema{
   477  				Type:     schema.TypeString,
   478  				Computed: true,
   479  			},
   480  			"hosted_zone_id": &schema.Schema{
   481  				Type:     schema.TypeString,
   482  				Computed: true,
   483  			},
   484  			// retain_on_delete is a non-API attribute that may help facilitate speedy
   485  			// deletion of a resoruce. It's mainly here for testing purposes, so
   486  			// enable at your own risk.
   487  			"retain_on_delete": &schema.Schema{
   488  				Type:     schema.TypeBool,
   489  				Optional: true,
   490  				Default:  false,
   491  			},
   492  
   493  			"tags": tagsSchema(),
   494  		},
   495  	}
   496  }
   497  
   498  func resourceAwsCloudFrontDistributionCreate(d *schema.ResourceData, meta interface{}) error {
   499  	conn := meta.(*AWSClient).cloudfrontconn
   500  
   501  	params := &cloudfront.CreateDistributionWithTagsInput{
   502  		DistributionConfigWithTags: &cloudfront.DistributionConfigWithTags{
   503  			DistributionConfig: expandDistributionConfig(d),
   504  			Tags:               tagsFromMapCloudFront(d.Get("tags").(map[string]interface{})),
   505  		},
   506  	}
   507  
   508  	resp, err := conn.CreateDistributionWithTags(params)
   509  	if err != nil {
   510  		return err
   511  	}
   512  	d.SetId(*resp.Distribution.Id)
   513  	return resourceAwsCloudFrontDistributionRead(d, meta)
   514  }
   515  
   516  func resourceAwsCloudFrontDistributionRead(d *schema.ResourceData, meta interface{}) error {
   517  	conn := meta.(*AWSClient).cloudfrontconn
   518  	params := &cloudfront.GetDistributionInput{
   519  		Id: aws.String(d.Id()),
   520  	}
   521  
   522  	resp, err := conn.GetDistribution(params)
   523  	if err != nil {
   524  		return err
   525  	}
   526  
   527  	// Update attributes from DistributionConfig
   528  	err = flattenDistributionConfig(d, resp.Distribution.DistributionConfig)
   529  	if err != nil {
   530  		return err
   531  	}
   532  	// Update other attributes outside of DistributionConfig
   533  	d.SetId(*resp.Distribution.Id)
   534  	err = d.Set("active_trusted_signers", flattenActiveTrustedSigners(resp.Distribution.ActiveTrustedSigners))
   535  	if err != nil {
   536  		return err
   537  	}
   538  	d.Set("status", resp.Distribution.Status)
   539  	d.Set("domain_name", resp.Distribution.DomainName)
   540  	d.Set("last_modified_time", aws.String(resp.Distribution.LastModifiedTime.String()))
   541  	d.Set("in_progress_validation_batches", resp.Distribution.InProgressInvalidationBatches)
   542  	d.Set("etag", resp.ETag)
   543  	d.Set("arn", resp.Distribution.ARN)
   544  
   545  	cloudFrontArn := resp.Distribution.ARN
   546  	tagResp, tagErr := conn.ListTagsForResource(&cloudfront.ListTagsForResourceInput{
   547  		Resource: cloudFrontArn,
   548  	})
   549  
   550  	if tagErr != nil {
   551  		log.Printf("[DEBUG] Error retrieving tags for ARN: %s", cloudFrontArn)
   552  	}
   553  
   554  	if tagResp != nil {
   555  		d.Set("tags", tagsToMapCloudFront(tagResp.Tags))
   556  	}
   557  
   558  	return nil
   559  }
   560  
   561  func resourceAwsCloudFrontDistributionUpdate(d *schema.ResourceData, meta interface{}) error {
   562  	conn := meta.(*AWSClient).cloudfrontconn
   563  	params := &cloudfront.UpdateDistributionInput{
   564  		Id:                 aws.String(d.Id()),
   565  		DistributionConfig: expandDistributionConfig(d),
   566  		IfMatch:            aws.String(d.Get("etag").(string)),
   567  	}
   568  	_, err := conn.UpdateDistribution(params)
   569  	if err != nil {
   570  		return err
   571  	}
   572  
   573  	if err := setTagsCloudFront(conn, d, d.Get("arn").(string)); err != nil {
   574  		return err
   575  	}
   576  
   577  	return resourceAwsCloudFrontDistributionRead(d, meta)
   578  }
   579  
   580  func resourceAwsCloudFrontDistributionDelete(d *schema.ResourceData, meta interface{}) error {
   581  	conn := meta.(*AWSClient).cloudfrontconn
   582  
   583  	// manually disable the distribution first
   584  	d.Set("enabled", false)
   585  	err := resourceAwsCloudFrontDistributionUpdate(d, meta)
   586  	if err != nil {
   587  		return err
   588  	}
   589  
   590  	// skip delete if retain_on_delete is enabled
   591  	if d.Get("retain_on_delete").(bool) {
   592  		log.Printf("[WARN] Removing Distributions ID %s with retain_on_delete set. Please delete this distribution manually.", d.Id())
   593  		d.SetId("")
   594  		return nil
   595  	}
   596  
   597  	// Distribution needs to be in deployed state again before it can be deleted.
   598  	err = resourceAwsCloudFrontDistributionWaitUntilDeployed(d.Id(), meta)
   599  	if err != nil {
   600  		return err
   601  	}
   602  
   603  	// now delete
   604  	params := &cloudfront.DeleteDistributionInput{
   605  		Id:      aws.String(d.Id()),
   606  		IfMatch: aws.String(d.Get("etag").(string)),
   607  	}
   608  
   609  	_, err = conn.DeleteDistribution(params)
   610  	if err != nil {
   611  		return err
   612  	}
   613  
   614  	// Done
   615  	d.SetId("")
   616  	return nil
   617  }
   618  
   619  // resourceAwsCloudFrontWebDistributionWaitUntilDeployed blocks until the
   620  // distribution is deployed. It currently takes exactly 15 minutes to deploy
   621  // but that might change in the future.
   622  func resourceAwsCloudFrontDistributionWaitUntilDeployed(id string, meta interface{}) error {
   623  	stateConf := &resource.StateChangeConf{
   624  		Pending:    []string{"InProgress", "Deployed"},
   625  		Target:     []string{"Deployed"},
   626  		Refresh:    resourceAwsCloudFrontWebDistributionStateRefreshFunc(id, meta),
   627  		Timeout:    40 * time.Minute,
   628  		MinTimeout: 15 * time.Second,
   629  		Delay:      10 * time.Minute,
   630  	}
   631  
   632  	_, err := stateConf.WaitForState()
   633  	return err
   634  }
   635  
   636  // The refresh function for resourceAwsCloudFrontWebDistributionWaitUntilDeployed.
   637  func resourceAwsCloudFrontWebDistributionStateRefreshFunc(id string, meta interface{}) resource.StateRefreshFunc {
   638  	return func() (interface{}, string, error) {
   639  		conn := meta.(*AWSClient).cloudfrontconn
   640  		params := &cloudfront.GetDistributionInput{
   641  			Id: aws.String(id),
   642  		}
   643  
   644  		resp, err := conn.GetDistribution(params)
   645  		if err != nil {
   646  			log.Printf("Error on retrieving CloudFront distribution when waiting: %s", err)
   647  			return nil, "", err
   648  		}
   649  
   650  		if resp == nil {
   651  			return nil, "", nil
   652  		}
   653  
   654  		return resp.Distribution, *resp.Distribution.Status, nil
   655  	}
   656  }
   657  
   658  // validateHTTP ensures that the http_version resource parameter is
   659  // correct.
   660  func validateHTTP(v interface{}, k string) (ws []string, errors []error) {
   661  	value := v.(string)
   662  	found := false
   663  	for _, w := range []string{"http1.1", "http2"} {
   664  		if value == w {
   665  			found = true
   666  		}
   667  	}
   668  	if found == false {
   669  		errors = append(errors, fmt.Errorf(
   670  			"HTTP version parameter must be one of http1.1 or http2"))
   671  	}
   672  	return
   673  }