diff --git a/README.md b/README.md index a29b843..bf6effb 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,13 @@ Type `C-c C-d C-c` to kill the URL (i.e. copy it to the clipboard) for the docum You can also type `C-c C-d C-r` to insert a comment containing a link to this documentation right above the resource or data block. +This feature requires either: + +- a `required_provider` declaration in any `.tf` file in current directory + (see [Terraform doc](https://developer.hashicorp.com/terraform/language/providers/requirements#requiring-providers)) +- a working `terraform providers` command. This command may require a + valid token (at least for AWS). + ## Customize Variables #### `terraform-indent-level`(Default: `2`) diff --git a/terraform-mode.el b/terraform-mode.el index 238d2e4..6328ae6 100644 --- a/terraform-mode.el +++ b/terraform-mode.el @@ -77,6 +77,9 @@ "Face for varriables." :group 'terraform-mode) +(defconst terraform--constants-regexp + (concat "\\(?:^\\|[^.]\\)" (regexp-opt '("null") 'words))) + (defconst terraform--block-builtins-without-name-or-type-regexp (rx line-start (zero-or-more space) @@ -154,6 +157,7 @@ (1 'terraform-builtin-face) (2 'terraform-resource-type-face t) (3 'terraform-resource-name-face t)) + (,terraform--constants-regexp 1 'font-lock-constant-face) ,@hcl-font-lock-keywords)) (defun terraform-format-buffer () @@ -252,18 +256,59 @@ (when (re-search-forward (concat "/\\(.*?\\)/" provider "\\]") nil t) (match-string 1))))) +(defun terraform--get-resource-provider-source (provider &optional dir) + "Return Terraform provider source for PROVIDER located in DIR. +Terraform provider source is searched in `required_provider' declaration +in current buffer or in other Terraform files located in the same directory +of the file of current buffer. If still not found, the provider source is +searched by running command `terraform providers'. +The DIR parameter is optional and used only for tests." + (goto-char (point-min)) + ;; find current directory if it's not specified in arguments + (if (and (not dir) buffer-file-name) (setq dir (file-name-directory buffer-file-name))) + (let (tf-files + ;; try to find provider source in current buffer + (provider-source (terraform--get-resource-provider-source-in-buffer provider))) + ;; check if terraform provider-source was found + (when (and (= (length provider-source) 0) dir) + ;; find all terraform files of this project. One of them + ;; should contain required_provider declaration + (setq tf-files (directory-files dir nil "^[[:alnum:][:blank:]_.-]+\\.tf$"))) + ;; iterate on terraform files until a provider source is found + (while (and (= (length provider-source) 0) tf-files) + (with-temp-buffer + (let* ((file (pop tf-files)) + (file-path (if dir (concat dir "/" file) file))) + (insert-file-contents file-path) + ;; look for provider source in a terraform file + (setq provider-source (terraform--get-resource-provider-source-in-buffer provider))))) + provider-source)) + +(defun terraform--get-resource-provider-source-in-buffer (provider) + "Search and return provider namespace for PROVIDER in current buffer. +Return nil if not found." + (goto-char (point-min)) + (if (and (re-search-forward "^terraform[[:blank:]]*{" nil t) + (re-search-forward "^[[:blank:]]*required_providers[[:blank:]]*{" nil t) + (re-search-forward (concat "^[[:blank:]]*" provider "[[:blank:]]*=[[:blank:]]*{") nil t) + (re-search-forward "^[[:blank:]]*source[[:blank:]]*=[[:blank:]]*\"\\([a-z/]+\\)\"" nil t)) + (match-string 1))) + (defun terraform--resource-url (resource doc-dir) "Return the url containing the documentation for RESOURCE using DOC-DIR." (let* ((provider (terraform--extract-provider resource)) - (provider-ns (terraform--get-resource-provider-namespace provider)) + ;; search provider source in terraform files + (provider-source (terraform--get-resource-provider-source provider)) (resource-name (terraform--extract-resource resource))) - (if provider-ns - (format "https://registry.terraform.io/providers/%s/%s/latest/docs/%s/%s" - provider-ns - provider - doc-dir - resource-name) - (user-error "Can not determine the provider namespace for %s" provider)))) + (when (= (length provider-source) 0) + ;; fallback to old method with terraform providers command + (setq provider-source (concat + (terraform--get-resource-provider-namespace provider) + "/" provider))) + (if (> (length provider-source) 0) + (format "https://registry.terraform.io/providers/%s/latest/docs/%s/%s" + provider-source doc-dir resource-name) + (user-error "Can not determine the provider source for %s" provider)))) (defun terraform--resource-url-at-point () (save-excursion @@ -359,7 +404,7 @@ If the point is not at the heading, call (imenu-add-to-menubar "Terraform")) ;;;###autoload -(add-to-list 'auto-mode-alist '("\\.tf\\(vars\\)?\\'" . terraform-mode)) +(add-to-list 'auto-mode-alist '("\\.t\\(f\\(vars\\)?\\|ofu\\)\\'" . terraform-mode)) (provide 'terraform-mode) diff --git a/test/fixtures/main.tf b/test/fixtures/main.tf new file mode 100644 index 0000000..01ea2a8 --- /dev/null +++ b/test/fixtures/main.tf @@ -0,0 +1 @@ +# blah blah diff --git a/test/fixtures/provider.tf b/test/fixtures/provider.tf new file mode 100644 index 0000000..4b0b295 --- /dev/null +++ b/test/fixtures/provider.tf @@ -0,0 +1,13 @@ +# blah blah +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5" + } + azurerm = { + source = "hashicorp/azurerm" + version = "~> 3.47" + } + } +} diff --git a/test/test-command.el b/test/test-command.el index 5baa5ea..4a629cd 100644 --- a/test/test-command.el +++ b/test/test-command.el @@ -156,4 +156,49 @@ resource \"elasticstack_elasticsearch_security_user\" \"filebeat_writer\" { (cl-letf (((symbol-function 'terraform--get-resource-provider-namespace) (lambda (prov) "elastic"))) (should (equal (terraform--resource-url-at-point) "https://registry.terraform.io/providers/elastic/elasticstack/latest/docs/resources/elasticsearch_security_user"))))) +(ert-deftest command--terraform--get-resource-provider-source-in-buffer () + (with-terraform-temp-buffer + " +# blah blah +terraform { + required_providers { + aws = { + source = \"hashicorp/aws\" + version = \"~> 5\" + } + azurerm = { + source = \"hashicorp/azurerm\" + version = \"~> 3.47\" + } + } +} +" + (should (equal (terraform--get-resource-provider-source-in-buffer "azurerm") "hashicorp/azurerm")) + ;;(should (equal (terraform--get-resource-provider-source-in-buffer "plop") nil)) + (should (equal (terraform--get-resource-provider-source-in-buffer "aws") "hashicorp/aws")))) + +;; required_providers is defined in current buffer +(ert-deftest command--terraform--get-resource-provider-source () + (with-terraform-temp-buffer + " +# blah blah +terraform { + required_providers { + aws = { + source = \"hashicorp/aws\" + version = \"~> 5\" + } + azurerm = { + source = \"hashicorp/azurerm\" + version = \"~> 3.47\" + } + } +} +" + (should (equal (terraform--get-resource-provider-source "aws" "test/fixtures") "hashicorp/aws")))) + +;; required_providers is defined in another file +(ert-deftest command--terraform--get-resource-provider-source-provider-in-file () + (should (equal (terraform--get-resource-provider-source "aws" "test/fixtures") "hashicorp/aws"))) + ;;; test-command.el ends here diff --git a/test/test-highlighting.el b/test/test-highlighting.el index f157598..4a09ae3 100644 --- a/test/test-highlighting.el +++ b/test/test-highlighting.el @@ -32,6 +32,14 @@ keyword (should (face-at-cursor-p 'font-lock-constant-face))))) +(ert-deftest font-lock--constants-keywords () + "Syntax highlight of constant keywords" + + (dolist (keyword '("null")) + (with-terraform-temp-buffer + keyword + (should (face-at-cursor-p 'font-lock-constant-face))))) + (ert-deftest font-lock--provider-block--with-one-space () "Syntax highlight of `provider' block."